fix: provide plain text and markdown clipboard formats - #193
fix: provide plain text and markdown clipboard formats#193Oguzhan-Ozpinar wants to merge 3 commits into
Conversation
📝 WalkthroughWalkthroughThe editor now serializes selected ProseMirror slices into HTML, plain text, and optional Markdown during copy, then prefers explicit Markdown clipboard data during paste. ChangesEditor clipboard handling
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant Editor
participant ProseMirrorView
participant MarkdownManager
participant Clipboard
Editor->>ProseMirrorView: serializeForClipboard(selection)
ProseMirrorView-->>Editor: slice, plain text, HTML DOM
Editor->>MarkdownManager: serialize slice content
MarkdownManager-->>Editor: Markdown text
Editor->>Clipboard: set HTML, plain text, and optional Markdown
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
|
Follow-up after testing: My first commit intentionally special-cased selections inside inline code and code blocks, since the example in #137 is code-oriented. After trying that behavior, however, it felt inconsistent: Markdown syntax can also leak from headings, lists, emphasis, and mixed selections, while users copying from the WYSIWYG view generally expect the visible content rather than the editor source. I have therefore revised the implementation to publish multiple clipboard representations for every WYSIWYG selection:
The Markdown source editor remains unchanged and continues to copy literal Markdown. Paste also prefers This seems like the least surprising compromise to me: plain-text destinations such as terminals get clean text, while rich-text and Markdown-aware destinations can preserve structure. I tested it with code blocks, mixed headings/lists/emphasis, Apple Notes, Obsidian, Mail, TextEdit, and plain-text paste. I think this is the most natural behavior, but I am very open to adjusting it based on maintainer preference and feedback from other users. |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/components/editor/Editor.tsx`:
- Around line 121-124: Update the slice serialization in getMarkdown to
normalize TipTap non-breaking-space entities by removing or replacing both
and &`#160`; before returning Markdown. Preserve the existing serialized
content and ensure copied table-cell selections produce normalized text/markdown
consistent with the full-document path.
🪄 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: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 30ef8de5-6b63-4634-8d4f-cf4ac47c2333
📒 Files selected for processing (1)
src/components/editor/Editor.tsx
PR erictli#193's clipboard rework (adopted into this branch) always put text/markdown on the clipboard when serialization succeeded, and Scratch's own paste handler prefers text/markdown when present. But ProseMirror's Selection.content() always preserves the enclosing block's node type (heading, codeBlock, frontmatter, etc.) regardless of whether the selection is a partial substring or the block's entire content - Slice.openStart/openEnd can't tell these apart either (both produce the same depth for an ordinary in-block text selection, verified against the actual prosemirror-model/-state/-tables source). So selecting just part of a heading's text (e.g. "Heading" out of "Heading Test") and pasting back into Scratch recreated a real heading block - the copied text/markdown was "# Heading", and handlePaste parses that as guaranteed markdown. External apps were unaffected (they only see text/plain, which was already clean); this was a Scratch-to-Scratch paste regression. This also incidentally repairs a second regression: an earlier commit (badf314) had a dedicated isCodeOnlySlice check to stop partial code selections from being fence-wrapped, but PR erictli#193's rewrite (bc6c971) dropped it without replacement - so codeBlock and Frontmatter (same "code: true" content model) had the identical bug as headings. Add isWholeBlockSelection(selection): true for NodeSelection (atomic nodes, e.g. block-math) and CellSelection (native table cell-drag selection, always structural) via instanceof, otherwise checks selection.$from.parentOffset === 0 && $to.parentOffset === $to.parent.content.size - i.e. does the selection span the full content of its bounding block(s), regardless of how many complete blocks that spans. Only serialize to markdown (and thus only set text/markdown) when true. Verified live against the running Editor (mocked Tauri invoke, real DOM selection + copy/paste ClipboardEvents, no test suite exists in this repo): partial heading/code/table-cell/frontmatter selections no longer carry markdown and paste as plain inline text; whole-block selections (heading, code block, table cell, frontmatter, full document) still preserve structure exactly as before. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Per feedback: even a whole-line selection (e.g. an entire heading) should copy as plain text by default - markdown structure should only survive when explicitly asked for, not automatically based on selection shape. Simplifies the previous isWholeBlockSelection gate into a hard rule: the copy handler never writes text/markdown at all anymore, just text/plain + text/html (text/html - and thus rich-paste into other apps - is unaffected, matches PR erictli#193 as before). handleCopyMarkdown (previously whole-document only, wired to the Copy & Export menu's "Copy Markdown" item) is now selection-aware: if there's a selection, it serializes just that slice to markdown via the existing serializeSliceToMarkdown helper; otherwise it falls back to the whole note, same as before. Bound it to a new Ctrl+Alt+C shortcut (e.code checked, not e.key, since Alt/Option remaps e.key to a special character on macOS) so it's reachable without going through the menu. Ctrl+Shift+C still opens the Copy & Export menu unchanged. Removed isWholeBlockSelection and the CellSelection import it needed - no longer used now that Ctrl+C has no conditional at all (including for native table cell-selection, which previously auto-preserved markdown and now requires the explicit Ctrl+Alt+C action like everything else). Verified live: Ctrl+C on a partial and a whole heading selection both produce plain text with no text/markdown. Ctrl+Alt+C on the same selections calls copy_to_clipboard with "# Heading" / "# Heading Test" respectively, and with nothing selected (collapsed cursor) falls back to the whole note's markdown. Ctrl+Shift+C still opens the Copy & Export menu. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Summary
text/plainfor every WYSIWYG selectiontext/htmland provide serialized Markdown astext/markdowntext/markdownwhen pasting back into Scratch so formatting survives round tripsFixes #137
Rationale
WYSIWYG copy now adapts to the destination: terminals and plain-text apps receive clean visible text, while rich-text and Markdown-aware destinations can preserve document structure. The paste preference is included to keep Scratch-to-Scratch copying lossless when the explicit Markdown representation is available.
Testing
npm run buildSummary by CodeRabbit