Code block copy button

submitted by edited

https://quokk.au/static/media/posts/X4/x9/X4x9bj3YQen3v0n.png

Code block copy button

I am sure you have seen websites in which all code blocks have a useful "copy to clipboard" button in the corner of the code block. I am not sure if this has been discussed for PieFed, but it could be useful. I wrote this userscript that adds this to all code blocks (multi-line and inline) on PieFed. Feel free to steal this code (and probably improve it) if you want to add this feature into PieFed.

code

```js
//adds a "copy" button to every code block in the default PieFed UI
(function() {
// places the provided string in the clipboard
function copyToClipboard(value) {
let isSuccessful = false;
try {
if (navigator.clipboard) {
navigator.clipboard.writeText(value);
isSuccessful = true;
} else {
const textarea = document.createElement("textarea");
textarea.textContent = value;
document.body.appendChild(textarea);
textarea.focus({ preventScroll: true });
textarea.select();
document.execCommand("copy");
document.body.removeChild(textarea);
isSuccessful = true;
}
}
catch {
isSuccessful = false;
}
return isSuccessful;
}

// copies to clipboard the code block associated with the provided button
function buttonHandlerCopy(button) {
let success = false;

// determine string to be copied  
let codeblock = button.parentElement;  
let copyString = codeblock.innerText;  

// copy string to clipboard  
success = copyToClipboard(copyString);  

// temporarily change icon to a checkmark if copy happened  
if (success){  
  // get copy icon size/color  
  let style = window.getComputedStyle(button);  
  var iconSize = style.getPropertyValue('font-size');  
  var iconColor = style.getPropertyValue('color');  

  // change icon to green checkmark  
  while (button.firstChild) button.removeChild(button.lastChild);  
  button.appendChild(checkmark(iconSize));  

  // after short amount of time, change icon back  
  setTimeout(function() {  
    while (button.firstChild) button.removeChild(button.lastChild);  
    button.appendChild(copyIcon(iconSize, iconColor));  
  }, 1000);  
}

}

// add css stylesheet to document
function addStylesheet(){
var styleString = `
code {
position: relative;
}

code .btn {
--bs-btn-padding-x: 0.25rem ! important;
--bs-btn-padding-y: 0 ! important;
}

code .before {
float: right;
}

code .btn:hover {
background-color: rgba(0, 0, 0, 0.3);
color: white;
}
`;
var head = document.querySelector('head');
if (!head) { return; }
var style = document.createElement('style');
style.type = 'text/css';
style.innerHTML = styleString;
head.appendChild(style);
}

const copyIcon = (size = "16", color = "#000000") => {
const svgNamespace = "http://www.w3.org/2000/svg";

// Create the SVG element  
const svg = document.createElementNS(svgNamespace, "svg");  
svg.setAttribute("xmlns", svgNamespace);  
svg.setAttribute("width", size);  
svg.setAttribute("height", size);  
svg.setAttribute("viewBox", "0 0 20 20");  
svg.setAttribute("fill", "none");  

// Create the path element  
const path = document.createElementNS(svgNamespace, "path");  
path.setAttribute("fill", color);  
path.setAttribute("fill-rule", "evenodd");  
path.setAttribute("d", "M4 2a2 2 0 00-2 2v9a2 2 0 002 2h2v2a2 2 0 002 2h9a2 2 0 002-2V8a2 2 0 00-2-2h-2V4a2 2 0 00-2-2H4zm9 4V4H4v9h2V8a2 2 0 012-2h5zM8 8h9v9H8V8z");  

// Append the path to the SVG  
svg.appendChild(path);  

return svg;  

return svg;

};

const checkmark = (size = "16", color = "#26a269") => {
const svgNamespace = "http://www.w3.org/2000/svg";

// Create the SVG element  
const svg = document.createElementNS(svgNamespace, "svg");  
svg.setAttribute("xmlns", svgNamespace);  
svg.setAttribute("width", size);  
svg.setAttribute("height", size);  
svg.setAttribute("viewBox", "0 0 16 16");  
svg.setAttribute("fill", "none");  

// create g elements  
const g0 = document.createElementNS(svgNamespace, "g");  
g0.setAttribute("id", "SVGRepo_bgCarrier");  
g0.setAttribute("stroke-width", "0");  

const g1 = document.createElementNS(svgNamespace, "g");  
g1.setAttribute("id", "SVGRepo_tracerCarrier");  
g1.setAttribute("stroke-linecap", "round");  
g1.setAttribute("stroke-linejoin", "round");  
svg.appendChild(g1);  

const g2 = document.createElementNS(svgNamespace, "g");  
g2.setAttribute("id", "SVGRepo_iconCarrier");  

// Create the path element, place in final g element  
const path = document.createElementNS(svgNamespace, "path");  
path.setAttribute("fill", color);  
path.setAttribute("d", "M15.4141 4.91424L5.99991 14.3285L0.585693 8.91424L3.41412 6.08582L5.99991 8.6716L12.5857 2.08582L15.4141 4.91424Z");  
g2.appendChild(path);  
svg.appendChild(g2);  

return svg;

};

addStylesheet();

// add buttons to each code block onscreen
document.querySelectorAll("code").forEach(code => {
// get element properties for the new button to match
let style = window.getComputedStyle(code);
var fontSize = style.getPropertyValue('font-size');
var color = style.getPropertyValue('color');

// create button  
var button = document.createElement('button');  
button.classList.add("btn");  
button.appendChild(copyIcon(fontSize, color));  
button.addEventListener("click", function(event){ buttonHandlerCopy(event.target.parentElement); });  
button.addEventListener("tap", function(event){ buttonHandlerCopy(event.target.parentElement); });  

// place the button at the beginning/end of the code block  
// depending on whether it is a multi-line block or an inline block  
if (code.parentElement.tagName.toLowerCase() == "pre"){  
  // place button at the beginning of the multi-line code block  
  button.classList.add("before");  
  code.insertBefore(button, code.firstChild);  
} else {  
  // place button at the end of the inline code block  
  button.classList.add("after");  
  code.appendChild(button);  
}

});
})();
```

1

Log in to comment

4 Comments

Here is a bit of demo code:

javascript alert('hi');

It might be a good idea to add a setting to disable it. I do not think that I ever would, but I can imagine someone possibly finding it distracting or whatever on inline code blocks in particular.

I'll add something in !piefed_css@piefed.social about it.