Article about copy-code-blocks

Plus some changes to CSS so behaves, and mostly looks the same across Safari, Chrome and Firefox.
Plus amendment to not make the footnotes absolutely tiny.
This commit is contained in:
Robert McGovern 2023-02-19 15:32:01 +00:00
parent 4408dc967e
commit e906cf0c1f
3 changed files with 198 additions and 7 deletions

View File

@ -0,0 +1,157 @@
---
title: Copy Code Blocks
tags: [webdev, site]
category: [site, webdev]
date: 2023-02-19
---
I've been reading [Bryce Wray's](https://www.brycewray.com) site for a little while. Its been interesting watching Bryce switch back and forth between [Hugo](https://github.com/brycewray/hugo_site) and [Eleventy / 11ty](https://github.com/brycewray/eleventy_site). Keeping both in sync feature wise.
Just recently Bryce did a post about adding [Code for Copying Code](https://www.brycewray.com/posts/2023/02/code-copying-code-eleventy-edition/) in Eleventy. Essentially for any code block, it adds an icon (or you could switch it out for text) inside a code block that you can click and it will copy the code to your clipboard.
On my site, the icon looks like this[^1]:
![Screenshot of the top right corned of a code block. There is a small grey button with two overlapping squares shown](/assets/images/posts/copy-code-block.png)
I am using Bryce's JavaScript virtually verbatim. The only difference is that I hook into the `load` event. Source from [copy-code-button.js](https://github.com/tarasis/tarasis.net/blob/main/src/assets/js/copy-code-button.js), with a minor change to the colour of the `svgCheck`.
```js
/*
h/t to...
- https://www.brycewray.com/posts/2023/02/code-copying-code-eleventy-edition/ & Bryce's gitrepo
- https://github.com/brycewray/eleventy_site/blob/main/src/assets/js/copy-code-button.js
- https://www.dannyguo.com/blog/how-to-add-copy-to-clipboard-buttons-to-code-blocks-in-hugo/
- https://aaronluna.dev/blog/add-copy-button-to-code-blocks-hugo-chroma/
- https://simplernerd.com/hugo-add-copy-to-clipboard-button/
- https://digitaldrummerj.me/hugo-add-copy-code-snippet-button/
*/
const svgCopy =
'<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true"><path fill-rule="evenodd" d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 010 1.5h-1.5a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-1.5a.75.75 0 011.5 0v1.5A1.75 1.75 0 019.25 16h-7.5A1.75 1.75 0 010 14.25v-7.5z"></path><path fill-rule="evenodd" d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0114.25 11h-7.5A1.75 1.75 0 015 9.25v-7.5zm1.75-.25a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-7.5a.25.25 0 00-.25-.25h-7.5z"></path></svg><span class="sr-only"> Click to copy code</span>';
const svgCheck =
'<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true"><path fill-rule="evenodd" fill="rgb(25, 0, 255)" d="M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z"></path></svg><span class="sr-only"> Copied!</span>';
const addCopyButtons = (clipboard) => {
// 1. Look for pre > code elements in the DOM
document.querySelectorAll("pre > code").forEach((codeBlock) => {
// 2. Create a button that will trigger a copy operation
const button = document.createElement("button");
button.className = "clipboard-button";
button.type = "button";
button.innerHTML = svgCopy;
button.addEventListener("click", () => {
clipboard.writeText(codeBlock.innerText).then(
() => {
button.blur();
button.innerHTML = svgCheck;
setTimeout(() => (button.innerHTML = svgCopy), 2000);
},
(error) => (button.innerHTML = "Error")
);
});
// 3. Append the button directly before the pre tag
// (with content-searching fix in place for Prism)
const pre = codeBlock.parentNode;
pre.parentNode.insertBefore(button, pre);
});
};
function addCopyButtonsAfterLoad() {
if (navigator && navigator.clipboard) {
addCopyButtons(navigator.clipboard);
} else {
const script = document.createElement("script");
script.src =
"https://cdnjs.cloudflare.com/ajax/libs/clipboard-polyfill/2.7.0/clipboard-polyfill.promise.js";
script.integrity =
"sha256-waClS2re9NUbXRsryKoof+F9qc1gjjIhc2eT7ZbIv94=";
script.crossOrigin = "anonymous";
script.onload = () => addCopyButtons(clipboard);
document.body.appendChild(script);
}
}
window.addEventListener("load", addCopyButtonsAfterLoad);
```
As this site is a franken-baby, an 11ty site using the Minimal Mistakes theme, I needed to use `liquid` rather than `njk` code in my page layout file. So in [single.html](https://github.com/tarasis/tarasis.net/blob/main/src/_includes/layouts/single.html) I removed {% raw %}`{{content}}`{% endraw %} and replaced it with:
{% raw %}
```liquid
{% assign Content = content %}
{% assign withoutDivStart = '<pre ' %}
{% assign withDivStart = '<div class="highlight codeblock"><pre ' %}
{% assign withoutDivEnd = '</code></pre>' %}
{% assign withDivEnd = '</code></pre></div>' %}
{% if content contains withoutDivStart %}
{% assign Content = content | replace: withoutDivStart, withDivStart %}
{% assign Content = Content | replace: withoutDivEnd, withDivEnd %}
{% endif %}
{{ Content }}
```
{% endraw %}
This wraps a `div` around the `pre + code` block that is output by the syntax highlighting plugin (I'm currently using a [Chroma plugin](https://github.com/tarasis/eleventy-plugin-syntaxhighlighting-chroma) I wrote, more in a later post). This is so we can set `position: relative` on the `div`, and `position:absolute` on the button.
The last part is the CSS, for me I'm currently putting the changes in [main.scss.liquid](https://github.com/tarasis/tarasis.net/blob/main/src/assets/css/main.scss.liquid). There are only minor color differences from Bryce's code. It also works with code blocks that have line numbers, but only when embedded in a table otherwise it copies the line numbers too. (For instance below is done using `css/lineNumbers/table` [^2])
```css/lineNumbers/table
div.highlight {
position: relative;
}
// === for copy-code-to-clipboard
// h/t https://simplernerd.com/hugo-add-copy-to-clipboard-button/
.clipboard-button {
position: absolute;
top: 2px;
right: 2px;
padding: 6px 8px 4px 8px;
margin: 5px;
// color: gray-500;
// border-color: gray-500;
// background-color: gray-100;
border: 1px solid;
border-radius: 6px;
font-size: 0.8em;
z-index: 1;
opacity: 0;
transition: 0.1s;
}
.clipboard-button > svg {
// fill: gray-500;
}
.clipboard-button:hover {
cursor: pointer;
border-color: #01b139;
background-color: #87d09e;
opacity: 1;
}
.clipboard-button:hover > svg {
fill: #1900ff;
}
.clipboard-button:focus {
outline: 0;
}
// .highlight {
// position: relative;
// }
.highlight:hover > .clipboard-button {
opacity: 1;
transition: 0.2s;
}
.highlight {
line-height: 1.5;
}
```
And thats it, the site now supports copying the code from the blocks at the press of a button.
[^1]: I'm well aware that the following CSS should be out in its own file. Aside, the reason this file has a `liquid` file extension is that during the site build the theme is baked into it from a variable in [site.json](https://github.com/tarasis/tarasis.net/blob/main/src/_data/site.json). I then use a separate step to sass build the compiled file.
[^2]: I had to do a hack to great rid of additional space that was being added despite `margin`, `padding` and anything else being set to `0`. I have tried various combinations. In theory nothing is adding `padding` or `margin` to the `table`, `tbody`, `tr`, or `td`; indeed they are explicitly set to 0. And yet there was still a chunk of spacing being added. I discovered that setting portions to `display:` `flex` & `inline-flex` solved the issue for Safari / Chrome, and `flex` and `ruby-base` for Firefox. All are hacks, but they provide the results that I want. (I was up to 4am trying to find that behaviours I wanted; not helped by Firefox not yet supporting `:has()` 🙈)

View File

@ -147,9 +147,43 @@ div.highlight {
position: relative;
}
.codeblock {
outline: 4px #7e1c1c solid;
border-radius: 5px;
div pre {
border: 4px #7e1c1c solid;
border-radius: 20px;
}
code {
outline: 1px #7e1c1c solid;
}
code > span {
padding-left: 10px;
}
// messes up table code blocks
//code > span:first-child {
// padding-top: 5px;
//}
//code > span:last-child {
// padding-bottom: 5px;
//}
// hack to remove outline that above sets
td > div > pre {
border: unset;
}
pre > code > div {
display: flex;
}
td {
display: inline-flex;
// below is hack so Firefox looks the same as Safari / Chrome.
// Both will ignore the assignment because they don't know about "ruby-base"
// https://caniuse.com/mdn-css_properties_display_ruby_values
display: ruby-base;
}
// === for copy-code-to-clipboard
@ -187,9 +221,7 @@ div.highlight {
.clipboard-button:focus {
outline: 0;
}
// .highlight {
// position: relative;
// }
.highlight:hover > .clipboard-button {
opacity: 1;
transition: 0.2s;
@ -199,7 +231,6 @@ div.highlight {
line-height: 1.5;
}
//@import "progress.css"; // for progress bar
//@import "minimal-mistakes/skins/{{ site.minimal_mistakes_skin | default: 'default' }}"; // skin
@import "minimal-mistakes/skins/_{{ site.minimal_mistakes_skin | default: 'default' }}.scss"; // skin
@ -207,3 +238,6 @@ div.highlight {
//@import "assets/css/override-notices.scss"
@import "override-notices.scss";
.footnotes ol, .footnotes li {
font-size: 1em;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB