Skip to content

Commit

Permalink
Add copy support (#330)
Browse files Browse the repository at this point in the history
* Add copy support

Note that I explicitly trim the text otherwise Firefox adds a trailing EOF line feed.

* Implement the tooltip for clipboard feature

* Lint

* Make it work with dark/light mode

---------

Co-authored-by: Julien Déramond <[email protected]>
  • Loading branch information
XhmikosR and julien-deramond authored Mar 17, 2023
1 parent 44f22fc commit b5475d7
Show file tree
Hide file tree
Showing 8 changed files with 154 additions and 4 deletions.
39 changes: 39 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
59 changes: 59 additions & 0 deletions src/assets/js/application.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/* global bootstrap:false */

import ClipboardJS from 'clipboard'

const btnTitle = 'Copy to clipboard'

const btnHtml = [
'<div class="bd-clipboard">',
`<button type="button" class="btn-clipboard" title="${btnTitle}">`,
'<svg class="bi" role="img" aria-label="Copy"><use xlink:href="#clipboard"/></svg>',
'</button>',
'</div>'].join('')

document.querySelectorAll('div.highlight')
.forEach((element) => {
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()
})

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'))

setTimeout(() => {
iconFirstChild.setAttributeNS(namespace, 'href', originalXhref)
event.trigger.title = originalTitle
}, 2000)
})

clipboard.on('error', event => {
const modifierKey = /mac/i.test(navigator.userAgent) ? '\u2318' : 'Ctrl-'
const fallbackMsg = `Press ${modifierKey}C to copy`
const tooltipBtn = bootstrap.Tooltip.getInstance(event.trigger)

tooltipBtn.setContent({ '.tooltip-inner': fallbackMsg })
event.trigger.addEventListener('hidden.bs.tooltip', () => {
tooltipBtn.setContent({ '.tooltip-inner': btnTitle })
}, { once: true })
})
2 changes: 1 addition & 1 deletion src/assets/scss/_bootstrap.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
36 changes: 36 additions & 0 deletions src/assets/scss/_clipboard-js.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// 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-body-color);
background-color: var(--bd-pre-bg);

border: 0;
border-radius: .25rem;

&:hover {
color: var(--bs-link-hover-color);
}
}
1 change: 1 addition & 0 deletions src/assets/scss/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
@import "variables";
@import "ads";
@import "buttons";
@import "clipboard-js";
@import "footer";
@import "navbar";
@import "pagination";
Expand Down
9 changes: 8 additions & 1 deletion src/layouts/partials/icons.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<svg xmlns="http://www.w3.org/2000/svg" style="display: none">
<symbol id="archive" viewBox="0 0 16 16">
<path d="M0 2a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1v7.5a2.5 2.5 0 0 1-2.5 2.5h-9A2.5 2.5 0 0 1 1 12.5V5a1 1 0 0 1-1-1V2zm2 3v7.5A1.5 1.5 0 0 0 3.5 14h9a1.5 1.5 0 0 0 1.5-1.5V5H2zm13-3H1v2h14V2zM5 7.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5z"/>
</symbol>
<symbol id="check2" viewBox="0 0 16 16">
<path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z"/>
</symbol>
<symbol id="clipboard" viewBox="0 0 16 16">
<path d="M4 1.5H3a2 2 0 0 0-2 2V14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3.5a2 2 0 0 0-2-2h-1v1h1a1 1 0 0 1 1 1V14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3.5a1 1 0 0 1 1-1h1v-1z"/>
<path d="M9.5 1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5h3zm-3-1A1.5 1.5 0 0 0 5 1.5v1A1.5 1.5 0 0 0 6.5 4h3A1.5 1.5 0 0 0 11 2.5v-1A1.5 1.5 0 0 0 9.5 0h-3z"/>
</symbol>
<symbol id="file-earmark-richtext" viewBox="0 0 16 16">
<path d="M14 4.5V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h5.5L14 4.5zm-3 0A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4.5h-2z"/>
<path d="M4.5 12.5A.5.5 0 0 1 5 12h3a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm0-2A.5.5 0 0 1 5 10h6a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm1.639-3.708 1.33.886 1.854-1.855a.25.25 0 0 1 .289-.047l1.888.974V8.5a.5.5 0 0 1-.5.5H5a.5.5 0 0 1-.5-.5V8s1.54-1.274 1.639-1.208zM6.25 6a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5z"/>
Expand Down
11 changes: 9 additions & 2 deletions src/layouts/partials/scripts.html
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
{{- $bootstrapJs := resources.Get "/js/bootstrap.bundle.min.js" | resources.Copy "/assets/js/vendor/bootstrap.bundle.min.js" -}}
<script async src="{{ $bootstrapJs.Permalink | relURL }}"></script>

{{- $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 -}}

<script async src="{{ $lazyload.RelPermalink | relURL }}"></script>
<script async src="{{ $application.RelPermalink | relURL }}"></script>

0 comments on commit b5475d7

Please sign in to comment.