From 458f0c60c4682d75a4c56b105884d4c38baad178 Mon Sep 17 00:00:00 2001 From: leoli Date: Wed, 3 Jul 2024 15:30:33 +0800 Subject: [PATCH] add code copy button to highlight --- assets/js/code.js | 66 +++++++++++++++++++++++++ assets/scss/copy-button.scss | 52 +++++++++++++++++++ assets/scss/main.scss | 1 + exampleSite/config/_default/params.toml | 5 ++ layouts/partials/essentials/head.html | 15 ++++++ 5 files changed, 139 insertions(+) create mode 100644 assets/js/code.js create mode 100644 assets/scss/copy-button.scss diff --git a/assets/js/code.js b/assets/js/code.js new file mode 100644 index 00000000..b7536705 --- /dev/null +++ b/assets/js/code.js @@ -0,0 +1,66 @@ +var scriptBundle = document.getElementById('script-bundle'); +var copyText = scriptBundle && scriptBundle.getAttribute('data-copy') ? scriptBundle.getAttribute('data-copy') : 'Copy'; +var copiedText = scriptBundle && scriptBundle.getAttribute('data-copied') ? scriptBundle.getAttribute('data-copied') : 'Copied'; + +function createCopyButton(highlightDiv) { + const button = document.createElement('button'); + button.className = 'copy-button'; + button.type = 'button'; + button.ariaLabel = copyText; + button.innerText = copyText; + button.addEventListener('click', () => copyCodeToClipboard(button, highlightDiv)); + addCopyButtonToDom(button, highlightDiv); +} + +async function copyCodeToClipboard(button, highlightDiv) { + const codeToCopy = highlightDiv.querySelector(':last-child').innerText; + try { + result = await navigator.permissions.query({ name: 'clipboard-write' }); + if (result.state == 'granted' || result.state == 'prompt') { + await navigator.clipboard.writeText(codeToCopy); + } else { + copyCodeBlockExecCommand(codeToCopy, highlightDiv); + } + } catch (_) { + copyCodeBlockExecCommand(codeToCopy, highlightDiv); + } finally { + codeWasCopied(button); + } +} + +function copyCodeBlockExecCommand(codeToCopy, highlightDiv) { + const textArea = document.createElement('textArea'); + textArea.contentEditable = 'true'; + textArea.readOnly = 'false'; + textArea.className = 'copy-textarea'; + textArea.value = codeToCopy; + highlightDiv.insertBefore(textArea, highlightDiv.firstChild); + const range = document.createRange(); + range.selectNodeContents(textArea); + const sel = window.getSelection(); + sel.removeAllRanges(); + sel.addRange(range); + textArea.setSelectionRange(0, 999999); + document.execCommand('copy'); + highlightDiv.removeChild(textArea); +} + +function codeWasCopied(button) { + button.blur(); + button.innerText = copiedText; + setTimeout(function () { + button.innerText = copyText; + }, 2000); +} + +function addCopyButtonToDom(button, highlightDiv) { + highlightDiv.insertBefore(button, highlightDiv.firstChild); + const wrapper = document.createElement('div'); + wrapper.className = 'highlight-wrapper'; + highlightDiv.parentNode.insertBefore(wrapper, highlightDiv); + wrapper.appendChild(highlightDiv); +} + +window.addEventListener('DOMContentLoaded', (event) => { + document.querySelectorAll('.highlight').forEach((highlightDiv) => createCopyButton(highlightDiv)); +}); diff --git a/assets/scss/copy-button.scss b/assets/scss/copy-button.scss new file mode 100644 index 00000000..883e5dd3 --- /dev/null +++ b/assets/scss/copy-button.scss @@ -0,0 +1,52 @@ +/* Code Copy */ +.highlight-wrapper { + display: block; +} + +.highlight { + position: relative; + z-index: 0; +} + +.highlight:hover > .copy-button { + visibility: visible; +} + +.copy-button { + position: absolute; + top: 0; + right: 0; + z-index: 10; + visibility: invisible; + width: 5rem; /* 20 / 16 = 1.25rem */ + padding: 0.25rem 0; /* Assuming py-1 is 0.25rem */ + font-family: monospace; + font-size: 0.875rem; /* Assuming text-sm is 0.875rem */ + cursor: pointer; + opacity: 0.9; + background-color: #e5e5e5; /* Assuming bg-neutral-200 is #e5e5e5 */ + white-space: nowrap; + border-bottom-left-radius: 0.25rem; /* Assuming rounded-bl-md is 0.25rem */ + border-top-right-radius: 0.25rem; /* Assuming rounded-tr-md is 0.25rem */ + color: #525252; /* Assuming text-neutral-700 is #525252 */ + @media (prefers-color-scheme: dark) { + background-color: #3f3f3f; /* Assuming dark:bg-neutral-600 is #3f3f3f */ + color: #e5e5e5; /* Assuming dark:text-neutral-200 is #e5e5e5 */ + } +} + +.copy-button:hover, +.copy-button:focus, +.copy-button:active, +.copy-button:active:hover { + background-color: #f3e8ff; /* Assuming bg-primary-100 is #f3e8ff */ + @media (prefers-color-scheme: dark) { + background-color: #5b21b6; /* Assuming dark:bg-primary-600 is #5b21b6 */ + } +} + +.copy-textarea { + position: absolute; + opacity: 0.05; + z-index: -10; +} diff --git a/assets/scss/main.scss b/assets/scss/main.scss index 4bbaf781..a5fd8eb5 100755 --- a/assets/scss/main.scss +++ b/assets/scss/main.scss @@ -17,6 +17,7 @@ } @import "search"; +@import "copy-button"; @import "social-share"; @import "gallery-slider"; @import "images"; diff --git a/exampleSite/config/_default/params.toml b/exampleSite/config/_default/params.toml index 09925537..7500e10b 100755 --- a/exampleSite/config/_default/params.toml +++ b/exampleSite/config/_default/params.toml @@ -52,6 +52,11 @@ show_description = true show_tags = true show_categories = true +# highlight +# hugo built-in highlight.js +[highlight] +enableCodeCopy = true + # announcement # announcement module: https://github.com/gethugothemes/hugo-modules/tree/master/components/announcement [announcement] diff --git a/layouts/partials/essentials/head.html b/layouts/partials/essentials/head.html index bb7b1a79..1a5e9498 100755 --- a/layouts/partials/essentials/head.html +++ b/layouts/partials/essentials/head.html @@ -8,6 +8,21 @@ + + +{{ $assets := newScratch }} + +{{ if .Site.Params.highlight.enableCodeCopy | default false }} +{{ $jsCode := resources.Get "js/code.js" }} +{{ $assets.Add "js" (slice $jsCode) }} +{{ end }} + +{{ if $assets.Get "js" }} +{{ $bundleJS := $assets.Get "js" | resources.Concat "js/main.bundle.js" | resources.Minify | resources.Fingerprint "sha512" }} + +{{ end }} + + {{ partialCached "favicon" . }}