Newsletter
TechAnV Blog
Get updates on security engineering, Rust, eBPF, and DevSecOps. No spam, unsubscribe anytime.
Check your inbox and click the confirmation link to complete your subscription.
Copy rich text to the clipboard#
I’ve been experimenting with a tool for generating the content for a weekly Substack newsletter by querying the Datasette API for my blog and assembling HTML for the last week of content.
I haven’t started sending this out yet, but I figured out how to write rich text to the clipboard as part of my initial prototype.
Substack allows you to paste in rich text (e.g. copied-and-pasted rendered HTML), so it’s useful to be able to programatically add rich text to the user’s clipboard in order to conveniently paste into Substack.
Initially I tried to get this working using the new Clipboard.write(), but I spotted this warning on the Interact with the clipboard page of MDN:
However, while
navigator.clipboard.readText()andnavigator.clipboard.writeText()work on all browsers,navigator.clipboard.read()andnavigator.clipboard.write()do not. For example, on Firefox at the time of writing,navigator.clipboard.read()andnavigator.clipboard.write()are not fully implemented, such that to:
- work with images use
browser.clipboard.setImageData()to write images to the clipboard anddocument.execCommand("paste")to paste images to a webpage.- write rich content (such as, HTML, rich text including images, etc.) to the clipboard, use
document.execCommand("copy")ordocument.execCommand("cut"). Then, eithernavigator.clipboard.read()(recommended) ordocument.execCommand("paste")to read the content from the clipboard.
This is a bit tough to read, but the TLDR version is that for rich text copying in Firefox the .write() method doesn’t work properly yet.
I actually pasted the above code into ChatGPT as a clue and got it to write me the following code, which I then tidied up and added the document.body.appendChild() and document.body.removeChild() lines (it failed without them):
1function copyRichText(html) {2 const htmlContent = html;3 // Create a temporary element to hold the HTML content4 const tempElement = document.createElement("div");5 tempElement.innerHTML = htmlContent;6 document.body.appendChild(tempElement);7 // Select the HTML content8 const range = document.createRange();9 range.selectNode(tempElement);10 // Copy the selected HTML content to the clipboard11 const selection = window.getSelection();12 selection.removeAllRanges();13 selection.addRange(range);14 document.execCommand("copy");15 selection.removeAllRanges();16 document.body.removeChild(tempElement);17}In an Observable notebook#
I used this to add a “copy” button to my Observable notebook like this:
1Object.assign(html`<button>Copy rich text newsletter to clipboard`, {2 onclick: () => {3 const htmlContent = newsletterHTML;4 // Create a temporary element to hold the HTML content5 const tempElement = document.createElement("div");6 tempElement.innerHTML = htmlContent;7 document.body.appendChild(tempElement);8 // Select the HTML content9 const range = document.createRange();10 range.selectNode(tempElement);11 // Copy the selected HTML content to the clipboard12 const selection = window.getSelection();13 selection.removeAllRanges();14 selection.addRange(range);15 document.execCommand("copy");16 selection.removeAllRanges();17 document.body.removeChild(tempElement);18 }19})This depends on some other cell defining newsletterHTML as a string of HTML.
Here’s the notebook that uses that.