Skip to content

Add Clean Formatting and Markdown paste options - #93

Open
alvst wants to merge 3 commits into
momenbasel:mainfrom
alvst:alvie/pr-17-paste-as-formats
Open

Add Clean Formatting and Markdown paste options#93
alvst wants to merge 3 commits into
momenbasel:mainfrom
alvst:alvie/pr-17-paste-as-formats

Conversation

@alvst

@alvst alvst commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

"Paste as Plain Text" is all-or-nothing: it's the only escape from a clip that arrives with a website's fonts and colors attached. This adds two middle options — keep the emphasis, drop the styling, or convert to Markdown.

What changed

  • Two new context-menu paste modes alongside Paste as Plain Text, enabled whenever a clip has rich content. Clean Formatting keeps bold, italic, underline, strikethrough, and links while normalizing fonts, sizes, and colors to the system default. Markdown converts rich content to Markdown text, splitting style markers across line breaks so the output stays valid, and rendering links as [text](url).
  • The monitor also captures a copy's HTML flavor — browser copies often carry HTML with no RTF — without changing how clips are classified or deduplicated. Conversions prefer HTML, then RTF, then plain text.
  • Paste plumbing moves from an asPlainText flag to a PasteFormat enum carrying all four modes.
  • The HTML importer is WebKit-backed and will fetch remote subresources during conversion, so every element that can reference an external resource (img, picture, source, iframe, object, embed, link, script, style, svg, video, audio, and CSS url()) is stripped before conversion, backed by a 2-second importer timeout. Sources over 1 MB skip rich conversion and fall back to plain text.
  • Capture-side, stored pasteboard HTML is capped at the sync layer's own inline payload bound (CKSchema.inlineLimit); oversized HTML is dropped and conversions fall back to RTF/plain text as before.

Screenshots

Before:
00-baseline screenshots bar.png

After:
12-paste-as-formats screenshots after.png

Notes for review

  • htmlData is not carried by iCloud sync, so the same clip can produce different Markdown on a synced Mac — the one that captured it converts from HTML, the other falls back to RTF or plain text. It degrades gracefully rather than failing, and extending the sync schema for it felt like the wrong thing to bundle into this PR. Stating it rather than letting it be discovered.
  • Markdown scope is deliberately narrow: text plus bold, italic, underline, and links. Headings, lists, and strikethrough are not emitted — Clean Formatting keeps strikethrough, Markdown doesn't. The converters only round-trip what they can represent faithfully; guessing at heading levels from font sizes produces confidently wrong output.
  • The strip-before-convert step means "Paste as Markdown" never issues a network request at paste time — a tracking pixel in a captured mail or web clip would otherwise have been fetched synchronously on the main thread.
  • Concealed clips bail before HTML is ever read, and htmlData inherits the store's 0600 permissions.
  • No dependencies; merges in any order.

This feature should be bundled into v2.

Part of #80.

alvst added 3 commits August 15, 2026 21:10
Two new context-menu paste modes alongside Paste as Plain Text, enabled
whenever a clip has rich content. Clean Formatting keeps bold, italic,
underline, strikethrough, and links while normalizing fonts, sizes, and
colors to the system default. Markdown converts the rich content to
Markdown text, splitting style markers across line breaks so the output
stays valid, and rendering links as [text](url).

The clipboard monitor now also captures a copy's HTML flavor - browser
copies often carry HTML with no RTF - without changing how clips are
classified. Conversions prefer HTML, then RTF, then fall back to plain
text. Paste plumbing moves from an asPlainText flag to a PasteFormat
enum carrying all four modes.
The HTML importer behind the Clean Formatting and Markdown conversions
is WebKit-backed: handed raw captured HTML, it fetches remote
subresources - tracking pixels included - synchronously on the main
thread at paste time, breaking the no-network-calls promise. Strip
every element that can reference an external resource (img, picture,
source, iframe, object, embed, link, script, style, svg, video, audio,
and CSS url() values) before conversion; the converters only keep text
plus bold/italic/underline/links, so nothing representable is lost. A
2-second importer timeout backs the stripping up, and sources over 1MB
skip rich conversion entirely, falling back to plain text so a
pathological clip can't hitch the paste.

Capture-side, cap stored pasteboard HTML at the sync layer's inline
payload bound (CKSchema.inlineLimit) so every routine text copy can't
persist an unbounded HTML blob; oversized HTML is dropped and the
conversions fall back to RTF/plain text as before.
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.

1 participant