From f9b2255847ef1cd809390b80051bb162b05963b8 Mon Sep 17 00:00:00 2001 From: Yamilet P Date: Wed, 22 Apr 2026 13:35:48 -0500 Subject: [PATCH 1/3] fix: action buttons of left column in toggle block hover (#2526) --- .../core/src/extensions/SideMenu/SideMenu.ts | 34 ++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/packages/core/src/extensions/SideMenu/SideMenu.ts b/packages/core/src/extensions/SideMenu/SideMenu.ts index e65d4b1d99..445ba7a337 100644 --- a/packages/core/src/extensions/SideMenu/SideMenu.ts +++ b/packages/core/src/extensions/SideMenu/SideMenu.ts @@ -47,13 +47,18 @@ function getBlockFromCoords( continue; } if (adjustForColumns) { - const column = element.closest("[data-node-type=columnList]"); + const column = element.closest("[data-node-type=column]"); if (column) { + + const columnRect = column.getBoundingClientRect(); + return getBlockFromCoords( view, { - // TODO can we do better than this? - left: coords.left + 50, // bit hacky, but if we're inside a column, offset x position to right to account for the width of sidemenu itself + left: Math.min( + Math.max(columnRect.left + 10, coords.left + 20), + columnRect.right - 10, + ), top: coords.top, }, false, @@ -101,6 +106,21 @@ function getBlockFromMousePos( return undefined; } + const column = referenceBlock.node.closest("[data-node-type=column]"); + + if (column) { + const columnRect = column.getBoundingClientRect(); + + return getBlockFromCoords( + view, + { + left: Math.min(columnRect.left + 20, columnRect.right - 10), + top: mousePos.y, + }, + false, + ); + } + /** * Because blocks may be nested, we need to check the right edge of the parent block: * ``` @@ -250,7 +270,7 @@ export class SideMenuView< // padding. This is a little weird since this child element will // be the first block, but since it's always non-nested and we // only take the x coordinate, it's ok. - column.firstElementChild!.getBoundingClientRect().x + column.getBoundingClientRect().x : ( this.pmView.dom.firstChild as HTMLElement ).getBoundingClientRect().x, @@ -603,6 +623,12 @@ export class SideMenuView< this.mousePos = { x: event.clientX, y: event.clientY }; + const target = event.target as HTMLElement | null; + + if (target?.closest(".bn-side-menu")) { + return; + } + // We want the full area of the editor to check if the cursor is hovering // above it though. const editorOuterBoundingBox = this.pmView.dom.getBoundingClientRect(); From 3aef93b8b6469c60092cda8c7628fa6f6d4e66d5 Mon Sep 17 00:00:00 2001 From: Yamilet P Date: Wed, 22 Apr 2026 15:54:56 -0500 Subject: [PATCH 2/3] test: add draghandle test for column nested in toggle block --- tests/src/end-to-end/draghandle/draghandle.test.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/src/end-to-end/draghandle/draghandle.test.ts b/tests/src/end-to-end/draghandle/draghandle.test.ts index f77c414d2b..57e200c20c 100644 --- a/tests/src/end-to-end/draghandle/draghandle.test.ts +++ b/tests/src/end-to-end/draghandle/draghandle.test.ts @@ -181,4 +181,18 @@ test.describe("Check Draghandle functionality", () => { await compareDocToSnapshot(page, "draghandlenesteddelete"); }); + + test("Hovering over column nested in toggle block should show draghandle", async () => { + await executeSlashCommand(page, "togglelist"); + await page.keyboard.type("Toggle heading"); + await page.keyboard.press("Enter"); + await executeSlashCommand(page, "two"); + await page.keyboard.type("Left column content"); + const leftColumn = await page.locator(".bn-block-column").first(); + await moveMouseOverElement(page, leftColumn); + await page.waitForSelector(DRAG_HANDLE_SELECTOR); + await page.click(DRAG_HANDLE_SELECTOR); + await page.waitForSelector(DRAG_HANDLE_MENU_SELECTOR); + }); + }); From efc1016b248f6eba713d7f1f54736625bf67a2cf Mon Sep 17 00:00:00 2001 From: Yamilet P Date: Wed, 22 Apr 2026 22:07:27 -0500 Subject: [PATCH 3/3] fix: include non-editable blocks in Ctrl+A selection and fix nested paste. --- .../src/api/clipboard/toClipboard/copyExtension.ts | 13 +++++++++---- .../KeyboardShortcuts/KeyboardShortcutsExtension.ts | 13 ++++++++++++- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/packages/core/src/api/clipboard/toClipboard/copyExtension.ts b/packages/core/src/api/clipboard/toClipboard/copyExtension.ts index 3a6aeaffd5..5e36fb8f1a 100644 --- a/packages/core/src/api/clipboard/toClipboard/copyExtension.ts +++ b/packages/core/src/api/clipboard/toClipboard/copyExtension.ts @@ -1,5 +1,5 @@ import { Extension } from "@tiptap/core"; -import { Fragment, Node } from "prosemirror-model"; +import { Fragment, Node, Slice } from "prosemirror-model"; import { NodeSelection, Plugin } from "prosemirror-state"; import { CellSelection } from "prosemirror-tables"; import type { EditorView } from "prosemirror-view"; @@ -127,13 +127,18 @@ export function selectedFragmentToHTML< ); } + let selectedFragment = view.state.selection.content().content; + + if (selectedFragment.childCount === 1 && + selectedFragment.firstChild?.type.name === "blockGroup") { + selectedFragment = selectedFragment.firstChild.content; + } + // Uses default ProseMirror clipboard serialization. const clipboardHTML: string = view.serializeForClipboard( - view.state.selection.content(), + new Slice(selectedFragment, 0, 0) ).dom.innerHTML; - const selectedFragment = view.state.selection.content().content; - const externalHTML = fragmentToExternalHTML( view, selectedFragment, diff --git a/packages/core/src/extensions/tiptap-extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts b/packages/core/src/extensions/tiptap-extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts index c0578d7f38..6ca1961ddf 100644 --- a/packages/core/src/extensions/tiptap-extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts +++ b/packages/core/src/extensions/tiptap-extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts @@ -1,6 +1,6 @@ import { Extension } from "@tiptap/core"; import { Fragment, Node } from "prosemirror-model"; -import { TextSelection } from "prosemirror-state"; +import { AllSelection, TextSelection } from "prosemirror-state"; import { getBottomNestedBlockInfo, @@ -953,6 +953,17 @@ export const KeyboardShortcutsExtension = Extension.create<{ "Mod-z": () => this.options.editor.undo(), "Mod-y": () => this.options.editor.redo(), "Shift-Mod-z": () => this.options.editor.redo(), + // Forces AllSelection from pos 0 to include non-editable blocks (e.g. images) that + // TextSelection would skip. + "Mod-a": () => { + const { doc } = this.options.editor.prosemirrorState; + this.options.editor.prosemirrorView?.dispatch( + this.options.editor.prosemirrorState.tr.setSelection( + new AllSelection(doc) + ) + ); + return true; + }, }; }, });