From f54df35767dcda9bc4853decff86d57323593685 Mon Sep 17 00:00:00 2001
From: Geoffrey Garrett <g.h.garrett13@gmail.com>
Date: Sun, 03 Jul 2022 18:42:35 +0000
Subject: [PATCH] Copy to clipboard feature for code block (#152)
---
assets/js/clipboard.js | 38 ++++++++++++
content/notes/config.md | 21 ++++---
data/config.yaml | 1
layouts/partials/head.html | 19 +++++-
assets/js/darkmode.js | 2
assets/styles/clipboard.scss | 47 +++++++++++++++
6 files changed, 114 insertions(+), 14 deletions(-)
diff --git a/assets/js/clipboard.js b/assets/js/clipboard.js
new file mode 100644
index 0000000..6389330
--- /dev/null
+++ b/assets/js/clipboard.js
@@ -0,0 +1,38 @@
+const svgCopy =
+ '<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true"><path fill-rule="evenodd" d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 010 1.5h-1.5a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-1.5a.75.75 0 011.5 0v1.5A1.75 1.75 0 019.25 16h-7.5A1.75 1.75 0 010 14.25v-7.5z"></path><path fill-rule="evenodd" d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0114.25 11h-7.5A1.75 1.75 0 015 9.25v-7.5zm1.75-.25a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-7.5a.25.25 0 00-.25-.25h-7.5z"></path></svg>';
+const svgCheck =
+ '<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true"><path fill-rule="evenodd" fill="rgb(63, 185, 80)" d="M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z"></path></svg>';
+
+
+const addCopyButtons = () => {
+ let els = document.getElementsByClassName("highlight");
+ // for each highlight
+ for (let i = 0; i < els.length; i++) {
+ if (els[i].getElementsByClassName("clipboard-button").length) continue;
+
+ // find pre > code inside els[i]
+ let codeBlocks = els[i].getElementsByTagName("code");
+
+ // line numbers are inside first code block
+ let lastCodeBlock = codeBlocks[codeBlocks.length - 1];
+ const button = document.createElement("button");
+ button.className = "clipboard-button";
+ button.type = "button";
+ button.innerHTML = svgCopy;
+ // remove every second newline from lastCodeBlock.innerText
+ button.addEventListener("click", () => {
+ navigator.clipboard.writeText(lastCodeBlock.innerText.replace(/\n\n/g, "\n")).then(
+ () => {
+ button.blur();
+ button.innerHTML = svgCheck;
+ setTimeout(() => (button.innerHTML = svgCopy), 2000);
+ },
+ (error) => (button.innerHTML = "Error")
+ );
+ });
+ // find chroma inside els[i]
+ let chroma = els[i].getElementsByClassName("chroma")[0];
+ els[i].insertBefore(button, chroma);
+ console.log(els[i].lastChild)
+ }
+}
diff --git a/assets/js/darkmode.js b/assets/js/darkmode.js
index 11ce15f..8168d77 100644
--- a/assets/js/darkmode.js
+++ b/assets/js/darkmode.js
@@ -8,7 +8,7 @@
if (currentTheme) {
document.documentElement.setAttribute('saved-theme', currentTheme);
- (currentTheme === 'dark') ? syntaxTheme.href = '{{ $darkSyntax.Permalink }}' : syntaxTheme.href = '{{ $lightSyntax.Permalink }}';
+ syntaxTheme.href = currentTheme === 'dark' ? '{{ $darkSyntax.Permalink }}' : '{{ $lightSyntax.Permalink }}';
}
const switchTheme = (e) => {
diff --git a/assets/styles/clipboard.scss b/assets/styles/clipboard.scss
new file mode 100644
index 0000000..7989e24
--- /dev/null
+++ b/assets/styles/clipboard.scss
@@ -0,0 +1,47 @@
+.clipboard-button {
+ position: absolute;
+ display: flex;
+ float: right;
+ right: 0;
+ padding: 0.69em;
+ margin: 0.5em;
+ color: var(--outlinegray);
+ border-color: var(--dark);
+ background-color: var(--lightgray);
+ filter: contrast(1.1);
+ border: 2px solid;
+ border-radius: 6px;
+ font-size: 0.8em;
+ z-index: 1;
+ opacity: 0;
+ transition: 0.12s;
+
+ & > svg {
+ fill: var(--light);
+ filter: contrast(0.3);
+ }
+
+ &:hover {
+ cursor: pointer;
+ border-color: var(--primary);
+
+ & > svg {
+ fill: var(--primary);
+ }
+ }
+
+ &:focus {
+ outline: 0;
+ }
+}
+
+.highlight {
+ position: relative;
+
+ &:hover > .clipboard-button {
+ opacity: 1;
+ transition: 0.2s;
+ }
+}
+
+
diff --git a/content/notes/config.md b/content/notes/config.md
index 076857e..056ae36 100644
--- a/content/notes/config.md
+++ b/content/notes/config.md
@@ -28,12 +28,15 @@
# whether to render titles for code blocks
enableCodeBlockTitle: true
+# whether to render copy buttons for code blocks
+enableCodeBlockCopy: true
+
# whether to try to process Latex
enableLatex: true
# whether to enable single-page-app style rendering
-# this prevents flahses of unstyled content and overall improves
-# smoothness of quartz. More info in issue #109 on GitHub
+# this prevents flashes of unstyled content and improves
+# smoothness of Quartz. More info in issue #109 on GitHub
enableSPA: true
# whether to render a footer
@@ -83,10 +86,10 @@
```
**Note** that if `{title=<my-title>}` is included, and code block titles are not
-enabled, no errors will occur and the title attribute will be ignored.
+enabled, no errors will occur, and the title attribute will be ignored.
### HTML Favicons
-If you would like to customize the favicons of your quartz-based website, you
+If you would like to customize the favicons of your Quartz-based website, you
can add them to the `data/config.yaml` file. The **default** without any set
`favicon` key is:
@@ -95,7 +98,7 @@
```
The default can be overridden by defining a value to the `favicon` key in your
-`data/config.yaml` file. Here is a `List[Dictionary]` example format, which is
+`data/config.yaml` file. For example, here is a `List[Dictionary]` example format, which is
equivalent to the default:
```yaml {title="data/config.yaml", linenos=false}
@@ -108,7 +111,7 @@
If you plan to add multiple favicons generated by a website (see list below), it
may be easier to define it as HTML. Here is an example which appends the
-**Apple touch icon** to quartz's default favicon:
+**Apple touch icon** to Quartz's default favicon:
```yaml {title="data/config.yaml", linenos=false}
favicon: |
@@ -118,7 +121,7 @@
This second favicon will now be used as a web page icon when someone adds your
webpage to the home screen of their Apple device. If you are interested in more
-information about the current, and past, standards of favicons, you can read
+information about the current and past standards of favicons, you can read
[this article](https://www.emergeinteractive.com/insights/detail/the-essentials-of-favicons/).
**Note** that all generated favicon paths, defined by the `href`
@@ -136,7 +139,7 @@
### Local Graph ###
localGraph:
- # whether automatically generate a legend
+ # whether automatically generate a legend
enableLegend: false
# whether to allow dragging nodes in the graph
@@ -181,7 +184,7 @@
Want to go even more in-depth? You can add custom CSS styling and change existing colours through editing `assets/styles/custom.scss`. If you'd like to target specific parts of the site, you can add ids and classes to the HTML partials in `/layouts/partials`.
### Partials
-Partials are what dictate what actually gets rendered to the page. Want to change how pages are styled and structured? You can edit the appropriate layout in `/layouts`.
+Partials are what dictate what gets rendered to the page. Want to change how pages are styled and structured? You can edit the appropriate layout in `/layouts`.
For example, the structure of the home page can be edited through `/layouts/index.html`. To customize the footer, you can edit `/layouts/partials/footer.html`
diff --git a/data/config.yaml b/data/config.yaml
index e55035a..7ef35a5 100644
--- a/data/config.yaml
+++ b/data/config.yaml
@@ -4,6 +4,7 @@
enableLinkPreview: true
enableLatex: true
enableCodeBlockTitle: true
+enableCodeBlockCopy: true
enableSPA: true
enableFooter: true
enableContextualBacklinks: true
diff --git a/layouts/partials/head.html b/layouts/partials/head.html
index e3eebbf..7dfbd50 100644
--- a/layouts/partials/head.html
+++ b/layouts/partials/head.html
@@ -54,6 +54,13 @@
<script src="{{$codeTitle.Permalink}}"></script>
{{end}}
+ {{ if $.Site.Data.config.enableCodeBlockCopy }}
+ {{ $clipboard := resources.Get "js/clipboard.js" | resources.Fingerprint "md5" | resources.Minify }}
+ {{ if (findRE "<pre" .Content 1) }}
+ <script src="{{$clipboard.Permalink}}"></script>
+ {{ end }}
+ {{ end }}
+
<!-- Preload page vars -->
{{$linkIndex := resources.Get "indices/linkIndex.json" | resources.Fingerprint
"md5" | resources.Minify | }} {{$contentIndex := resources.Get
@@ -85,6 +92,10 @@
const pathWindow = window.location.pathname;
const isHome = pathBase == pathWindow;
+ {{if $.Site.Data.config.enableCodeBlockCopy -}}
+ addCopyButtons();
+ {{ end }}
+
{{if $.Site.Data.config.enableSPA -}}
addTitleToCodeBlocks();
{{ end }}
@@ -118,12 +129,12 @@
const init = (doc = document) => {
// NOTE: everything within this callback will be executed for initial page navigation. This is a good place to put JavaScript that only replaces DOM nodes.
+ {{if $.Site.Data.config.enableCodeBlockCopy -}}
+ addCopyButtons();
+ {{ end }}
+
{{if $.Site.Data.config.enableCodeBlockTitle -}}
- {{if $.Site.Data.config.enableSPA -}}
addTitleToCodeBlocks();
- {{ else }}
- window.addEventListener("DOMContentLoaded", addTitleToCodeBlocks);
- {{- end -}}
{{- end -}}
{{if $.Site.Data.config.enableLatex}}
renderMathInElement(doc.body, {
--
Gitblit v1.10.0