Geoffrey Garrett
2022-07-03 f54df35767dcda9bc4853decff86d57323593685
Copy to clipboard feature for code block (#152)

Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>
2 files added
4 files modified
128 ■■■■ changed files
assets/js/clipboard.js 38 ●●●●● patch | view | raw | blame | history
assets/js/darkmode.js 2 ●●● patch | view | raw | blame | history
assets/styles/clipboard.scss 47 ●●●●● patch | view | raw | blame | history
content/notes/config.md 21 ●●●●● patch | view | raw | blame | history
data/config.yaml 1 ●●●● patch | view | raw | blame | history
layouts/partials/head.html 19 ●●●● patch | view | raw | blame | history
assets/js/clipboard.js
New 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)
    }
}
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) => {
assets/styles/clipboard.scss
New 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;
  }
}
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`
data/config.yaml
@@ -4,6 +4,7 @@
enableLinkPreview: true
enableLatex: true
enableCodeBlockTitle: true
enableCodeBlockCopy: true
enableSPA: true
enableFooter: true
enableContextualBacklinks: true
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, {