Day-2, Adding Copy to Clipboard Button to Codeblocks

Around two months ago, I had the opportunity of adding a copy to clipboard button for all codeblocks on a page. This is how I went about it.

About the problem

Search results on Neera are unique in a way that we show the top answer of all Stackoverflow questions when they are shown in the results.

This is how they look when the page first loads: Initial Result View

Once you click to see the answer: Answer preview

This provided was an easy method to quickly get the accepted/most upvoted answer on Stackoverflow.

We did this by making a server which had an archive of all of Stackoverflow. We sent this server all the question id after after search and in return we received top answers of all the questions. More explained about how we set this up and put such a large archive in a database here.

Okay, so what are we working on?

Stackoverflow doesn't have the feature of copying the contents of a multi-lined codeblock using a single click. We wanted to add a button to do the same to our answer previews.

First things first

I first inspected and saw that a pre tag is present before the usual code tag when it comes to multi-lined codeblocks.

<pre>
  <code>	
   ...
  </code>
</pre>

I din't want a copy button when there is a single word or a few words of code, which is similar to single backticks in the case of markdown.

Hence, I targeted the content of only those <code> tags that resided inside <pre> tags.

Too much talk, let's code now

This is the first iteration of code I wrote, after getting help from this article. This used JavaScript Clipboard API.

function addCopyButtons(clipboard) {
  document.querySelectorAll("pre > code").forEach(function (codeBlock) {
    var button = document.createElement("button");
    button.className = "copy-code-button";
    button.type = "button";
    button.innerText = "Copy";

    button.addEventListener("click", function () {
      clipboard.writeText(codeBlock.innerText).then(
        function () {
          button.blur();
          button.innerText = "Copied!";

          setTimeout(function () {
            button.innerText = "Copy";
          }, 2000);
        },
        function (error) {
          button.innerText = "Error";
        }
      );
    });
  });
}

I put the code above in the useEffect hook, as we use React for our frontend. This would add the buttons just when the page renders.

Content with what I had written till now, I tried looking at what it looked like and to my surprise there was no button to be found :|

What is happening? or to be exact why is nothing happening?

Let's go over the code once again, line by line this time.

While I know I didn't go over "line by line", but I still don't see any issue. Do you?

So after spending 20 minutes looking figuring out where I was going wrong in the developer tools, I realized the issue.

Remember how the results look when results first render? and remember how I used useEffect and how it worked just when the page renders?

Is this supposed to be the point I understand things?

So, here is what's happening:

Explain to me like I am 5 (not really, but you get the point)

The point that I am getting at is that till I opened the answers they aren't a part of the document and even when I do open the answers the function to add buttons has already finished running. Hence, the reason there were no buttons is that there were no codeblocks at that time.

What now?

So now to solve this problem I had to make the function run at the same time the button is clicked to expand the answer, that should be enough right?

Nope.

It didn't work, I figured that this could be due to a delay in adding the answer DOM to the document, but the function started running before. Hence I made the function run after a small delay of 50 miliseconds (I think we can go lower than this).

  onClick={() => {
    setShowAnswer(!showAnswer);   // This boolean expands or collapses the answer
    setTimeout(() => {
      addCopyButtons(navigator.clipboard);
    }, 50);
  }}

This did what I wanted it to do.

A little bit of styling please

Then I wrote a bit of css to do the following:

.copy-code-button {
  color: #272822;
  background-color: #fff;
  border-color: #272822;
  border: 2px solid;
  border-radius: 3px 3px 0px 0px;

  display: block;
  margin: 0.25rem 0rem 0 auto;
  padding: 0.2rem;
  font-size: 0.8em;
}

.copy-code-button:hover {
  cursor: pointer;
  background-color: #f2f2f2;
}

.copy-code-button:focus {
  background-color: #e6e6e6;
  outline: 0;
}

.copy-code-button:active {
  background-color: #d9d9d9;
}

The Result

The Stackoverflow Answer

Comments


about · posts · resume · home · github · twitter · rss