Skip to content

Commit

Permalink
LCS: Attempt to cherry-pick 0527e17 to get copy-to-clipboard
Browse files Browse the repository at this point in the history
Add "copy to clipboard" button for code blocks (mmistakes#2812)

* Add copy-to-clipboard button and JS

* Ignore line numbers if present

* Rewrite heading permalink code to use vanilla JS

* README: Add credits to zenorocha/clipboard.js (MIT License)

@iBug really wants a place here in the Credits section :P

* Add .no-copy for hiding the button, update docs

* Add td.rouge-code to selectors

* Fix navigator.clipboard branch

* Add screenreader text for copy button

* Restore focus to the button after copying

* Add site-wide enable switch
  • Loading branch information
iBug authored and duetosymmetry committed Aug 15, 2024
1 parent c1144e8 commit a07231f
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 6 deletions.
1 change: 1 addition & 0 deletions _config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ repository : # GitHub username/repo-name e.g. "mmistakes/minimal-m
teaser : # filename of teaser fallback teaser image placed in /images/, .e.g. "500x300.png"
# breadcrumbs : false # true, false (default)
words_per_minute : 200
enable_copy_code_button : # true, false (default)
comments:
provider : # false (default), "disqus", "discourse", "facebook", "google-plus", "staticman", "custom"
disqus:
Expand Down
5 changes: 4 additions & 1 deletion _includes/head.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@
<meta name="MobileOptimized" content="320">
<meta name="viewport" content="width=device-width, initial-scale=1.0">

<script>
<script type="text/javascript">
document.documentElement.className = document.documentElement.className.replace(/\bno-js\b/g, '') + ' js ';
{% if site.enable_copy_code_button -%}
window.enable_copy_code_button = true;
{%- endif %}
</script>

<!-- For all browsers -->
Expand Down
57 changes: 57 additions & 0 deletions _sass/_utilities.scss
Original file line number Diff line number Diff line change
Expand Up @@ -484,3 +484,60 @@ a.reversefootnote {
color: $danger-color;
font-weight: bold;
}

/*
Copy <pre> block to clipboard
========================================================================== */

// a <textarea> to hold text for document.execCommand("copy")
.clipboard-helper {
// Prevent zooming on iOS
font-size: 12pt !important;
border: 0 !important;
padding: 0 !important;
margin: 0 !important;
outline: none !important;
position: absolute;
}

pre {
.clipboard-copy-button {
display: block;
position: absolute;
top: 0.6em;
right: 0.5em;
z-index: 1;
background: none;
border: none;
outline: none;
border-radius: 0.1em;
padding: 0.2em 0.5em;
color: white;
opacity: 0.4;
transition: color 0.25s linear -0.25s, opacity 0.25s linear;

&:hover {
color: #ffffca;
}

&::before {
content: '';
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 2;
}

@at-root {
.no-copy & {
display: none;
}
}
}

&:hover .clipboard-copy-button {
opacity: 1;
}
}
107 changes: 102 additions & 5 deletions assets/js/_main.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ $(document).ready(function(){
gallery: {
enabled: true,
navigateByImgClick: true,
preload: [0,1] // Will preload 0 - before current, and 1 after the current image
preload: [0, 1], // Will preload 0 - before current, and 1 after the current image
},
image: {
tError: '<a href="%url%">Image #%curr%</a> could not be loaded.',
Expand All @@ -88,13 +88,110 @@ $(document).ready(function(){
// make it unique to apply your CSS animations just to this exact popup
mainClass: 'mfp-zoom-in',
callbacks: {
beforeOpen: function() {
beforeOpen: function () {
// just a hack that adds mfp-anim class to markup
this.st.image.markup = this.st.image.markup.replace('mfp-figure', 'mfp-figure mfp-with-anim');
}
this.st.image.markup = this.st.image.markup.replace(
"mfp-figure",
"mfp-figure mfp-with-anim"
);
},
},
closeOnContentClick: true,
midClick: true // allow opening popup on middle mouse click. Always set it to true if you don't provide alternative source.
midClick: true, // allow opening popup on middle mouse click. Always set it to true if you don't provide alternative source.
});

// Add anchors for headings
document
.querySelector(".page__content")
.querySelectorAll("h1, h2, h3, h4, h5, h6")
.forEach(function (element) {
var id = element.getAttribute("id");
if (id) {
var anchor = document.createElement("a");
anchor.className = "header-link";
anchor.href = "#" + id;
anchor.innerHTML =
'<span class="sr-only">Permalink</span><i class="fas fa-link"></i>';
anchor.title = "Permalink";
element.appendChild(anchor);
}
});

// Add copy button for <pre> blocks
var copyText = function (text) {
if (document.queryCommandEnabled("copy") && navigator.clipboard) {
navigator.clipboard.writeText(text).then(
() => true,
() => console.error("Failed to copy text to clipboard: " + text)
);
return true;
} else {
var isRTL = document.documentElement.getAttribute("dir") === "rtl";

var textarea = document.createElement("textarea");
textarea.className = "clipboard-helper";
textarea.style[isRTL ? "right" : "left"] = "-9999px";
// Move element to the same position vertically
var yPosition = window.pageYOffset || document.documentElement.scrollTop;
textarea.style.top = yPosition + "px";

textarea.setAttribute("readonly", "");
textarea.value = text;
document.body.appendChild(textarea);

var success = true;
try {
textarea.select();
success = document.execCommand("copy");
} catch (e) {
success = false;
}
textarea.parentNode.removeChild(textarea);
return success;
}
};

var copyButtonEventListener = function (event) {
var thisButton = event.target;

// Locate the <code> element
var codeBlock = thisButton.nextElementSibling;
while (codeBlock && codeBlock.tagName.toLowerCase() !== "code") {
codeBlock = codeBlock.nextElementSibling;
}
if (!codeBlock) {
// No <code> found - wtf?
console.warn(thisButton);
throw new Error("No code block found for this button.");
}

// Skip line numbers if present (i.e. {% highlight lineno %})
var realCodeBlock = codeBlock.querySelector("td.code, td.rouge-code");
if (realCodeBlock) {
codeBlock = realCodeBlock;
}
var result = copyText(codeBlock.innerText);
// Restore the focus to the button
thisButton.focus();
return result;
};

if (window.enable_copy_code_button) {
document
.querySelectorAll(".page__content pre > code")
.forEach(function (element, index, parentList) {
// Locate the <pre> element
var container = element.parentElement;
// Sanity check - don't add an extra button if there's already one
if (container.firstElementChild.tagName.toLowerCase() !== "code") {
return;
}
var copyButton = document.createElement("button");
copyButton.title = "Copy to clipboard";
copyButton.className = "clipboard-copy-button";
copyButton.innerHTML = '<span class="sr-only">Copy code</span><i class="far fa-copy"></i>';
copyButton.addEventListener("click", copyButtonEventListener);
container.prepend(copyButton);
});
}
});

0 comments on commit a07231f

Please sign in to comment.