let addr: SocketAddr = config. address . parse () ?;
let ln = TcpListener :: bind ( & addr). await ?;
info! ( "🦊 {}" , config. base_url );
let addr: SocketAddr = config.address.parse()?;
let ln = TcpListener::bind(&addr).await?;
info!("🦊 {}", config.base_url);
When you copy text from a web page… and you paste it into Google Slides… it retains some of its formatting.
But if you were to feed it carefully-formatted HTML… HTML generated only for the purpose of being pasted into Google Slides, then maybe… it could work?That idea is hardly new: you'll find slides code highlighters around the web.
You can add a handler for that event, which lets you override what will go in the clipboard.
preventDefault() to, well, prevent anything from being copied in the system clipboard. But you can't read what's in the clipboard, because presumably security and privacy and things like that.
You can also generate a synthetic copy event. However, it will not affect the system clipboard at all.
async function writeToClipboard () {
const text = "Hello, world!" ;
const html = "<h1>Hello, world!</h1>" ;
await navigator . clipboard . write ([
new ClipboardItem ({
'text/plain' : new Blob ([ text ], { type : 'text/plain' }),
'text/html' : new Blob ([ html ], { type : 'text/html' })
})
]);
console . log ( "Content copied to clipboard as both text and HTML." );
}
Each browser will add its own little wrapper around our HTML payload, which is mildly upsetting, but not a huge deal in the grand scheme of things.
style attributes:
- color for the foreground
- background-color where needed
`s into text frames with line breaks — but it helps to know what markup gets flattened and what doesn't before building your exporter.
## The mechanics of copying
All right, so we have our plan:
1. Build an HTML string with fully-inlined styles for our code block
2. Write multiple representations to a single ClipboardItem: `text/plain` and `text/html`
3. Store it in the clipboard via navigator.clipboard.write()
But wait — there's an even trickier issue: what if you want to copy just part of a highlighted code block? Your eventual users might not want the full block; they may select a single method, or a range of lines.
When you intercept the copy event dispatched on a selection, you get access to a
Selection object from document.getSelection(). But you can't easily map the selected DOM ranges to ranges in your original syntax tree — at least not reliably.
That's where this project grew beyond a simple button.
What I ended up building wasn't a single-purpose bookmarklet, but a small library: you give it a source file and a grammar, plus a pre-built palette of token colors, and it generates both visual HTML for display and copy-ready HTML with everything inlined.
## Real-life example
Here's the copy button as implemented in the blog engine's articles — rendered only for logged-in users. Clicking it invokes the following sequence:
js
const button = document.querySelector('button.copy-code');
button.addEventListener('click', async () => {
// we disable the default browser behavior
// because we're not copying the user's existing selection
const range = document.createRange();
range.selectNodeContents(codeElement);
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
// then let the clipboard API do the rest
await copyRichText({ html: toInlineHTML(codeElement), text: codeElement.innerText });
});
There are caveats, naturally. Firefox wraps our payload differently than Safari, Chrome adds its own comment blocks around the fragment. But the content arrives intact — and with every style inline, the pasted code closely resembles the original site.
Understanding computed styles
When you press Ctrl+C, Cmd+C, or invoke execCommand("copy"), the browser copies the current selection as plain text, HTML, and RTF. The HTML payload, however, comes with some questionable decisions baked in.
If you pretty-print the copied style of a link, two issues stand out. First, the browser inlines a “reset” for dimensions and the box model whenever possible. Second, it does partial style computation — it copies the color directive inline rather than relying on external CSS rules — but it stops short of resolving all values fully.
The most visible problem is that the payload contains both the dark mode and light mode colors of the link:
- Light mode
- Dark mode
The browser selects the right one later using the light-dark CSS function, based on the active color scheme. But when you're pasting content into a slide deck, you want the color that's active right now — not a future decision re-made by the receiving application.
To get that, you can't rely on the HTML generated by the copy event. Instead, grab the relevant node's outerHTML property. For a selected paragraph, that returns markup without any inline styles. The innerHTML property shows the children only, and contextText gives plain text — none of which carry the styling information you need.
What you want is the computed style: the values active at this exact moment, as shown in dev tools' “Computed” tab. All major browsers except Firefox provide access via computedStyleMap(). Querying a paragraph's computed style for color returns something like rgb(255, 255, 255); narrowing to a link inside it returns rgb(255, 116, 116) in dark mode or rgb(232, 12, 12) in light mode.
The full copy-as-HTML solution
With a small vanilla.js implementation, you can copy an element to the clipboard as HTML while preserving the computed text color and font-weight of every child element:
One function in that implementation — normalizeColor — deserves special attention.
Why a color normalizer exists
This works reliably until you change your site's CSS. A redesign introduced colors specified in Display P3, a wide-gamut color space using a D65 white point and the sRGB tone reproduction curve. With an HDR display, these colors render visibly more intense than their sRGB counterparts; on non-HDR hardware, browsers tone-map them to the nearest sRGB equivalent.
The same redesign used a variable-width font with an unusual font-weight of 120. With variable fonts, “normal” isn't always 400 — this particular typeface maps normal to 100 and bold to 150.
When pasting the generated HTML into Google Slides, the code appeared all black. Google Slides doesn't inject the CSS you provide directly; it parses styles and only applies the ones it understands. Display P3 colors aren't supported, so they fell back to black.
Transforming a color from outside a gamut to the closest representable color inside that gamut is — spoiler — genuinely hard. The Color.js documentation sums it up:
“The process of transforming a color outside of a given gamut to a color that is as close as possible but is inside gamut is called gamut mapping”
Entire books are written on this subject, and it's a legitimate case for pulling in a third-party dependency.
Final observations
Slides now render with the correct colors, and the fix is bundled into the site's copy button. But the underlying frustrations remain. Google Slides still doesn't accept SVG files — EPS-based workarounds are not a substitute — and it ships a keyboard shortcut for rotating elements that no one asked for. Meanwhile, Keynote handles display-p3 colors and SVG natively, which is reason enough to switch.



