From 589c7e746b4a8809bc6bef1be517d203f39b079e Mon Sep 17 00:00:00 2001 From: mianguyen Date: Sat, 4 Apr 2026 17:25:33 -0500 Subject: [PATCH 1/4] fix: move an image doesn't work with drag and drop (#2603) --- .../FormattingToolbar/FormattingToolbar.ts | 29 ++++++++++--- tests/src/end-to-end/images/images.test.ts | 43 ++++++++++++++++++- 2 files changed, 65 insertions(+), 7 deletions(-) diff --git a/packages/core/src/extensions/FormattingToolbar/FormattingToolbar.ts b/packages/core/src/extensions/FormattingToolbar/FormattingToolbar.ts index 021c20ea3d..0b30d7474b 100644 --- a/packages/core/src/extensions/FormattingToolbar/FormattingToolbar.ts +++ b/packages/core/src/extensions/FormattingToolbar/FormattingToolbar.ts @@ -61,16 +61,17 @@ export const FormattingToolbarExtension = createExtension(({ editor }) => { * We want to mimic the Notion behavior of not showing the toolbar while the user is holding down the mouse button (to create a selection) */ let preventShowWhileMouseDown = false; + let preventShowWhileDragging = false; const unsubscribeOnChange = editor.onChange(() => { - if (preventShowWhileMouseDown) { + if (preventShowWhileMouseDown || preventShowWhileDragging) { return; } // re-evaluate whether the toolbar should be shown store.setState(shouldShow()); }); const unsubscribeOnSelectionChange = editor.onSelectionChange(() => { - if (preventShowWhileMouseDown) { + if (preventShowWhileMouseDown || preventShowWhileDragging) { return; } // re-evaluate whether the toolbar should be shown @@ -91,6 +92,7 @@ export const FormattingToolbarExtension = createExtension(({ editor }) => { "pointerup", () => { preventShowWhileMouseDown = false; + // We only want to re-show the toolbar if the mouse made the selection if (editor.isFocused()) { store.setState(shouldShow()); @@ -102,12 +104,27 @@ export const FormattingToolbarExtension = createExtension(({ editor }) => { dom.addEventListener( "pointercancel", () => { - preventShowWhileMouseDown = false; + preventShowWhileMouseDown = true; + }, + { signal, + capture: true }, + ); + + editor.prosemirrorView.root.addEventListener( + "dragstart", + () => { + preventShowWhileDragging = true; + store.setState(false); }, - { - signal, - capture: true, + { signal }, + ); + + editor.prosemirrorView.root.addEventListener( + "dragend", + () => { + preventShowWhileDragging = false; }, + { signal }, ); signal.addEventListener("abort", () => { diff --git a/tests/src/end-to-end/images/images.test.ts b/tests/src/end-to-end/images/images.test.ts index 7e1a862a18..bf2cf98c3f 100644 --- a/tests/src/end-to-end/images/images.test.ts +++ b/tests/src/end-to-end/images/images.test.ts @@ -2,12 +2,13 @@ import { FileChooser, expect } from "@playwright/test"; import { test } from "../../setup/setupScript.js"; import { BASE_URL, + DRAG_HANDLE_SELECTOR, H_ONE_BLOCK_SELECTOR, IMAGE_SELECTOR, } from "../../utils/const.js"; import { insertHeading } from "../../utils/copypaste.js"; import { compareDocToSnapshot, focusOnEditor } from "../../utils/editor.js"; -import { dragAndDropBlock } from "../../utils/mouse.js"; +import { dragAndDropBlock, moveMouseOverElement } from "../../utils/mouse.js"; import { executeSlashCommand } from "../../utils/slashmenu.js"; const IMAGE_UPLOAD_PATH = "src/end-to-end/images/placeholder.png"; @@ -128,4 +129,44 @@ test.describe("Check Image Block and Toolbar functionality", () => { await compareDocToSnapshot(page, "dragImage"); }); + test("Formatting toolbar should not appear when dragging image block", async ({ + page, + }) => { + await focusOnEditor(page); + await executeSlashCommand(page, "image"); + await insertHeading(page, 1); + + // move mouse over image to reveal drag handle + const dragTarget = page.locator(IMAGE_SELECTOR); + await moveMouseOverElement(page, dragTarget); + await page.waitForTimeout(100); + + await page.waitForSelector(DRAG_HANDLE_SELECTOR); + const dragHandle = page.locator(DRAG_HANDLE_SELECTOR); + const dragHandleBox = (await dragHandle.boundingBox())!; + + // start drag from the drag handle + await page.mouse.move( + dragHandleBox.x + dragHandleBox.width / 2, + dragHandleBox.y + dragHandleBox.height / 2, + { steps: 5 }, + ); + await page.mouse.down(); + await page.waitForTimeout(100); + + // move mid-drag to the heading block + const dropTarget = page.locator(H_ONE_BLOCK_SELECTOR); + const dropTargetBox = (await dropTarget.boundingBox())!; + await page.mouse.move( + dropTargetBox.x + dropTargetBox.width / 2, + dropTargetBox.y + dropTargetBox.height / 2, + { steps: 5 }, + ); + + // assert formatting toolbar is not visible during drag + const toolbar = page.locator(".bn-formatting-toolbar"); + await expect(toolbar).not.toBeVisible(); + + await page.mouse.up(); + }); }); From 6d0218b798f1383ea6dea328eb442c02a4345aa2 Mon Sep 17 00:00:00 2001 From: Matthew Lipski Date: Wed, 22 Apr 2026 12:36:29 +0200 Subject: [PATCH 2/4] Removed redundant check in formatting toolbar `shouldShow` --- .../FormattingToolbar/FormattingToolbar.ts | 22 +++++-------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/packages/core/src/extensions/FormattingToolbar/FormattingToolbar.ts b/packages/core/src/extensions/FormattingToolbar/FormattingToolbar.ts index 0b30d7474b..84524eee2e 100644 --- a/packages/core/src/extensions/FormattingToolbar/FormattingToolbar.ts +++ b/packages/core/src/extensions/FormattingToolbar/FormattingToolbar.ts @@ -1,4 +1,4 @@ -import { NodeSelection, TextSelection } from "prosemirror-state"; +import { TextSelection } from "prosemirror-state"; import { createExtension, @@ -16,15 +16,6 @@ export const FormattingToolbarExtension = createExtension(({ editor }) => { return false; } - // Don't show if a block with inline content is selected. - if ( - tr.selection instanceof NodeSelection && - (tr.selection.node.type.spec.content === "inline*" || - tr.selection.node.firstChild?.type.spec.content === "inline*") - ) { - return false; - } - // Don't show if the selection is a text selection but contains no text. if ( tr.selection instanceof TextSelection && @@ -71,7 +62,7 @@ export const FormattingToolbarExtension = createExtension(({ editor }) => { store.setState(shouldShow()); }); const unsubscribeOnSelectionChange = editor.onSelectionChange(() => { - if (preventShowWhileMouseDown || preventShowWhileDragging) { + if (preventShowWhileMouseDown || preventShowWhileDragging) { return; } // re-evaluate whether the toolbar should be shown @@ -92,7 +83,7 @@ export const FormattingToolbarExtension = createExtension(({ editor }) => { "pointerup", () => { preventShowWhileMouseDown = false; - + // We only want to re-show the toolbar if the mouse made the selection if (editor.isFocused()) { store.setState(shouldShow()); @@ -106,8 +97,7 @@ export const FormattingToolbarExtension = createExtension(({ editor }) => { () => { preventShowWhileMouseDown = true; }, - { signal, - capture: true }, + { signal, capture: true }, ); editor.prosemirrorView.root.addEventListener( @@ -116,7 +106,7 @@ export const FormattingToolbarExtension = createExtension(({ editor }) => { preventShowWhileDragging = true; store.setState(false); }, - { signal }, + { signal }, ); editor.prosemirrorView.root.addEventListener( @@ -124,7 +114,7 @@ export const FormattingToolbarExtension = createExtension(({ editor }) => { () => { preventShowWhileDragging = false; }, - { signal }, + { signal }, ); signal.addEventListener("abort", () => { From 88aae167894a923d65c08f68bb4521d102be9847 Mon Sep 17 00:00:00 2001 From: Matthew Lipski Date: Wed, 22 Apr 2026 15:35:04 +0200 Subject: [PATCH 3/4] Moved/refactored tests --- .../src/end-to-end/dragdrop/dragdrop.test.ts | 32 ++++++++++ .../dragImage-chromium-linux.json | 0 .../dragImage-firefox-linux.json | 0 .../dragImage-webkit-linux.json | 0 tests/src/end-to-end/images/images.test.ts | 62 +------------------ 5 files changed, 33 insertions(+), 61 deletions(-) rename tests/src/end-to-end/{images/images.test.ts-snapshots => dragdrop/dragdrop.test.ts-snapshots}/dragImage-chromium-linux.json (100%) rename tests/src/end-to-end/{images/images.test.ts-snapshots => dragdrop/dragdrop.test.ts-snapshots}/dragImage-firefox-linux.json (100%) rename tests/src/end-to-end/{images/images.test.ts-snapshots => dragdrop/dragdrop.test.ts-snapshots}/dragImage-webkit-linux.json (100%) diff --git a/tests/src/end-to-end/dragdrop/dragdrop.test.ts b/tests/src/end-to-end/dragdrop/dragdrop.test.ts index aad78548c7..ea5eb94ce7 100644 --- a/tests/src/end-to-end/dragdrop/dragdrop.test.ts +++ b/tests/src/end-to-end/dragdrop/dragdrop.test.ts @@ -1,14 +1,17 @@ +import { expect } from "@playwright/test"; import { test } from "../../setup/setupScript.js"; import { BASE_URL, H_ONE_BLOCK_SELECTOR, H_THREE_BLOCK_SELECTOR, H_TWO_BLOCK_SELECTOR, + IMAGE_SELECTOR, PARAGRAPH_SELECTOR, } from "../../utils/const.js"; import { insertHeading, insertParagraph } from "../../utils/copypaste.js"; import { compareDocToSnapshot, focusOnEditor } from "../../utils/editor.js"; import { dragAndDropBlock } from "../../utils/mouse.js"; +import { executeSlashCommand } from "../../utils/slashmenu.js"; test.describe.configure({ mode: "serial" }); @@ -80,4 +83,33 @@ test.describe("Check Block Dragging Functionality", () => { await compareDocToSnapshot(page, "dragdropnested"); }); + + test("Should be able to drag image", async ({ page }) => { + await focusOnEditor(page); + await executeSlashCommand(page, "image"); + + await insertHeading(page, 1); + + const dragTarget = await page.locator(IMAGE_SELECTOR); + const dropTarget = await page.locator(H_ONE_BLOCK_SELECTOR); + await page.pause(); + await dragAndDropBlock(page, dragTarget, dropTarget, false); + + await compareDocToSnapshot(page, "dragImage"); + }); + + test("Formatting toolbar should not appear when dragging image block", async ({ + page, + }) => { + await focusOnEditor(page); + await executeSlashCommand(page, "image"); + await insertHeading(page, 1); + + const dragTarget = page.locator(IMAGE_SELECTOR); + const dropTarget = page.locator(H_ONE_BLOCK_SELECTOR); + await dragAndDropBlock(page, dragTarget, dropTarget, false); + + const toolbar = page.locator(".bn-formatting-toolbar"); + await expect(toolbar).not.toBeVisible(); + }); }); diff --git a/tests/src/end-to-end/images/images.test.ts-snapshots/dragImage-chromium-linux.json b/tests/src/end-to-end/dragdrop/dragdrop.test.ts-snapshots/dragImage-chromium-linux.json similarity index 100% rename from tests/src/end-to-end/images/images.test.ts-snapshots/dragImage-chromium-linux.json rename to tests/src/end-to-end/dragdrop/dragdrop.test.ts-snapshots/dragImage-chromium-linux.json diff --git a/tests/src/end-to-end/images/images.test.ts-snapshots/dragImage-firefox-linux.json b/tests/src/end-to-end/dragdrop/dragdrop.test.ts-snapshots/dragImage-firefox-linux.json similarity index 100% rename from tests/src/end-to-end/images/images.test.ts-snapshots/dragImage-firefox-linux.json rename to tests/src/end-to-end/dragdrop/dragdrop.test.ts-snapshots/dragImage-firefox-linux.json diff --git a/tests/src/end-to-end/images/images.test.ts-snapshots/dragImage-webkit-linux.json b/tests/src/end-to-end/dragdrop/dragdrop.test.ts-snapshots/dragImage-webkit-linux.json similarity index 100% rename from tests/src/end-to-end/images/images.test.ts-snapshots/dragImage-webkit-linux.json rename to tests/src/end-to-end/dragdrop/dragdrop.test.ts-snapshots/dragImage-webkit-linux.json diff --git a/tests/src/end-to-end/images/images.test.ts b/tests/src/end-to-end/images/images.test.ts index bf2cf98c3f..b4c7dd2910 100644 --- a/tests/src/end-to-end/images/images.test.ts +++ b/tests/src/end-to-end/images/images.test.ts @@ -1,14 +1,7 @@ import { FileChooser, expect } from "@playwright/test"; import { test } from "../../setup/setupScript.js"; -import { - BASE_URL, - DRAG_HANDLE_SELECTOR, - H_ONE_BLOCK_SELECTOR, - IMAGE_SELECTOR, -} from "../../utils/const.js"; -import { insertHeading } from "../../utils/copypaste.js"; +import { BASE_URL } from "../../utils/const.js"; import { compareDocToSnapshot, focusOnEditor } from "../../utils/editor.js"; -import { dragAndDropBlock, moveMouseOverElement } from "../../utils/mouse.js"; import { executeSlashCommand } from "../../utils/slashmenu.js"; const IMAGE_UPLOAD_PATH = "src/end-to-end/images/placeholder.png"; @@ -116,57 +109,4 @@ test.describe("Check Image Block and Toolbar functionality", () => { await compareDocToSnapshot(page, "deleteImage"); }); - test("Should be able to drag image", async ({ page }) => { - await focusOnEditor(page); - await executeSlashCommand(page, "image"); - - await insertHeading(page, 1); - - const dragTarget = await page.locator(IMAGE_SELECTOR); - const dropTarget = await page.locator(H_ONE_BLOCK_SELECTOR); - await page.pause(); - await dragAndDropBlock(page, dragTarget, dropTarget, false); - - await compareDocToSnapshot(page, "dragImage"); - }); - test("Formatting toolbar should not appear when dragging image block", async ({ - page, - }) => { - await focusOnEditor(page); - await executeSlashCommand(page, "image"); - await insertHeading(page, 1); - - // move mouse over image to reveal drag handle - const dragTarget = page.locator(IMAGE_SELECTOR); - await moveMouseOverElement(page, dragTarget); - await page.waitForTimeout(100); - - await page.waitForSelector(DRAG_HANDLE_SELECTOR); - const dragHandle = page.locator(DRAG_HANDLE_SELECTOR); - const dragHandleBox = (await dragHandle.boundingBox())!; - - // start drag from the drag handle - await page.mouse.move( - dragHandleBox.x + dragHandleBox.width / 2, - dragHandleBox.y + dragHandleBox.height / 2, - { steps: 5 }, - ); - await page.mouse.down(); - await page.waitForTimeout(100); - - // move mid-drag to the heading block - const dropTarget = page.locator(H_ONE_BLOCK_SELECTOR); - const dropTargetBox = (await dropTarget.boundingBox())!; - await page.mouse.move( - dropTargetBox.x + dropTargetBox.width / 2, - dropTargetBox.y + dropTargetBox.height / 2, - { steps: 5 }, - ); - - // assert formatting toolbar is not visible during drag - const toolbar = page.locator(".bn-formatting-toolbar"); - await expect(toolbar).not.toBeVisible(); - - await page.mouse.up(); - }); }); From f83b5d60239b811c4c4e4087c70885eb4b8a973a Mon Sep 17 00:00:00 2001 From: Matthew Lipski Date: Wed, 22 Apr 2026 16:07:50 +0200 Subject: [PATCH 4/4] Implemented PR feedback --- tests/src/end-to-end/dragdrop/dragdrop.test.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/src/end-to-end/dragdrop/dragdrop.test.ts b/tests/src/end-to-end/dragdrop/dragdrop.test.ts index ea5eb94ce7..45a3e4a3db 100644 --- a/tests/src/end-to-end/dragdrop/dragdrop.test.ts +++ b/tests/src/end-to-end/dragdrop/dragdrop.test.ts @@ -84,7 +84,11 @@ test.describe("Check Block Dragging Functionality", () => { await compareDocToSnapshot(page, "dragdropnested"); }); - test("Should be able to drag image", async ({ page }) => { + test("Should be able to drag image", async ({ page, browserName }) => { + test.skip( + browserName === "firefox", + "Playwright doesn't correctly simulate drag events in Firefox.", + ); await focusOnEditor(page); await executeSlashCommand(page, "image"); @@ -100,7 +104,12 @@ test.describe("Check Block Dragging Functionality", () => { test("Formatting toolbar should not appear when dragging image block", async ({ page, + browserName, }) => { + test.skip( + browserName === "firefox", + "Playwright doesn't correctly simulate drag events in Firefox.", + ); await focusOnEditor(page); await executeSlashCommand(page, "image"); await insertHeading(page, 1);