Repository navigation
Conversation
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.
|
Landed as 479bacd through #104 with a follow-up (c7cdf8a). The regex pass that ran before the HTML importer rewrote visible text along with markup: a url(...) in the clip's own words, a copied CSS snippet or the URL("https://...") call in a code sample pasted as "none" through Clean Formatting and Markdown. It also guarded against something NSAttributedString(html:) does not do; a local HTTP server logged no request for an unsanitized img, input type=image, body background, image-set() or meta refresh. The HTML now reaches the importer as captured, with the 2 s document timeout and the 1 MB conversion cap kept. Two more things the review turned up: editing a clip replaced its text and RTF but kept the captured HTML, which the converters prefer, so Paste as Markdown after an edit produced the pre-edit words (both edit paths clear it now), and HTML is captured only when the pasteboard carries no RTF and only up to 64 KB, since store.json is re-encoded after every capture and Safari copies already carry RTF. Thanks @alvst. |
"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
[text](url).asPlainTextflag to aPasteFormatenum carrying all four modes.img,picture,source,iframe,object,embed,link,script,style,svg,video,audio, and CSSurl()) is stripped before conversion, backed by a 2-second importer timeout. Sources over 1 MB skip rich conversion and fall back to plain text.CKSchema.inlineLimit); oversized HTML is dropped and conversions fall back to RTF/plain text as before.Screenshots
Before:

After:

Notes for review
htmlDatais 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.htmlDatainherits the store's 0600 permissions.This feature should be bundled into v2.
Part of #80.