Copy to clipboard feature for code block (#152)

Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>
This commit is contained in:
Geoffrey Garrett 2022-07-03 20:42:35 +02:00 committed by GitHub
parent 015ed4cfa2
commit f54df35767
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 114 additions and 14 deletions

38
assets/js/clipboard.js Normal file
View File

@ -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)
}
}

View File

@ -8,7 +8,7 @@ const syntaxTheme = document.querySelector("#theme-link");
if (currentTheme) { if (currentTheme) {
document.documentElement.setAttribute('saved-theme', 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) => { const switchTheme = (e) => {

View File

@ -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;
}
}

View File

@ -28,12 +28,15 @@ enableLinkPreview: true
# whether to render titles for code blocks # whether to render titles for code blocks
enableCodeBlockTitle: true enableCodeBlockTitle: true
# whether to render copy buttons for code blocks
enableCodeBlockCopy: true
# whether to try to process Latex # whether to try to process Latex
enableLatex: true enableLatex: true
# whether to enable single-page-app style rendering # whether to enable single-page-app style rendering
# this prevents flahses of unstyled content and overall improves # this prevents flashes of unstyled content and improves
# smoothness of quartz. More info in issue #109 on GitHub # smoothness of Quartz. More info in issue #109 on GitHub
enableSPA: true enableSPA: true
# whether to render a footer # whether to render a footer
@ -83,10 +86,10 @@ To add code block titles with Quartz:
``` ```
**Note** that if `{title=<my-title>}` is included, and code block titles are not **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 ### 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 can add them to the `data/config.yaml` file. The **default** without any set
`favicon` key is: `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 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: equivalent to the default:
```yaml {title="data/config.yaml", linenos=false} ```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 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 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} ```yaml {title="data/config.yaml", linenos=false}
favicon: | favicon: |
@ -118,7 +121,7 @@ favicon: |
This second favicon will now be used as a web page icon when someone adds your 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 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/). [this article](https://www.emergeinteractive.com/insights/detail/the-essentials-of-favicons/).
**Note** that all generated favicon paths, defined by the `href` **Note** that all generated favicon paths, defined by the `href`
@ -136,7 +139,7 @@ enableGlobalGraph: false
### Local Graph ### ### Local Graph ###
localGraph: localGraph:
# whether automatically generate a legend # whether automatically generate a legend
enableLegend: false enableLegend: false
# whether to allow dragging nodes in the graph # 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`. 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
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` 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`

View File

@ -4,6 +4,7 @@ openToc: false
enableLinkPreview: true enableLinkPreview: true
enableLatex: true enableLatex: true
enableCodeBlockTitle: true enableCodeBlockTitle: true
enableCodeBlockCopy: true
enableSPA: true enableSPA: true
enableFooter: true enableFooter: true
enableContextualBacklinks: true enableContextualBacklinks: true

View File

@ -54,6 +54,13 @@
<script src="{{$codeTitle.Permalink}}"></script> <script src="{{$codeTitle.Permalink}}"></script>
{{end}} {{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 --> <!-- Preload page vars -->
{{$linkIndex := resources.Get "indices/linkIndex.json" | resources.Fingerprint {{$linkIndex := resources.Get "indices/linkIndex.json" | resources.Fingerprint
"md5" | resources.Minify | }} {{$contentIndex := resources.Get "md5" | resources.Minify | }} {{$contentIndex := resources.Get
@ -85,6 +92,10 @@
const pathWindow = window.location.pathname; const pathWindow = window.location.pathname;
const isHome = pathBase == pathWindow; const isHome = pathBase == pathWindow;
{{if $.Site.Data.config.enableCodeBlockCopy -}}
addCopyButtons();
{{ end }}
{{if $.Site.Data.config.enableSPA -}} {{if $.Site.Data.config.enableSPA -}}
addTitleToCodeBlocks(); addTitleToCodeBlocks();
{{ end }} {{ end }}
@ -118,12 +129,12 @@
const init = (doc = document) => { 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. // 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.enableCodeBlockTitle -}}
{{if $.Site.Data.config.enableSPA -}}
addTitleToCodeBlocks(); addTitleToCodeBlocks();
{{ else }}
window.addEventListener("DOMContentLoaded", addTitleToCodeBlocks);
{{- end -}}
{{- end -}} {{- end -}}
{{if $.Site.Data.config.enableLatex}} {{if $.Site.Data.config.enableLatex}}
renderMathInElement(doc.body, { renderMathInElement(doc.body, {