diff --git a/packages/core/src/api/clipboard/toClipboard/copyExtension.ts b/packages/core/src/api/clipboard/toClipboard/copyExtension.ts index 3a6aeaffd5..47dd18fc9a 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,23 @@ export function selectedFragmentToHTML< ); } + const originalSlice = view.state.selection.content(); + + // When AllSelection is used, ProseMirror wraps contents in blockGroup node. + // Unwrap it to avoid nested blocks when pasting. + + let selectedFragment = originalSlice.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, originalSlice.openStart, originalSlice.openEnd) ).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; + }, }; }, }); diff --git a/tests/src/end-to-end/copypaste/copypaste.test.ts b/tests/src/end-to-end/copypaste/copypaste.test.ts index aaf36897d6..e796b9d707 100644 --- a/tests/src/end-to-end/copypaste/copypaste.test.ts +++ b/tests/src/end-to-end/copypaste/copypaste.test.ts @@ -187,4 +187,30 @@ test.describe("Check Copy/Paste Functionality", () => { await compareDocToSnapshot(page, "images.json"); }); + + test("Image as first block should be able to be copied using Ctrl+A", async ({ + page, + browserName, + }) => { + test.skip( + browserName === "firefox" || browserName === "webkit", + "Firefox doesn't yet support the async clipboard API. Webkit copy/paste stopped working after updating to Playwright 1.33.", + ); + + await focusOnEditor(page); + + const IMAGE_EMBED_URL = "https://placehold.co/800x540.png"; + await executeSlashCommand(page, "image"); + + await page.click(`[data-test="embed-tab"]`); + await page.click(`[data-test="embed-input"]`); + await page.keyboard.type(IMAGE_EMBED_URL); + await page.click(`[data-test="embed-input-button"]`); + await page.waitForSelector(`img[src="${IMAGE_EMBED_URL}"]`); + + await copyPasteAll(page); + + await compareDocToSnapshot(page, "imageAsFirstBlock.json"); + }); + });