A small quality of life improvement for programming-related websites is to add copy to clipboard buttons to code blocks. When a visitor wants to copy a code example or a shell command, it’s nice to be able to just hit a button rather than manually select the text, right click, and press copy.
I use Hugo to build my personal website. While Hugo has built-in support for syntax highlighting, it doesn’t support copy buttons. Here is how I added the feature to my website. The end result looks like this:
Adding the buttons
I inspected the source of a page with code blocks and found that Hugo generates each block with markup like this:
For many implementations of copy code buttons that I’ve seen, the button is located in the top right or bottom right corner of the code block. However, I’ve noticed that the button can cover up some of the code if the line is too long, especially on mobile. To avoid this possibility, I placed each button before the entire code block.
Some implementations only show the button when the user hovers over the code block, but for discoverability, I left the buttons always visible.
For styling the buttons, I used this CSS:
Interacting with the clipboard
However, there is a newer approach, the Clipboard API. It has several advantages: it’s asynchronous, takes arbitrary text/data (so it doesn’t have to already exist on the page), and has a better story for dealing with permissions. Chrome, Firefox, and Opera support it already. For other browsers, there is a polyfill.
I put the code in a function and added click handlers. I used innerText to get the code to be copied. After the copy operation, the button displays either an error message or a success message that lasts for two seconds.
Next, I added a check for whether or not the browser supports the Clipboard API. If not, the script loads the polyfill from CDNJS.
After the Clipboard API becomes ubiquitous, I’ll remove the polyfill code.
Smart loading with Hugo
After I got the functionality to work, I thought about how to include the script. I had three options. The first was to indiscriminately include it on every page. The script is small, but for optimization, I wanted to only include it when it’s actually needed, saving a bit of bandwidth and a network request (or two, if the polyfill is needed).
The second option was to use a custom Hugo front matter variable. With this method, I’d set a flag on every post that has a code block. The template could then check for this flag. However, this approach involves manual work and runs the risk of me forgetting to do it.
The third option was to find a way to use Hugo to figure out which pages have at least one code block. A regex seemed like the way to go. I used Hugo’s findRE function to determine if the HTML seems to contain a
I passed it a limit parameter of
1 because I only care if the page has a code block or not, not the total number of code blocks.
Keep in mind that this script should be loaded after the page content, preferably at the end of the body so that it doesn’t block rendering. Otherwise, the selector might run before the code blocks actually exist.
This solution should easily work for non-Hugo websites as well. The only part of the script that is specific to Hugo is the
pre > code selector. Modifying the selector and possibly where the button is inserted should be all that is needed.
CodeCopy is a browser extension for Chrome and Firefox that adds copy buttons to code blocks on many websites that are likely to have them, such as GitHub and Stack Overflow. It’s made by the same person behind clipboard.js.
Originally published at www.dannyguo.com.