Skip Navigation

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.

 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);  
    }  
  });  
})();  
  
  

Comments

2