Hugo : Add a code block Copy button

Overview

Most of the script samples from these notes, I am copy pasting them. Sometimes I need to remove incorrect characters, slowing me down. I want to simplify this by adding a copy button on my code blocks.

Copy Code JavaScript

Add a file named copycode.js in folder assets. ( I wont use subfolder to split css and js code, but fill free to do so)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
function createCopyButton(highlightDiv) {
    const button = document.createElement("button");
    button.className = "btn-copy";
    button.type = "button";
    button.innerText = "Copy";
    button.addEventListener("click", () => copyCodeToClipboard(button, highlightDiv));
    highlightDiv.insertBefore(button, highlightDiv.firstChild);
  
    const wrapper = document.createElement("div");
    wrapper.className = "highlight-wrapper";
    highlightDiv.parentNode.insertBefore(wrapper, highlightDiv);
    wrapper.appendChild(highlightDiv);
  }
  
  document.querySelectorAll(".highlight").forEach((highlightDiv) => createCopyButton(highlightDiv));
  
  async function copyCodeToClipboard(button, highlightDiv) {
    const codeToCopy = highlightDiv.querySelector(":last-child > .chroma > code").innerText.replace(/\n+\s*\n+/g, "\n");
    try {
      var result = await navigator.permissions.query({ name: "clipboard-write" });
      if (result.state == "granted" || result.state == "prompt") {
        await navigator.clipboard.writeText(codeToCopy);
      } else {
        copyCodeBlockExecCommand(codeToCopy, highlightDiv);
      }
    } catch (_) {
      copyCodeBlockExecCommand(codeToCopy, highlightDiv);
    } finally {
   button.blur();
    button.innerText = "Copied!";
    setTimeout(function () {
      button.innerText = "Copy";
    }, 2000);  }
  }
  
  function copyCodeBlockExecCommand(codeToCopy, highlightDiv) {
    const textArea = document.createElement("textArea");
    textArea.contentEditable = "true";
    textArea.readOnly = "false";
    textArea.className = "copyable-text-area";
    textArea.value = codeToCopy;
    highlightDiv.insertBefore(textArea, highlightDiv.firstChild);
    const range = document.createRange();
    range.selectNodeContents(textArea);
    const sel = window.getSelection();
    sel.removeAllRanges();
    sel.addRange(range);
    textArea.setSelectionRange(0, 999999);
    document.execCommand("copy");
    highlightDiv.removeChild(textArea);
  }

Button Styling

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
  .chroma .lntd pre {
      border: 0px solid #b3b3b3;
  }
  
  .chroma .lntd:first-child {
    padding: 7px 7px 7px 10px;
    margin: 0;
  }
  
  .chroma .lntd:last-child {
    padding: 7px 10px 7px 7px;
    margin: 0;
  }
  
  .highlight {
    position: relative;
    z-index: 0;
    padding: 0;
    margin:40px 0 10px 0;
    border-radius: 4px;
  }
  
  .highlight > .chroma {
    position: static;
    z-index: 1;
    border-top-left-radius: 0px;
    border-top-right-radius: 0px;
    border-bottom-left-radius: 4px;
    border-bottom-right-radius: 4px;
    padding: 10px;
  }

  .highlight-wrapper {
    display: block;
  }
  
  .highlight-wrapper .lntd pre {
      padding: 0;
  }

  .btn-copy {
    position: absolute;
    z-index: 2;
    right: 0;
    top: -29px;
    font-size: 13px;
    font-weight: 700;
    line-height: 14px;
    letter-spacing: 0.5px;
    width: 65px;
    color: #b3b3b3;
    background-color: #2a2d34ff;
    border: 1.25px solid #2a2d34ff;
    border-top-left-radius: 4px;
    border-top-right-radius: 4px;
    border-bottom-right-radius: 0px;
    border-bottom-left-radius: 0px;
    white-space: nowrap;
    padding: 6px 6px 7px 6px;
    margin: 0 0 0 1px;
    cursor: pointer;
    opacity: 0.6;
  }
  
  .btn-copy:hover,
  .btn-copy:focus,
  .btn-copy:active,
  .btn-copy:active:hover {
    color: #2a2d34ff;
    background-color: #b3b3b3;
    opacity: 0.8;
  }
  
  .copyable-text-area {
    position: absolute;
    height: 0;
    z-index: -1;
    opacity: .01;
  }
  .chroma [data-lang]:before {
    position: absolute;
    z-index: 0;
    top: -29px;
    left: 0;
    content: attr(data-lang);
    font-size: 13px;
    font-weight: 700;
    color: #b3b3b3;
    background-color: #2a2d34ff;
    border-top-left-radius: 4px;
    border-top-right-radius: 4px;
    border-bottom-left-radius: 0;
    border-bottom-right-radius: 0;
    padding: 6px 6px 7px 6px;
    line-height: 14px;
    opacity: 0.6;
    position: absolute;
    letter-spacing: 0.5px;
    border: 1.25px solid #2a2d34ff;
    margin: 0 0 0 1px;
  }
  

When there is a block of code in the current payload, we need to add the stylesheet to the html page. In the header :

1
2
3
4
5
6
7
  {{ if (findRE "<code" .Content 1) }}
    {{ $syntax := resources.Get "chroma.css" | minify }}
    <link href="{{ $syntax.RelPermalink }}" rel="stylesheet">

    {{ $copyCss := resources.Get "copycodebtn.css" | minify }}
    <link href="{{ $copyCss.RelPermalink }}" rel="stylesheet">
  {{ end }}

chroma.css can be generated from hugo command line.

1
hugo gen chromastyles --style=monokai > assets/chroma.css

In the footer :

1
2
3
4
  {{ if (findRE "<code" .Content 1) }} 
    {{ $jsCopy :=resources.Get "copycode.js" | minify }} 
    <script src="{{ $jsCopy.RelPermalink }}"></script>
  {{ end }}

Change config.toml

Because I have updated the chromastyle css, I had to modify this part. This can be omitted.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
  [markup.highlight]
    anchorLineNos = false
    codeFences = true
    guessSyntax = false
    lineAnchors = ' '
    lineNoStart = 1
    lineNos=true
    lineNumbersInTable=true
    noClasses = false
    noHl = false
    style = 'monokai'
    tabWidth = 4

References