How to add a copy code button in Hugo (keyboard friendly)

Original link: https://blog.loikein.one/posts/2022-08-11-hugo-copy-code-button/

I’ve wanted to do this for a long time, but because I haven’t studied JavaScript seriously, I give up halfway every time because I can’t piece together the desired effect. There are actually many articles on the Internet (see: Issue #5619 · python-poetry/poetry , but I couldn’t find a version that considered accessibility anyway. There is a copy code button that supports keyboard browsing (Enter / Spacebar) on MDN , but the code is really It’s too abstract for me to understand…

However, as the saying goes, fishing is the primary productive force. Today, I was nervously fishing for fish, and I suddenly remembered this thing. After two hours of sewing, it actually came out for me. Fishing is really a scary… process.

First results:

 hello world

The file paths for this article are based on the entire Hugo website folder.

Copy code function

Source: How to Add Copy to Clipboard Buttons to Code Blocks in Hugo – Simplernerd

code

First, create a new JavaScript file as: ./themes/diary/static/js/clipboard.js

 // buttons 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>' ; 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" 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>' ; // add button function 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.title = "Copy" ; 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 after the pre tag (.highlight > pre > button > code) const pre = codeBlock.parentNode; pre.parentNode.insertBefore(button, pre.nextSibling); }); }; // trigger function if (navigator && navigator.clipboard) { addCopyButtons(navigator.clipboard); } else { const script = document .createElement( "script" ); script.src = "https://cdnjs.cloudflare.com/ajax/libs/clipboard-polyfill/3.0.3/promise/clipboard-polyfill.promise.min.js" ; script.integrity = "sha512-O9Q+AhI1w7LT1/tHysPWDwwrgB1fKJ/nXPNLC30i8LF6RdSz4dGZyWB9WySag3DZMdGuK5yHJEdKXMKI2m5uSQ==" ; script.crossOrigin = "anonymous" ; script.referrerpolicy = "no-referrer" ; script.onload = () => addCopyButtons(clipboard); document .body.appendChild(script); }

There are several changes in the above code:

  1. Remove fill (fill color) of svgCheck and specify it by CSS instead
  2. Add button.title = "Copy"; (description text when the mouse is over)
  3. Modify the position of the added button to the back of the pre (detailed explanation below)
  4. Update clipboard-polyfill version to latest stable version (not tested)

Then, add the following code to ./themes/diary/layouts/partials/footer.html :

 < footer > ... <!-- copy code --> < script src = " / script > </ footer >

explain

First, use Chroma that comes with Hugo for code highlighting, and the rendering result is HTML in the following format:

 < div class = "highlight" > < pre class = "..." > < code class = "..." data-lang = "..." >...</ code > </ pre > </ div >

So the condition in the layout is to append JavaScript only when the rendered page contains <code .

Then comes the JavaScript code. The first part is the SVG of the two buttons, then the addCopyButtons equation (which includes both adding a button and copying the code), and finally calling the equation.

Regarding the part of adding buttons, the original author put it in front of <pre> , it should be just a matter of personal preference. However, since I was testing keyboard browsing, the preference for :focus on the web page was <pre> instead of <div class="highlight"> , so if you put the button in front of <pre> , you can’t use sibling selector for the button Added CSS on :focus . After some searching, I found that the button can be added after the <pre> by modifying the following:

 - pre.parentNode.insertBefore(button, pre); + pre.parentNode.insertBefore(button, pre.nextSibling);

After adding the above code, the rendering result is HTML in the following format:

 < div class = "highlight" > < pre class = "..." > < code class = "..." data-lang = "..." >...</ code > </ pre > < button class = "copy-code-button" type = "button" > < svg ...>...</ svg > </ button > </ div >

Finally call the equation. Call it directly if the browser supports the clipboard API , otherwise load the clipboard-polyfill . Remarks: The latter is marked as obsolete, but it will not cause additional requests on supported browsers, and it should not be a big problem to keep it; although I don’t want to support IE, but anyway, people have already written it, no need White need not…

Support keyboard browsing

Add step 4 to the addCopyButtons equation, leaving the rest unchanged:

 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 = "copy-code-button" ; button.type = "button" ; button.title = "Copy" ; 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 after the pre tag (.highlight > pre > button > code) const pre = codeBlock.parentNode; pre.parentNode.insertBefore(button, pre.nextSibling); // 4. Listen to keyboard press const highlight = pre.parentNode; highlight.addEventListener( 'keydown' , function (event) { if ( event.key === " " || event.key === "Spacebar" || event.code === "Space" || event.key === "Enter" || event.code === "Enter" ) { clipboard.writeText(codeBlock.innerText).then( () => { button.blur(); button.innerHTML = svgCheck; setTimeout(() => (button.innerHTML = svgCopy), 2000 ); }, (error) => (button.innerHTML = "Error" ) ); } }); }); };

This part of the code refers to several StackOverflow answers. First of all, the code framework comes from this answer , and then because it supports the enter key and the space bar, it refers to this answer . Finally, I learned from this answer that keyCode and which have been eliminated and replaced by key and code .

Adapt CSS

Also refer to How to Add Copy to Clipboard Buttons to Code Blocks in Hugo – Simplernerd , but this part has changed a lot.

The new CSS file is: ./assets/css/code-fense.css (follow the way of adding custom CSS written before)

 . highlight { position : relative ; } . copy-code-button { color : var ( -- dark - gray ); background-color : rgba ( 255 , 255 , 255 , 50 % ); border : none ; border-radius : 6 px ; padding : 0 5 px 5 px 5 px ; font-size : 1 rem ; position : absolute ; z-index : 1 ; right : 0 ; top : 0 ; margin : 10 px ; transition : .1 s ; opacity : 0.5 ; } . copy-code-button > svg { fill: var ( -- white ); } . copy-code-button :hover , . copy-code-button :focus , pre :active ~ . copy-code-button , pre :focus ~ . copy-code-button , div . highlight :active > . copy-code-button , div . highlight :focus > . copy-code-button { cursor : pointer ; opacity : 1 ; }

Since my blog has a dark mode, plus hover/focus , I had to write four different styles. I was tired of writing, and after thinking about it for a long time, I came up with the taboo and double transparency. However, this is also based on the fact that I haven’t figured out how to automatically switch the code highlighting style when switching modes. As a result, I am currently lazy and use Dracula, which has a very convenient black background. If the automatic switch is made, the scoring situation will be rewritten again.

First, .highlight{ position: relative; } and .copy-code-button{ position: absolute; } are required, otherwise the button will drift out of the text bar. position is something like black magic, and I don’t particularly want to delve into it. The specific location of the button is actually a little bit of black magic, but it is still relatively easy to understand, that is, it is fixed in the upper right corner of the code box.

Then the button pattern color, because the press is translucent, all the colors only need to write one. The original author seems to write the mouse over the code block to display it. I think it is not obvious, so I changed it to always display.

The last is to cancel the transparency when interacting with the button / <pre> / <div class="highlight"> , the pointer becomes a finger. In theory active and focus are two different situations, but I don’t have the energy to write a third set of styles for all buttons/links, and it’s better to write them together than not to write active , so I’ve always written like this so far . The <pre> part is the reason why the position of the add button was modified in the previous article. If you are interested in further research, you can move to read:

Off topic: what is accessibility? Why Web Development Needs to Consider Accessibility?

Accessibility is not just for the convenience of others, but also for the convenience of oneself. I have spent a lot of time learning this thing , copy the main reference link below.

I took this opportunity to complete the jump to the main content button that I had not finished before, otherwise I would feel shameless to educate others. life.

This article is reproduced from: https://blog.loikein.one/posts/2022-08-11-hugo-copy-code-button/
This site is for inclusion only, and the copyright belongs to the original author.

Leave a Comment