to copy or not copy

written the

Some javascript would have been nice

I want my blog to be as javascript-less as it can be (so none). Why ?

  1. I hate writing js
  2. Better support across the board, especially all the weirdos with JS deactivated (me)
  3. I hate writing js
  4. I must be masochistic, and doing everything the hard way is always so funier

All is fine and good, since all posts here are statically generated, no javascript needed.
That was before I started thinking about code snippets. Now, I wish I wasn't a stubborn idiot and could write some javascript.

Code snippets and today's expectations

This blog's code snippets are generated using pygments and custom lua code, to fix indentation and add line numbering spans. So far so good, no javascript needed.

Although, if you read this post's title, you know we are talking copy here. Modern websites with code snippets all have something in common : a copy button !
Typically, those are created using bits of javascript and its clipboard API, but we want none, what should/can we do ?!

Not a lot in term of copy. Using today's CSS API, one cannot (as I know of) modify the system nor secondary clipboard. Our best workaround is to make whole-text selection easier, so copying is not as painful as it can be.

I thought you were a perfect match

Base state

I started to look at user-select and pointer-events, thinking I could pull some type of wizardry using hover states.
Sadly, this proved to not be viable (or I just could not pull it off).

Starting with a simple pre element :

This is a simple
multiline codeblock.

And adding a span element over our code snippet :

[select]
This is a simple
multiline codeblock.

Adding magic (or trying to)

Now, the idea was :

  1. Set cursor: pointer on the span to fake a select button.

  2. When the span element is hovered, set its pointer-events to none, so all mouse events are transfered to the pre element

  3. Using the following selector, span:hover ~ pre, we are able to set user-select to all

  4. For the user, it feels like clicking on the button ends up selecting the whole code snippet whereas in reality they just clicked on the pre element with user-select: all

  5. Hide the span to screen readers as those do not need extra information

Users now only need to press Ctrl+c to have the selected code in their clipboard. Let's try this plan out !

[select]
This is a simple
multiline codeblock.

So you can easily see when everything aligns magically, the pre background turn aqua when you can finally click to select.
As you can see, it is not easy nor fast to trigger our desired state.

Further testing through proof-reading shows that the selection isn't as flawless as it could be.
previous revisions: note on tactil screen
version 1
And I'm pretty sure this won't work, or be even worst, on mobile.
version 2
So... I'm writing this paragraph after some tests : this version works soooo good on tactil screens : on my linux-surface and my android, it's perfect !
This makes the desktop failure that sadder, ahaha.

What I landed on, for now at least

local function gcdn(numbers)
	local a = numbers[1]
	for i = 2, #numbers do
		local b = numbers[i]
		while b ~= 0 do
			a, b = b, a % b
		end
	end
	return a
end
After some proof-reading, the span element was not clear enough. It think I was mimic-ing the JS copy button too hard while doing something completely different. I ended up using the label in place, instead of position: absolute. IMO, the codeblock looks nicer and everything is clearer since I can write more explicative text.
previous revisions: landed on button explanations
version 1

Here, one can click on the [select], to make the pre element selectable in one click, with user-select: all.
Once the all-select feature is no longer needed, another click on [stop], and things are back to normal.

minimal working example
HTML snippet
<div>
<pre><code>:root {
	display: none;
}</code></pre>
	<label><input type="checkbox" />single click text selection</label>
</div>
CSS snippet
div { position: relative; }
pre:has(~ label > input:checked) {
	user-select: all;
}