From e529aaf2ee374c260da9eb814cccf4fdfb883bb5 Mon Sep 17 00:00:00 2001 From: Ryan Peden Date: Fri, 31 Mar 2023 20:10:00 -0400 Subject: [PATCH 1/4] enabled 'copy to clipboard' button for code blocks --- docs/stylesheets/extra.css | 3 +++ mkdocs.insiders.yml | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index e90393aa018c..391231fe5cc6 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -48,6 +48,9 @@ outline: none; } +nav.md-code__nav { + margin-top: 20px; +} /* Image drop shadow, separates browser from background */ img { margin-top: 1rem; diff --git a/mkdocs.insiders.yml b/mkdocs.insiders.yml index f4818d5f2b1e..7bfe29ac8347 100644 --- a/mkdocs.insiders.yml +++ b/mkdocs.insiders.yml @@ -6,4 +6,8 @@ plugins: cards_color: fill: "#115AF4" typeset: +theme: + features: + - content.code.copy + From fcd367eea09cd944231bc8884f7b231f02bd93c1 Mon Sep 17 00:00:00 2001 From: Ryan Peden Date: Wed, 12 Apr 2023 16:04:18 -0400 Subject: [PATCH 2/4] Add JS to remove leading `$` symbols when copying terminal blocks --- docs/js/custom.js | 60 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/docs/js/custom.js b/docs/js/custom.js index ff9b3d75e3cb..1b177eec5bf6 100644 --- a/docs/js/custom.js +++ b/docs/js/custom.js @@ -112,8 +112,66 @@ function setupTermynal() { loadVisibleTermynals(); } +/** + * This function is enhances the 'copy to clipboard' buttons on code blocks + * by removing leading $ signs. This is useful for code blocks containing + * shell commands. + */ +function enhanceCopyButtons() { + // wait until the DOM is loaded before looking up the buttons + document.addEventListener("DOMContentLoaded", function() { + var buttons = document.querySelectorAll( + ".terminal pre[id^='__code_'] button.md-code__button"); + buttons.forEach(function(button) { + // check if the button's "data-clipboard-target" attribute is already + // a cleaned textarea + if (!button.getAttribute("data-clipboard-target").startsWith("#__cleaned")) { + // Adds event listeners to the button to clean the code block + // before the 'click' event handler copies the code to the clipboard. + + // On desktop devices, clean the code block when the user + // hovers over the button. + button.addEventListener("mouseenter", copyCleaner); + + // On mobile devices, clean the code block when the user + // taps on the button. This works because the 'click' event + // is fired after the 'touchstart' event. + button.addEventListener("touchstart", copyCleaner); + + function copyCleaner() { + // look up the code block + var codeBlockSelector = button.getAttribute("data-clipboard-target"); + + // return early if we've already cleaned this code block + if (codeBlockSelector.startsWith("#__cleaned")) { return; } + var codeBlock = document.querySelector(codeBlockSelector); + + // clean the code block by removing leading $ signs and trim whitespace + var cleanedCode = codeBlock.innerText.replace(/^\$\s/gm, "").trim(); + var cleanedCodeId = "__cleaned_" + codeBlockSelector.replace('#','').split(" ")[0]; + var cleanedCodeBlock = document.getElementById(cleanedCodeId); + + // if the cleaned code block doesn't exist, create it and add it to the DOM + if (cleanedCodeBlock == null) { + cleanedCodeBlock = document.createElement("code"); + cleanedCodeBlock.id = cleanedCodeId; + + // preserve newlines by replacing them with
+ cleanedCodeBlock.innerHTML = cleanedCode; + + cleanedCodeBlock.style.display = "none"; + document.body.appendChild(cleanedCodeBlock); + } + button.setAttribute("data-clipboard-target", "#" + cleanedCodeId); + } + } + }); + }); +} + async function main() { setupTermynal(); + enhanceCopyButtons(); } -main() +main() \ No newline at end of file From a8656a190da6524579934bfbf4cef35dc0e9e3ec Mon Sep 17 00:00:00 2001 From: Ryan Peden Date: Wed, 12 Apr 2023 16:34:55 -0400 Subject: [PATCH 3/4] Update comments and formatting in JS --- docs/js/custom.js | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/docs/js/custom.js b/docs/js/custom.js index 1b177eec5bf6..e6defbbb1611 100644 --- a/docs/js/custom.js +++ b/docs/js/custom.js @@ -113,20 +113,20 @@ function setupTermynal() { } /** - * This function is enhances the 'copy to clipboard' buttons on code blocks - * by removing leading $ signs. This is useful for code blocks containing - * shell commands. + * This function is enhances the 'copy to clipboard' buttons on terminal blocks + * by removing leading $ signs. This ensures users can copy and paste the + * commands into their terminal without having to manually remove the $ signs. */ function enhanceCopyButtons() { - // wait until the DOM is loaded before looking up the buttons + // Wait until the DOM is loaded before looking up the buttons document.addEventListener("DOMContentLoaded", function() { var buttons = document.querySelectorAll( ".terminal pre[id^='__code_'] button.md-code__button"); buttons.forEach(function(button) { - // check if the button's "data-clipboard-target" attribute is already - // a cleaned textarea + // Check if the button's "data-clipboard-target" attribute is already + // a cleaned textarea - if so, no need to clean it again. if (!button.getAttribute("data-clipboard-target").startsWith("#__cleaned")) { - // Adds event listeners to the button to clean the code block + // Next, add event listeners to the button to clean the code block // before the 'click' event handler copies the code to the clipboard. // On desktop devices, clean the code block when the user @@ -139,7 +139,8 @@ function enhanceCopyButtons() { button.addEventListener("touchstart", copyCleaner); function copyCleaner() { - // look up the code block + // Look up the code block referenced by the button's + // "data-clipboard-target" attribute. var codeBlockSelector = button.getAttribute("data-clipboard-target"); // return early if we've already cleaned this code block @@ -155,8 +156,6 @@ function enhanceCopyButtons() { if (cleanedCodeBlock == null) { cleanedCodeBlock = document.createElement("code"); cleanedCodeBlock.id = cleanedCodeId; - - // preserve newlines by replacing them with
cleanedCodeBlock.innerHTML = cleanedCode; cleanedCodeBlock.style.display = "none"; From 5087752854b915b14adb97b6cff0d6ab4054ae96 Mon Sep 17 00:00:00 2001 From: Ryan Peden Date: Wed, 12 Apr 2023 17:08:09 -0400 Subject: [PATCH 4/4] Add text node to escape HTML and remove XSS risk. --- docs/js/custom.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/docs/js/custom.js b/docs/js/custom.js index e6defbbb1611..4babf15c7603 100644 --- a/docs/js/custom.js +++ b/docs/js/custom.js @@ -141,26 +141,31 @@ function enhanceCopyButtons() { function copyCleaner() { // Look up the code block referenced by the button's // "data-clipboard-target" attribute. - var codeBlockSelector = button.getAttribute("data-clipboard-target"); + let codeBlockSelector = button.getAttribute("data-clipboard-target"); // return early if we've already cleaned this code block if (codeBlockSelector.startsWith("#__cleaned")) { return; } - var codeBlock = document.querySelector(codeBlockSelector); + let codeBlock = document.querySelector(codeBlockSelector); // clean the code block by removing leading $ signs and trim whitespace - var cleanedCode = codeBlock.innerText.replace(/^\$\s/gm, "").trim(); - var cleanedCodeId = "__cleaned_" + codeBlockSelector.replace('#','').split(" ")[0]; - var cleanedCodeBlock = document.getElementById(cleanedCodeId); + let cleanedCode = codeBlock.innerText.replace(/^\$\s/gm, "").trim(); + let cleanedCodeId = "__cleaned_" + + codeBlockSelector.replace('#','').split(" ")[0]; + let cleanedCodeBlock = document.getElementById(cleanedCodeId); // if the cleaned code block doesn't exist, create it and add it to the DOM if (cleanedCodeBlock == null) { cleanedCodeBlock = document.createElement("code"); cleanedCodeBlock.id = cleanedCodeId; - cleanedCodeBlock.innerHTML = cleanedCode; + // escape HTML entities via createTextNode + let textNode = document.createTextNode(cleanedCode); + cleanedCodeBlock.appendChild(textNode); cleanedCodeBlock.style.display = "none"; document.body.appendChild(cleanedCodeBlock); } + // update the button's "data-clipboard-target" attribute to reference + // the cleaned code block button.setAttribute("data-clipboard-target", "#" + cleanedCodeId); } }