Skip to content

fix: provide plain text and markdown clipboard formats - #193

Open
Oguzhan-Ozpinar wants to merge 3 commits into
erictli:mainfrom
Oguzhan-Ozpinar:fix/137-copy-code-as-plain-text
Open

fix: provide plain text and markdown clipboard formats#193
Oguzhan-Ozpinar wants to merge 3 commits into
erictli:mainfrom
Oguzhan-Ozpinar:fix/137-copy-code-as-plain-text

Conversation

@Oguzhan-Ozpinar

@Oguzhan-Ozpinar Oguzhan-Ozpinar commented Jul 28, 2026

Copy link
Copy Markdown

Summary

  • expose clean visible text as text/plain for every WYSIWYG selection
  • preserve rich formatting as text/html and provide serialized Markdown as text/markdown
  • prefer explicit text/markdown when pasting back into Scratch so formatting survives round trips
  • leave Markdown source mode copy behavior unchanged
  • normalize non-breaking-space entities in copied Markdown, including table selections

Fixes #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 build
  • verified code blocks, nested code blocks, inline code, headings, lists, emphasis, and mixed selections
  • verified rich-text paste in Apple Notes, Obsidian, Mail, and TextEdit
  • verified plain-text paste contains no Markdown wrappers

Summary by CodeRabbit

  • New Features
    • Improved editor copy and paste behavior across supported clipboard formats.
    • Copy now preserves HTML, plain text, and Markdown when available.
    • Pasting prioritizes Markdown clipboard content and provides a more reliable plain-text fallback.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The editor now serializes selected ProseMirror slices into HTML, plain text, and optional Markdown during copy, then prefers explicit Markdown clipboard data during paste.

Changes

Editor clipboard handling

Layer / File(s) Summary
Copy serialization flow
src/components/editor/Editor.tsx
Adds slice-to-Markdown serialization and custom copy handling that writes HTML, plain text, and optional Markdown clipboard data.
Paste format selection
src/components/editor/Editor.tsx
Paste handling prefers text/markdown, skips pattern gating for explicit Markdown, and parses it through the markdown manager when available.

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
Loading

Possibly related PRs

  • erictli/scratch#33: Modifies plain-text clipboard copying in the same editor file.
  • erictli/scratch#115: Introduces overlapping clipboard Markdown serialization in the same editor file.

Suggested reviewers: erictli

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The paste-handling changes go beyond the linked copy-only issue and add new markdown paste behavior. Move the paste behavior into a separate PR or add it to the linked issue's acceptance criteria.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The copy handler now emits clean text/plain for WYSIWYG selections, addressing issue #137's markdown-in-clipboard problem.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: improving clipboard output with plain text and Markdown formats.
✨ 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.

@Oguzhan-Ozpinar

Copy link
Copy Markdown
Author

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:

  • text/plain: clean visible text, without Markdown wrappers
  • text/html: rich formatting for Mail, Notes, and other rich-text destinations
  • text/markdown: Markdown for Markdown-aware destinations and format-preserving round trips

The Markdown source editor remains unchanged and continues to copy literal Markdown. Paste also prefers text/markdown when the destination is Scratch and that representation is available.

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 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

📥 Commits

Reviewing files that changed from the base of the PR and between badf314 and bc6c971.

📒 Files selected for processing (1)
  • src/components/editor/Editor.tsx

Comment thread src/components/editor/Editor.tsx Outdated
@Oguzhan-Ozpinar Oguzhan-Ozpinar changed the title fix: copy code selections as plain text fix: provide plain text and markdown clipboard formats Jul 28, 2026
tilhammer added a commit to tilhammer/scratch that referenced this pull request Aug 2, 2026
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>
tilhammer added a commit to tilhammer/scratch that referenced this pull request Aug 2, 2026
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>
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.

[Issue] Copying text also copies markdown alongside the text

1 participant