One Thousand Monkeys
   5 min read

I dun it!
The button to copy the contents of the code boxes now appears both in those that have syntax (highlight, chroma) as in those that don’t have:

1
cowsay "monkeys, I tell you!"
# output
 ______________________
< monkeys, I tell you! >
 ----------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

How have I dun it? Dunno…
Hence the title: one thousand monkeys banging on one thousand typewriters could write “The C Programming Language” if they had until the heat death of the Universe to do it.
I am partly kidding; but that’s how it felt.

The reasoning

Eventually I cottoned on that in Zdoc codeblocks can have two different structures, depending on if syntax highlighting has been activated; one big difference in that case is the insertion of a column with the line number or the prompt prior to the text itself; or rather, I had noticed it already, but eventually I surrendered to the evidence that I would have to create two different selectors, which could separate one case from the other so I could handle them differently.
But for a long time I could only select the codes with highlight, or when I could place buttons on the two types of blocks, the highlight blocks ended up with two buttons, one of them inoperative (because of the two column structure, I imagine).

The highlight codeblock is structured like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
<!-- line numbers -->
</span><span class="lnt">36
</span></code></pre></td>
<td class="lntd">
<pre class="chroma"><code class="language-assets/sass/pages/_single.scss" data-lang="assets/sass/pages/_single.scss">
<!-- code -->
</code></pre></td></tr></table>
</div>

While the syntax-less code block is much simpler:

1
2
3
<pre><code>
<!-- code -->
</code></pre>

The selector

Eventually I tried this method, and it worked:

  • Grab everything under <pre><code>
  • Does it have more sections inside?
    • Y: button with syntax
    • N: button without syntax
As it happens, Zdoc has some code for Mermaid diagrams, but then you look into it and it’s not active.
Not very useful…

In code, it came up like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
  var codeBlocks = document.querySelectorAll('pre > code');
  for (var i = 0; i < codeBlocks.length; i++) {
     var highlightBlocks = codeBlocks[i].firstElementChild;
    if (highlightBlocks != null) {
        i++;
        addCopyButton(codeBlocks[i].parentNode.parentNode);
    } else { 
      addCopyCodeButton(codeBlocks[i].parentNode.parentNode);
    }
  }

After I became able to select the two blocks in two differently coded branches, I treated them differently in the most heavy-handed way possible, which was to duplicate the function and make adjustments in each of the copies.
Then I had a devil of a time to avoid getting duplicated buttons, and everything I achieved was through painstaking trial-and-error with all combinations of .firstElementChild e .parentNode.
And I find myself in a place I don’t appreciate at all, “it works and I don’t know how”"; and since I don’t kow how it works, I can’t help but notice that the buttons are in different places and misaligned, but I can’t solve it…

Result

 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
// =================================================================
// insert just before the \<\/script\> tag
// tomspencer, https://www.tomspencer.dev/blog/2018/09/14/adding-click-to-copy-buttons-to-a-hugo-powered-blog/
(function() {
  'use strict';

  if(!document.queryCommandSupported('copy')) {
    return;
  }

  function flashCopyMessage(el, msg) {
    el.textContent = msg;
    setTimeout(function() {
      el.textContent = "COPY";
    }, 1000);
  }

  function selectText(node) {
    var selection = window.getSelection();
    var range = document.createRange();
    range.selectNodeContents(node);
    selection.removeAllRanges();
    selection.addRange(range);
    return selection;
  }

  function addCopyButton(containerEl) {
    var copyBtn = document.createElement("button");
    copyBtn.className = "highlight-copy-btn";
    copyBtn.textContent = "COPY";

    var codeEl = containerEl.firstElementChild;
    copyBtn.addEventListener('click', function() {
      try {
        var selection = selectText(codeEl);
        document.execCommand('copy');
        selection.removeAllRanges();

        flashCopyMessage(copyBtn, 'Copied!')
      } catch(e) {
        console && console.log(e);
        flashCopyMessage(copyBtn, 'Failed :\'(')
        }
      });
      containerEl.appendChild(copyBtn);
  }
   
  function addCopyCodeButton(containerEl) {
    var copyBtn = document.createElement("button");
    copyBtn.className = "code-copy-btn";
    copyBtn.textContent = "COPY";

    var codeEl = containerEl.firstElementChild;
    copyBtn.addEventListener('click', function() {
      try {
        var selection = selectText(codeEl);
        document.execCommand('copy');
        selection.removeAllRanges();

        flashCopyMessage(copyBtn, 'Copied!')
      } catch(e) {
        console && console.log(e);
        flashCopyMessage(copyBtn, 'Failed :\'(')
        }
       });

      containerEl.appendChild(copyBtn);
    }
  
  var codeBlocks = document.querySelectorAll('pre > code');
  for (var i = 0; i < codeBlocks.length; i++) {
     var highlightBlocks = codeBlocks[i].firstElementChild;
    if (highlightBlocks != null) {
        i++;
        addCopyButton(codeBlocks[i].parentNode.parentNode);
    } else { 
      addCopyCodeButton(codeBlocks[i].parentNode.parentNode);
    }
  }
})();

And the CSS:

 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
.code-copy-btn {
  position: relative;
  bottom: 1.1rem;
  left: 0.5rem;
  border: 0;
  border-radius: 4px;
  padding: 1px;
  font-size: 0.6em;
  line-height: 1.0;
  opacity: 0.6;
  min-width: 65px;
  text-align: center;
  letter-spacing: 2px;
  @include themify($codeblock) {
  color: themed('content-pre-color');
  background: themed('content-pre-background-color');
  border: 1.25px solid themed('border-line-color');
  }
  border-top-left-radius: 0;
  border-top-right-radius: 4px;
  border-bottom-right-radius: 0;
  border-bottom-left-radius: 4px;
  white-space: nowrap;
  padding: 4px 4px 5px 4px;
  margin: 0 0 0 1px;
  cursor: pointer;
  opacity: 0.6;
}

.code-copy-btn:hover {
  @include themify($themes) {
    color: themed('link');
    background-color: themed('body-background-color');
  }
}

.highlight-copy-btn {
  position: relative;
  bottom: 0rem;
  left: -1.8rem;
  border: 0;
  border-radius: 4px;
  padding: 1px;
  font-size: 0.6em;
  line-height: 1.0;
  opacity: 0.6;
  min-width: 65px;
  text-align: center;
  letter-spacing: 2px;
  @include themify($codeblock) {
  color: themed('content-pre-color');
  background: themed('content-pre-background-color');
  border: 1.25px solid themed('border-line-color');
  }
  border-top-left-radius: 0;
  border-top-right-radius: 4px;
  border-bottom-right-radius: 0;
  border-bottom-left-radius: 4px;
  white-space: nowrap;
  padding: 4px 4px 5px 4px;
  margin: 0 0 0 1px;
  cursor: pointer;
  opacity: 0.6;

}

.highlight-copy-btn:hover {
  @include themify($themes) {
    color: themed('link');
    background-color: themed('body-background-color');
    }
}

In the end

Do I feel happy? No.
But that’s all there is at the moment.
What I actually needed was to find some way of inserting the column with the line numbers on all codeblocks, so I could go back to the previous code, which was much cleaner.

Next steps

I need to find a workflow for adding network diagrams…
Even Mermaid won’t do it. Not that it works.

I’m not bitter, you’re bitter.