Skip to content

fix: cannot copy image when it is the first/only block in editor (#2016)#2672

Open
yamiletpineda wants to merge 3 commits intoTypeCellOS:mainfrom
yamiletpineda:fix/cmd-a-image-copy
Open

fix: cannot copy image when it is the first/only block in editor (#2016)#2672
yamiletpineda wants to merge 3 commits intoTypeCellOS:mainfrom
yamiletpineda:fix/cmd-a-image-copy

Conversation

@yamiletpineda
Copy link
Copy Markdown

@yamiletpineda yamiletpineda commented Apr 23, 2026

Summary

Fixes #2016. Image cannot be copied using Ctrl+A and Ctrl+C keyboard shortcuts when it is the first or only block in the editor.

Rationale

A user should be able to copy an image using the appropriate keyboard commands. As this is a common user action, the unexpected behavior is disruptive to user workflows.

Changes

  • Added a mod-a keyboard shortcut handler in KeyboardShortcutsExtension.ts that forces an AllSelection covering the entire document from position 0. Without this, the Ctrl+A keyboard command created a TextSelection starting at position 6, skipping the first block when it was a non-editable node (e.g. image).
  • Unwrapped blockGroup from the selected fragment before building clipboard data in copyExtension.ts, preventing nested blocks when pasting.

Impact

  • Ctrl+A now selects all blocks including non-editable ones at the start of the document.
  • Normal copy and paste behavior is unaffected.

Testing

  • Manually tested fix on Chrome and Firefox.
  • Added a Playwright end-to-end test for implementation in copypaste.test.ts.
  • All existing copy/paste equality tests pass after fixing slice open depth preservation when unwrapping the blockGroup node.

Screenshots/Video

copy-image-keyboard-shortcut.mov

Checklist

  • Code follows the project's coding standards.
  • Unit tests covering the new feature have been added.
  • All existing tests pass.
  • The documentation has been updated to reflect the new feature

Additional Notes

  • Limitation: Non-editable blocks (e.g. images) are not visually highlighted when selected via Ctrl+A. Follow-up CSS work needed.
  • The Playwright test requires a snapshot to be generated by running pnpm playwright test copypaste for the first time.

Summary by CodeRabbit

  • New Features

    • Added select all keyboard shortcut (Ctrl+A/Cmd+A) to select entire document content.
  • Bug Fixes

    • Improved clipboard handling for copy/paste operations with block-level content.
  • Tests

    • Added test coverage for copying and pasting images positioned at the start of the document.

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 23, 2026

@Rn123456789 is attempting to deploy a commit to the TypeCell Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 23, 2026

📝 Walkthrough

Walkthrough

The changes implement proper selection and clipboard handling for non-editable content (images) when using select-all. A Mod-a keyboard handler creates AllSelection to include all document content, and clipboard serialization detects and unwraps single blockGroup wrappers to ensure proper HTML conversion for mixed content types.

Changes

Cohort / File(s) Summary
Keyboard Selection Handler
packages/core/src/extensions/tiptap-extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts
Adds Mod-a handler that creates AllSelection transaction replacing current selection with document-wide selection, ensuring non-editable nodes like images are included.
Clipboard Serialization
packages/core/src/api/clipboard/toClipboard/copyExtension.ts
Updates selectedFragmentToHTML to base serialization on originalSlice and detect/unwrap single blockGroup wrappers, enabling proper HTML conversion for selections containing non-editable blocks.
E2E Test Coverage
tests/src/end-to-end/copypaste/copypaste.test.ts
Adds test validating copy/paste workflow for images as first block using select-all, with Firefox/WebKit skip conditions.

Sequence Diagram

sequenceDiagram
    participant User
    participant KeyboardHandler as Mod-a Handler
    participant SelectionSystem as Selection System
    participant ClipboardAPI as Clipboard API
    participant HTMLSerializer as Clipboard Serializer

    User->>KeyboardHandler: Press Ctrl+A
    KeyboardHandler->>SelectionSystem: Create AllSelection(pos 0)
    SelectionSystem->>SelectionSystem: Dispatch transaction
    User->>ClipboardAPI: Press Ctrl+C
    ClipboardAPI->>HTMLSerializer: Get selection content
    HTMLSerializer->>HTMLSerializer: Detect blockGroup wrapper
    HTMLSerializer->>HTMLSerializer: Unwrap fragment
    HTMLSerializer->>HTMLSerializer: Construct Slice from unwrapped content
    HTMLSerializer->>ClipboardAPI: Return serialized HTML
    ClipboardAPI->>User: Copy to clipboard
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Suggested reviewers

  • nperez0111

Poem

🐰 A humble image, first in line,
Could never copy — a sad design.
But now with AllSelection's might,
And unwrapped slices, pure and bright,
The image hops to clipboard's care—
Select-all magic floats through air! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main fix: enabling copy functionality for images at the first/only block position via the referenced issue #2016.
Linked Issues check ✅ Passed The PR fully addresses issue #2016 by implementing AllSelection handler for Ctrl+A to include non-editable blocks and unwrapping blockGroup in clipboard data to enable proper copy/paste of images as first blocks.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the copy issue: keyboard shortcut handler, clipboard serialization adjustment, and E2E test validation without unrelated modifications.
Description check ✅ Passed The pull request description comprehensively addresses the template requirements with clear sections covering summary, rationale, changes, impact, testing, and checklist completion.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/src/end-to-end/copypaste/copypaste.test.ts`:
- Around line 191-213: Add a new end-to-end test that mirrors the existing
"Image as first block should be able to be copied using Ctrl+A" but inserts a
text block after the image to cover the "image first, text follows" regression;
use the same helpers (focusOnEditor, executeSlashCommand to insert the image via
IMAGE_EMBED_URL, waitForSelector `img[src="${IMAGE_EMBED_URL}"]`), then insert
or type a text block after the image (e.g., create a paragraph block via
keyboard or executeSlashCommand), call copyPasteAll(page) and assert with
compareDocToSnapshot using a new snapshot name like "imageThenText.json" to lock
in mixed-content behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 49a2dc8f-0476-4b92-abf9-57c6b96354a1

📥 Commits

Reviewing files that changed from the base of the PR and between 666ee1e and 6cb73a1.

📒 Files selected for processing (3)
  • packages/core/src/api/clipboard/toClipboard/copyExtension.ts
  • packages/core/src/extensions/tiptap-extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts
  • tests/src/end-to-end/copypaste/copypaste.test.ts

Comment on lines +191 to +213
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");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add coverage for the “image first, text follows” case.

This test covers the only-image scenario, but issue #2016 also reports that when text follows the leading image, Ctrl+A/C copies only the text. Please add a mixed-content case so the fix is locked down for both reported regressions.

Suggested additional coverage
+  test("Image as first block followed by text should 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 page.keyboard.press("ArrowDown");
+    await page.keyboard.press("Enter");
+    await page.keyboard.type("paragraph after image");
+
+    await copyPasteAll(page);
+
+    await compareDocToSnapshot(page, "imageAsFirstBlockWithText.json");
+  });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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");
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");
});
test("Image as first block followed by text should 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 page.keyboard.press("ArrowDown");
await page.keyboard.press("Enter");
await page.keyboard.type("paragraph after image");
await copyPasteAll(page);
await compareDocToSnapshot(page, "imageAsFirstBlockWithText.json");
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/src/end-to-end/copypaste/copypaste.test.ts` around lines 191 - 213, Add
a new end-to-end test that mirrors the existing "Image as first block should be
able to be copied using Ctrl+A" but inserts a text block after the image to
cover the "image first, text follows" regression; use the same helpers
(focusOnEditor, executeSlashCommand to insert the image via IMAGE_EMBED_URL,
waitForSelector `img[src="${IMAGE_EMBED_URL}"]`), then insert or type a text
block after the image (e.g., create a paragraph block via keyboard or
executeSlashCommand), call copyPasteAll(page) and assert with
compareDocToSnapshot using a new snapshot name like "imageThenText.json" to lock
in mixed-content behavior.

Copy link
Copy Markdown
Contributor

@nperez0111 nperez0111 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yamiletpineda it seems that you've submitted two PRs that have overlapping changes & seem to identify some root issue with the copy behavior not actually containing all of the content that it should. Can you please close each of these & merge into 1 PR please.

I'm not sure about your use of the AllSelection here, and why it'd be any different from the existing keybind for it.

On your other PR you touch the column behavior which I'm also not certain that I understand why it was changed. Can you please explain them

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cannot copy image when it is the first/only block in editor

3 participants