From f54df35767dcda9bc4853decff86d57323593685 Mon Sep 17 00:00:00 2001 From: Geoffrey Garrett Date: Sun, 3 Jul 2022 20:42:35 +0200 Subject: [PATCH] Copy to clipboard feature for code block (#152) Co-authored-by: Jacky Zhao --- assets/js/clipboard.js | 38 +++++++++++++++++++++++++++++ assets/js/darkmode.js | 2 +- assets/styles/clipboard.scss | 47 ++++++++++++++++++++++++++++++++++++ content/notes/config.md | 21 +++++++++------- data/config.yaml | 1 + layouts/partials/head.html | 19 ++++++++++++--- 6 files changed, 114 insertions(+), 14 deletions(-) create mode 100644 assets/js/clipboard.js create mode 100644 assets/styles/clipboard.scss 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 = + ''; +const svgCheck = + ''; + + +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 @@ const syntaxTheme = document.querySelector("#theme-link"); 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 @@ enableLinkPreview: true # 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 @@ To add code block titles with Quartz: ``` **Note** that if `{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 @@ can add them to the `data/config.yaml` file. The **default** without any set ``` 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 @@ In this format, the keys are identical to their HTML representations. 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 @@ favicon: | 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 @@ enableGlobalGraph: false ### 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 @@ paths: 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 @@ openToc: false 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 @@ {{end}} + {{ if $.Site.Data.config.enableCodeBlockCopy }} + {{ $clipboard := resources.Get "js/clipboard.js" | resources.Fingerprint "md5" | resources.Minify }} + {{ if (findRE " + {{ end }} + {{ end }} + {{$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, {