+
+
+
+
+
+
+
+
Hugo - Introduce a Copy Button for Code Blocks
+
+
+
+ May 28, 2024 | Reading Time: 4 min
+
+
+
+
+ hugo
+
+
+
+
+
+
+
+One way to enhance the usability of your documentation or blog is by adding a copy button to your code blocks, allowing users to easily copy code snippets to their clipboard. In this post, we will integrate a copy button for code blocks in a Hugo-based static site.
+The implementation involves creating a JavaScript function to add the button and then integrating that into your page layouts. The example provided uses Bootstrap 5 but can be adapted as needed.
+Step 1: JavaScript: Adding the Copy Button
+-
+
- Create a new JavaScript file,
copybutton.js
with below code.
+ - This can be placed in the
static/js
orassets/js
directory of your Hugo project.
+ - The implementation introduces a header row with language and copy button. It will do this for all code blocks with the
language-*
class and with some parent ashighlight
.
+ - The business logic can be easily modified to suit any other class layout (e.g: all pre code blocks, or all code blocks etc) +
- In this current form, it would work for both table based class generation in hugo or normal one. +
- Please note that you would need to adjust the styling to your theme. +
1function addCopyButtonToCodeBlocks() {
+ 2 // Function to determine if the background color is light or dark
+ 3 function isColorDark(color) {
+ 4 const rgb = color.match(/\d+/g);
+ 5 const r = parseInt(rgb[0], 10);
+ 6 const g = parseInt(rgb[1], 10);
+ 7 const b = parseInt(rgb[2], 10);
+ 8 // Calculate luminance
+ 9 const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
+ 10 return luminance < 0.5;
+ 11 }
+ 12
+ 13 // Function to adjust color brightness significantly
+ 14 function adjustColorBrightness(color, amount) {
+ 15 const rgb = color.match(/\d+/g);
+ 16 const r = Math.min(255, Math.max(0, parseInt(rgb[0], 10) + amount));
+ 17 const g = Math.min(255, Math.max(0, parseInt(rgb[1], 10) + amount));
+ 18 const b = Math.min(255, Math.max(0, parseInt(rgb[2], 10) + amount));
+ 19 return `rgb(${r}, ${g}, ${b})`;
+ 20 }
+ 21
+ 22 // Get all code blocks with a class of "language-*"
+ 23 const codeBlocks = document.querySelectorAll(
+ 24 'pre > code[class^="language-"]'
+ 25 );
+ 26 const copyIcon = '<i class="fas fa-copy"></i> copy code';
+ 27 const copiedIcon = '<i class="fas fa-check"></i> copied!';
+ 28
+ 29 // For each code block, add a copy button inside a header
+ 30 codeBlocks.forEach((codeBlock) => {
+ 31 // Get the background color of the code block
+ 32 const computedStyle = window.getComputedStyle(codeBlock);
+ 33 const backgroundColor = computedStyle.backgroundColor;
+ 34
+ 35 // Adjust the header color to be significantly lighter or darker than the background color
+ 36 const headerColor = isColorDark(backgroundColor)
+ 37 ? adjustColorBrightness(backgroundColor, 65)
+ 38 : adjustColorBrightness(backgroundColor, -65);
+ 39 const textColor = isColorDark(backgroundColor) ? "#d1d1d1" : "#606060";
+ 40
+ 41 // Create the header div
+ 42 const header = document.createElement("div");
+ 43 header.style.backgroundColor = headerColor;
+ 44 header.style.display = "flex";
+ 45 header.style.justifyContent = "space-between";
+ 46 header.style.alignItems = "center";
+ 47 header.style.paddingRight = "0.5rem";
+ 48 header.style.paddingLeft = "0.5rem";
+ 49 header.style.borderTopLeftRadius = "5px";
+ 50 header.style.borderTopRightRadius = "5px";
+ 51 header.style.color = textColor;
+ 52 header.style.borderBottom = `1px solid ${headerColor}`;
+ 53 header.classList.add("small");
+ 54
+ 55 // Create the copy button
+ 56 const copyButton = document.createElement("button");
+ 57 copyButton.classList.add("btn", "copy-code-button");
+ 58 copyButton.style.background = "none";
+ 59 copyButton.style.border = "none";
+ 60 copyButton.style.color = textColor;
+ 61 copyButton.style.fontSize = "100%"; // Override the font size
+ 62 copyButton.style.cursor = "pointer";
+ 63 copyButton.innerHTML = copyIcon;
+ 64 copyButton.style.marginLeft = "auto";
+ 65
+ 66 // Add a click event listener to the copy button
+ 67 copyButton.addEventListener("click", () => {
+ 68 // Copy the code inside the code block to the clipboard
+ 69 const codeToCopy = codeBlock.innerText;
+ 70 navigator.clipboard.writeText(codeToCopy);
+ 71
+ 72 // Update the copy button text to indicate that the code has been copied
+ 73 copyButton.innerHTML = copiedIcon;
+ 74 setTimeout(() => {
+ 75 copyButton.innerHTML = copyIcon;
+ 76 }, 1500);
+ 77 });
+ 78
+ 79 // Get the language from the class
+ 80 const classList = Array.from(codeBlock.classList);
+ 81 const languageClass = classList.find((cls) => cls.startsWith("language-"));
+ 82 const language = languageClass
+ 83 ? languageClass.replace("language-", "")
+ 84 : "";
+ 85
+ 86 // Create the language label
+ 87 const languageLabel = document.createElement("span");
+ 88 languageLabel.textContent = language ? language.toLowerCase() : "";
+ 89 languageLabel.style.marginRight = "10px";
+ 90
+ 91 // Append the language label and copy button to the header
+ 92 header.appendChild(languageLabel);
+ 93 header.appendChild(copyButton);
+ 94
+ 95 // Find the parent element with the "highlight" class and insert the header before it
+ 96 const highlightParent = codeBlock.closest(".highlight");
+ 97 if (highlightParent) {
+ 98 highlightParent.parentNode.insertBefore(header, highlightParent);
+ 99 }
+100 });
+101}
+102
+103// Call the function to add copy buttons to code blocks
+104document.addEventListener("DOMContentLoaded", addCopyButtonToCodeBlocks);
+
Step 2: Integration in layouts
+-
+
- To include this JavaScript file in your Hugo site, you need to modify a layout page where you want to have the copybutton present. +
- If you want it to be present in all your pages, it could be the
baseof.html
file.
+ - If you place it in the
assets/js
directory, it can integrated as:
+
1{{ with resources.Get "js/copybutton.js" }}
+2 {{ $minifiedScript := . | minify | fingerprint }}
+3 <script src="{{ $minifiedScript.Permalink }}" integrity="{{ $minifiedScript.Data.Integrity }}" defer></script>
+4{{ else }}
+5 {{ errorf "copybutton.js not found in assets/js/" }}
+6{{ end }}
+
-
+
- If you place it in the
static/js
directory, it can integrated as:
+
1<script src="{{ "js/copybutton.js" | relURL }}" defer></script>
+