Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Copy code to clipboard #2812

Merged
merged 12 commits into from
May 5, 2024
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ To test the theme, run `bundle exec rake preview` and open your browser at `http
- [Gumshoe](https://github.com/cferdinandi/gumshoe)
- [jQuery throttle / debounce](http://benalman.com/projects/jquery-throttle-debounce-plugin/)
- [Lunr](http://lunrjs.com)
- [Clipboard.js](https://clipboardjs.com)

## License

Expand Down Expand Up @@ -282,3 +283,7 @@ Pure Liquid Jekyll Table of Contents is distributed under the terms of the [MIT
Minimal Mistakes incorporates [Lunr](http://lunrjs.com),
Copyright (c) 2018 Oliver Nightingale.
Lunr is distributed under the terms of the [MIT License](http://opensource.org/licenses/MIT).

Minimal Mistakes incorporates [clipboard.js](https://clipboardjs.com/),
Copyright (c) 2021 Zeno Rocha.
Clipboard.js is distributed under the terms of the [MIT License](https://opensource.org/licenses/MIT).
1 change: 1 addition & 0 deletions _config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ logo : # path of logo image to display in the masthead, e.g.
masthead_title : # overrides the website title displayed in the masthead, use " " for no title
# breadcrumbs : false # true, false (default)
words_per_minute : 200
enable_copy_code_button : # true, false (default)
copyright : # "copyright" name, defaults to site.title
copyright_url : # "copyright" URL, defaults to site.url
comments:
Expand Down
5 changes: 4 additions & 1 deletion _includes/head.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@
{%- comment %} https://docs.google.com/presentation/d/1rmxwWa9P6_xHqonmh5ONXRS-jPc5XKbnv99Rjkhe04s/present {% endcomment -%}
<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/minimal-mistakes/_utilities.scss
Original file line number Diff line number Diff line change
Expand Up @@ -591,3 +591,60 @@ a.reversefootnote {
position: static;
}
}

/*
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;
}
}
130 changes: 106 additions & 24 deletions assets/js/_main.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@
jQuery plugin settings and other scripts
========================================================================== */

$(function() {
$(document).ready(function () {
// FitVids init
$("#main").fitVids();

// Follow menu drop down
$(".author__urls-wrapper").find("button").on("click", function() {
$(".author__urls-wrapper button").on("click", function () {
$(".author__urls").toggleClass("is--visible");
$(".author__urls-wrapper").find("button").toggleClass("open");
});

// Close search screen with Esc key
$(document).keyup(function(e) {
$(document).keyup(function (e) {
if (e.keyCode === 27) {
if ($(".initial-content").hasClass("is--hidden")) {
$(".search-content").toggleClass("is--visible");
Expand All @@ -23,12 +23,12 @@ $(function() {
});

// Search toggle
$(".search__toggle").on("click", function() {
$(".search__toggle").on("click", function () {
$(".search-content").toggleClass("is--visible");
$(".initial-content").toggleClass("is--hidden");
// set focus on input
setTimeout(function() {
$(".search-content").find("input").focus();
setTimeout(function () {
$(".search-content input").focus();
}, 400);
});

Expand All @@ -37,11 +37,11 @@ $(function() {
offset: 20,
speed: 400,
speedAsDuration: true,
durationMax: 500
durationMax: 500,
});

// Gumshoe scroll spy init
if($("nav.toc").length > 0) {
if ($("nav.toc").length > 0) {
var spy = new Gumshoe("nav.toc a", {
// Active classes
navClass: "active", // applied to the nav list item
Expand All @@ -56,7 +56,7 @@ $(function() {
reflow: true, // if true, listen for reflows

// Event support
events: true // if true, emit custom events
events: true, // if true, emit custom events
});
}

Expand Down Expand Up @@ -95,38 +95,120 @@ $(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.'
tError: '<a href="%url%">Image #%curr%</a> could not be loaded.',
},
removalDelay: 500, // Delay in milliseconds before popup is removed
// Class that is added to body when popup is open.
// 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"
);
}
},
},
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
$('.page__content').find('h1, h2, h3, h4, h5, h6').each(function() {
var id = $(this).attr('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";
$(this).append(anchor);
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);
});
}
});
2 changes: 1 addition & 1 deletion assets/js/main.min.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions docs/_config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ logo : # path of logo image to display in the masthead, e.g.
masthead_title : # overrides the website title displayed in the masthead, use " " for no title
# breadcrumbs : false # true, false (default)
words_per_minute : 200
enable_copy_code_button : true
comments:
provider : "false" # false (default), "disqus", "discourse", "facebook", "staticman_v2", "staticman", "utterances", "giscus", "custom"
disqus:
Expand Down
24 changes: 24 additions & 0 deletions docs/_docs/05-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,30 @@ For example,
}
```

### Code block copy button

To enable a copy button on code blocks, add the following to `_config.yml`:

```yaml
enable_copy_code_button: true
```

When enabled site-wide, the button can be disabled on individual code blocks by adding `no-copy` to the code block's class list.

````markdown
```
Hey, I have a "copy to clipboard" button!
```
````

````markdown
```
But I don't have one.
```
{: .no-copy}
````
{: .no-copy}

### Comments

[**Disqus**](https://disqus.com/), [**Discourse**](https://www.discourse.org/), [**Facebook**](https://developers.facebook.com/docs/plugins/comments), [**utterances**](https://utteranc.es/), [**giscus**](https://giscus.app/) and static-based commenting via [**Staticman**](https://staticman.net/) are built into the theme. First set the comment provider you'd like to use:
Expand Down