From e1eeca1899f248918a441232a7dea96c8443eafa Mon Sep 17 00:00:00 2001 From: XhmikosR Date: Sun, 27 Nov 2022 17:34:33 +0200 Subject: [PATCH 1/4] Add copy support Note that I explicitly trim the text otherwise Firefox adds a trailing EOF line feed. --- package-lock.json | 39 ++++++++++++++++++++++ package.json | 1 + src/assets/js/application.js | 53 ++++++++++++++++++++++++++++++ src/assets/scss/_clipboard-js.scss | 35 ++++++++++++++++++++ src/assets/scss/style.scss | 1 + src/layouts/partials/icons.html | 9 ++++- src/layouts/partials/scripts.html | 11 +++++-- 7 files changed, 146 insertions(+), 3 deletions(-) create mode 100644 src/assets/js/application.js create mode 100644 src/assets/scss/_clipboard-js.scss diff --git a/package-lock.json b/package-lock.json index 039a5f659..4eaacfccc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "devDependencies": { "autoprefixer": "^10.4.14", "bootstrap": "5.3.0-alpha1", + "clipboard": "^2.0.11", "cross-env": "^7.0.3", "eslint": "^8.36.0", "find-unused-sass-variables": "^4.0.5", @@ -1173,6 +1174,17 @@ "node": ">= 6" } }, + "node_modules/clipboard": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.11.tgz", + "integrity": "sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==", + "dev": true, + "dependencies": { + "good-listener": "^1.2.2", + "select": "^1.1.2", + "tiny-emitter": "^2.0.0" + } + }, "node_modules/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -1573,6 +1585,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delegate": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", + "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==", + "dev": true + }, "node_modules/dependency-graph": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz", @@ -2624,6 +2642,15 @@ "integrity": "sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==", "dev": true }, + "node_modules/good-listener": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz", + "integrity": "sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==", + "dev": true, + "dependencies": { + "delegate": "^3.1.2" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -5440,6 +5467,12 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, + "node_modules/select": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", + "integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==", + "dev": true + }, "node_modules/semver": { "version": "7.3.8", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", @@ -6525,6 +6558,12 @@ "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", "dev": true }, + "node_modules/tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==", + "dev": true + }, "node_modules/to-buffer": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", diff --git a/package.json b/package.json index 9fc633348..fae692e1d 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "devDependencies": { "autoprefixer": "^10.4.14", "bootstrap": "5.3.0-alpha1", + "clipboard": "^2.0.11", "cross-env": "^7.0.3", "eslint": "^8.36.0", "find-unused-sass-variables": "^4.0.5", diff --git a/src/assets/js/application.js b/src/assets/js/application.js new file mode 100644 index 000000000..f3c931675 --- /dev/null +++ b/src/assets/js/application.js @@ -0,0 +1,53 @@ +/* eslint-env browser */ + +/* global ClipboardJS:false */ + +import ClipboardJS from 'clipboard'; + +const btnHtml = [ +'
', + '', +'
'].join(''); + +document.querySelectorAll('div.highlight') + .forEach((element) => { + element.insertAdjacentHTML('beforebegin', btnHtml) + }) + +const clipboard = new ClipboardJS('.btn-clipboard', { + target: trigger => trigger.parentNode.nextElementSibling, + text: trigger => trigger.parentNode.nextElementSibling.textContent.trimEnd() +}) + +clipboard.on('success', (event) => { + const iconFirstChild = event.trigger.querySelector('.bi').firstElementChild + const namespace = 'http://www.w3.org/1999/xlink' + const originalXhref = iconFirstChild.getAttributeNS(namespace, 'href') + const originalTitle = event.trigger.title + + event.clearSelection() + iconFirstChild.setAttributeNS(namespace, 'href', originalXhref.replace('clipboard', 'check2')) + event.trigger.title = 'Copied!' + + setTimeout(() => { + iconFirstChild.setAttributeNS(namespace, 'href', originalXhref) + event.trigger.title = originalTitle + }, 2000) +}) + +/*clipboard.on('error', () => { + const modifierKey = /mac/i.test(navigator.userAgent) ? '\u2318' : 'Ctrl-' + const fallbackMsg = 'Press ' + modifierKey + 'C to copy' + const errorElement = document.getElementById('copy-error-callout') + + if (!errorElement) { + return + } + + errorElement.classList.remove('d-none') + errorElement.insertAdjacentHTML('afterbegin', fallbackMsg) +})*/ diff --git a/src/assets/scss/_clipboard-js.scss b/src/assets/scss/_clipboard-js.scss new file mode 100644 index 000000000..5d4cbba44 --- /dev/null +++ b/src/assets/scss/_clipboard-js.scss @@ -0,0 +1,35 @@ +// clipboard.js +// +// JS-based `Copy` buttons for code snippets. + +.bd-clipboard { + position: relative; + display: none; + float: right; + + + .highlight { + margin-top: 0; + } + + @media (min-width: 768px) { + display: block; + } +} + +.btn-clipboard { + position: absolute; + top: .75em; + right: .5em; + z-index: 10; + display: block; + padding: .5em .75em .625em; + line-height: 1; + color: var(--bs-gray-900); + background-color: var(--bs-gray-100); + border: 0; + border-radius: .25rem; + + &:hover { + color: var(--bs-primary); + } +} diff --git a/src/assets/scss/style.scss b/src/assets/scss/style.scss index 6afe46dcb..23972b65b 100644 --- a/src/assets/scss/style.scss +++ b/src/assets/scss/style.scss @@ -4,6 +4,7 @@ @import "variables"; @import "ads"; @import "buttons"; +@import "clipboard-js"; @import "footer"; @import "navbar"; @import "pagination"; diff --git a/src/layouts/partials/icons.html b/src/layouts/partials/icons.html index 722a8ab8b..46b6753ab 100644 --- a/src/layouts/partials/icons.html +++ b/src/layouts/partials/icons.html @@ -1,7 +1,14 @@ - + + + + + + + + diff --git a/src/layouts/partials/scripts.html b/src/layouts/partials/scripts.html index 6cb54836c..cce276a89 100644 --- a/src/layouts/partials/scripts.html +++ b/src/layouts/partials/scripts.html @@ -1,11 +1,18 @@ {{- $bootstrapJs := resources.Get "/js/bootstrap.bundle.min.js" | resources.Copy "/assets/js/vendor/bootstrap.bundle.min.js" -}} -{{- $esbuildOptions := dict "targetPath" "/assets/js/lazyload.js" "target" "es2019" -}} +{{- $esbuildOptions := dict "target" "es2019" -}} +{{- $lazyloadOptions := dict "targetPath" "/assets/js/lazyload.js" -}} +{{- $appOptions := dict "targetPath" "/assets/js/application.js" -}} {{- if eq hugo.Environment "production" -}} {{- $esbuildOptions = merge $esbuildOptions (dict "minify" "true") -}} {{- end -}} -{{- $lazyload := resources.Get "js/lazyload.js" | js.Build $esbuildOptions }} +{{- $lazyloadOptions = merge $esbuildOptions $lazyloadOptions -}} +{{- $appOptions = merge $esbuildOptions $appOptions -}} +{{- $lazyload := resources.Get "js/lazyload.js" | js.Build $lazyloadOptions -}} +{{- $application := resources.Get "js/application.js" | js.Build $appOptions -}} + + From bbb5c17bb9e63cadddfd551a2b939f3c1c8cbc86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20D=C3=A9ramond?= Date: Thu, 1 Dec 2022 18:16:22 +0100 Subject: [PATCH 2/4] Implement the tooltip for clipboard feature --- src/assets/js/application.js | 38 ++++++++++++++++++++------------- src/assets/scss/_bootstrap.scss | 2 +- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/assets/js/application.js b/src/assets/js/application.js index f3c931675..496a4bb9f 100644 --- a/src/assets/js/application.js +++ b/src/assets/js/application.js @@ -4,12 +4,12 @@ import ClipboardJS from 'clipboard'; +const btnTitle = 'Copy to clipboard' + const btnHtml = [ '
', - '', '
'].join(''); @@ -18,6 +18,12 @@ document.querySelectorAll('div.highlight') element.insertAdjacentHTML('beforebegin', btnHtml) }) +window.addEventListener('load', () => { + document.querySelectorAll('.btn-clipboard').forEach(btn => { + bootstrap.Tooltip.getOrCreateInstance(btn, { btnTitle }) + }) +}) + const clipboard = new ClipboardJS('.btn-clipboard', { target: trigger => trigger.parentNode.nextElementSibling, text: trigger => trigger.parentNode.nextElementSibling.textContent.trimEnd() @@ -25,13 +31,17 @@ const clipboard = new ClipboardJS('.btn-clipboard', { clipboard.on('success', (event) => { const iconFirstChild = event.trigger.querySelector('.bi').firstElementChild + const tooltipBtn = bootstrap.Tooltip.getInstance(event.trigger) const namespace = 'http://www.w3.org/1999/xlink' const originalXhref = iconFirstChild.getAttributeNS(namespace, 'href') const originalTitle = event.trigger.title + tooltipBtn.setContent({ '.tooltip-inner': 'Copied!' }) + event.trigger.addEventListener('hidden.bs.tooltip', () => { + tooltipBtn.setContent({ '.tooltip-inner': btnTitle }) + }, { once: true }) event.clearSelection() iconFirstChild.setAttributeNS(namespace, 'href', originalXhref.replace('clipboard', 'check2')) - event.trigger.title = 'Copied!' setTimeout(() => { iconFirstChild.setAttributeNS(namespace, 'href', originalXhref) @@ -39,15 +49,13 @@ clipboard.on('success', (event) => { }, 2000) }) -/*clipboard.on('error', () => { +clipboard.on('error', event => { const modifierKey = /mac/i.test(navigator.userAgent) ? '\u2318' : 'Ctrl-' - const fallbackMsg = 'Press ' + modifierKey + 'C to copy' - const errorElement = document.getElementById('copy-error-callout') - - if (!errorElement) { - return - } + const fallbackMsg = `Press ${modifierKey}C to copy` + const tooltipBtn = bootstrap.Tooltip.getInstance(event.trigger) - errorElement.classList.remove('d-none') - errorElement.insertAdjacentHTML('afterbegin', fallbackMsg) -})*/ + tooltipBtn.setContent({ '.tooltip-inner': fallbackMsg }) + event.trigger.addEventListener('hidden.bs.tooltip', () => { + tooltipBtn.setContent({ '.tooltip-inner': btnTitle }) + }, { once: true }) +}) diff --git a/src/assets/scss/_bootstrap.scss b/src/assets/scss/_bootstrap.scss index 013626f1f..415cafc19 100644 --- a/src/assets/scss/_bootstrap.scss +++ b/src/assets/scss/_bootstrap.scss @@ -31,7 +31,7 @@ @import "bootstrap/close"; // @import "bootstrap/toasts"; // @import "bootstrap/modal"; -// @import "bootstrap/tooltip"; +@import "bootstrap/tooltip"; // @import "bootstrap/popover"; // @import "bootstrap/carousel"; // @import "bootstrap/spinners"; From 85fd7725dd3a3c8ca293e93a6bc7cf4797646115 Mon Sep 17 00:00:00 2001 From: XhmikosR Date: Sun, 4 Dec 2022 06:51:03 +0200 Subject: [PATCH 3/4] Lint --- src/assets/js/application.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/assets/js/application.js b/src/assets/js/application.js index 496a4bb9f..f1aa74612 100644 --- a/src/assets/js/application.js +++ b/src/assets/js/application.js @@ -1,17 +1,15 @@ -/* eslint-env browser */ +/* global bootstrap:false */ -/* global ClipboardJS:false */ - -import ClipboardJS from 'clipboard'; +import ClipboardJS from 'clipboard' const btnTitle = 'Copy to clipboard' const btnHtml = [ '
', - '', -'
'].join(''); +''].join('') document.querySelectorAll('div.highlight') .forEach((element) => { From bff52b8791af252dfbfcbaeae51144f85310f471 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20D=C3=A9ramond?= Date: Thu, 16 Mar 2023 21:40:17 +0100 Subject: [PATCH 4/4] Make it work with dark/light mode --- src/assets/scss/_clipboard-js.scss | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/assets/scss/_clipboard-js.scss b/src/assets/scss/_clipboard-js.scss index 5d4cbba44..2ae8fa5b6 100644 --- a/src/assets/scss/_clipboard-js.scss +++ b/src/assets/scss/_clipboard-js.scss @@ -24,12 +24,13 @@ display: block; padding: .5em .75em .625em; line-height: 1; - color: var(--bs-gray-900); - background-color: var(--bs-gray-100); + color: var(--bs-body-color); + background-color: var(--bd-pre-bg); + border: 0; border-radius: .25rem; &:hover { - color: var(--bs-primary); + color: var(--bs-link-hover-color); } }