Code block copy button
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);
}
});
})();