Copy to clipboard feature for code block (#152)
Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>
This commit is contained in:
		
							parent
							
								
									015ed4cfa2
								
							
						
					
					
						commit
						f54df35767
					
				
					 6 changed files with 114 additions and 14 deletions
				
			
		
							
								
								
									
										38
									
								
								assets/js/clipboard.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								assets/js/clipboard.js
									
										
									
									
									
										Normal 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)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -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) => {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										47
									
								
								assets/styles/clipboard.scss
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								assets/styles/clipboard.scss
									
										
									
									
									
										Normal 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;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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` 
 | 
				
			||||||
| 
						 | 
					@ -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`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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, {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue