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 6ce3b569..e38890b3 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" . }}