From 1a66a0d7ae045716290efc957cf8177f07bd9d52 Mon Sep 17 00:00:00 2001 From: Dan Allen Date: Thu, 10 Dec 2020 17:57:57 -0700 Subject: [PATCH] add copy to clipboard button to source blocks * add subsetted SVG icon sprite for octicons * add copy to clipboard button to source blocks (visible on hover) * replace pseudo-element for source language with a toolbox (managed by JavaScript) * autodetect literal block with command and promote to a console source block * intelligently extract commands from console source block, flatten them, and join them in a chain * prevent command prompt(s) in console source block from being selected * darken color for annotation on source block * configure svgo to preserve desc element * configure svgo to preserve ID prefixes for icons --- gulp.d/tasks/build.js | 32 ++++++++------- preview-src/index.adoc | 9 ++++ src/css/doc.css | 91 ++++++++++++++++++++++++++++++++++++----- src/css/vars.css | 2 +- src/img/octicons.svg | 36 ++++++++++++++++ src/js/06-copy-to-clipboard.js | 66 ++++++++++++++++++++++++++++++ src/partials/footer-scripts.hbs | 1 + 7 files changed, 212 insertions(+), 25 deletions(-) create mode 100644 src/img/octicons.svg create mode 100644 src/js/06-copy-to-clipboard.js diff --git a/gulp.d/tasks/build.js b/gulp.d/tasks/build.js index c4c2795..64642ad 100644 --- a/gulp.d/tasks/build.js +++ b/gulp.d/tasks/build.js @@ -100,20 +100,24 @@ module.exports = (src, dest, preview) => () => { .src(['css/site.css', 'css/vendor/*.css'], { ...opts, sourcemaps }) .pipe(postcss((file) => ({ plugins: postcssPlugins, options: { file } }))), vfs.src('font/*.{ttf,woff*(2)}', opts), - vfs - .src('img/**/*.{gif,ico,jpg,png,svg}', opts) - .pipe( - preview - ? through() - : imagemin( - [ - imagemin.gifsicle(), - imagemin.jpegtran(), - imagemin.optipng(), - imagemin.svgo({ plugins: [{ removeViewBox: false }] }), - ].reduce((accum, it) => (it ? accum.concat(it) : accum), []) - ) - ), + vfs.src('img/**/*.{gif,ico,jpg,png,svg}', opts).pipe( + preview + ? through() + : imagemin( + [ + imagemin.gifsicle(), + imagemin.jpegtran(), + imagemin.optipng(), + imagemin.svgo({ + plugins: [ + { cleanupIDs: { preservePrefixes: ['symbol-', 'view-'] } }, + { removeViewBox: false }, + { removeDesc: false }, + ], + }), + ].reduce((accum, it) => (it ? accum.concat(it) : accum), []) + ) + ), vfs.src('helpers/*.js', opts), vfs.src('layouts/*.hbs', opts), vfs.src('partials/*.hbs', opts) diff --git a/preview-src/index.adoc b/preview-src/index.adoc index 3759146..bc52e5f 100644 --- a/preview-src/index.adoc +++ b/preview-src/index.adoc @@ -69,6 +69,15 @@ vfs <2> Wrap each streaming file in a buffer so the files can be processed by uglify. Uglify can only work with buffers, not streams. +Execute these commands to validate and build your site: + + $ podman run -v $PWD:/antora:Z --rm -t antora/antora \ + version + 3.0.0 + $ podman run -v $PWD:/antora:Z --rm -it antora/antora \ + --clean \ + antora-playbook.yml + Cum dicat #putant# ne. Est in <> homero principes, meis deleniti mediocrem ad has. Altera atomorum his ex, has cu elitr melius propriae. diff --git a/src/css/doc.css b/src/css/doc.css index caa6fd1..3c27ae5 100644 --- a/src/css/doc.css +++ b/src/css/doc.css @@ -561,26 +561,97 @@ padding: 0.75rem; } -/* NOTE assume pre.highlight contains code[data-lang] */ .doc pre.highlight { position: relative; } -.doc .listingblock code[data-lang]::before { - content: attr(data-lang); - display: none; +.doc .language-console .hljs-meta { + user-select: none; +} + +.doc .source-toolbox { + display: flex; + visibility: hidden; + position: absolute; + top: 0.25rem; + right: 0.5rem; color: var(--pre-annotation-font-color); + font-family: var(--body-font-family); font-size: calc(13.5 / var(--rem-base) * 1rem); - letter-spacing: 0.05em; line-height: 1; +} + +.doc .listingblock:hover .source-toolbox { + visibility: visible; +} + +.doc .source-toolbox .source-lang { text-transform: uppercase; + letter-spacing: 0.075em; + font-size: 0.96em; + line-height: 1.0425; +} + +.doc .source-toolbox > :not(:last-child)::after { + content: "|"; + letter-spacing: 0; + padding: 0 1ch; +} + +.doc .source-toolbox .copy-button { + display: flex; + flex-direction: column; + align-items: center; + background: transparent; + border: none; + color: inherit; + outline: none; + padding: 0; + font-size: inherit; + line-height: inherit; + width: 1em; + height: 1em; +} + +.source-toolbox .copy-button * { + flex: none; +} + +.source-toolbox .copy-button svg { + fill: currentColor; + width: inherit; + height: inherit; +} + +.source-toolbox .copy-toast { + position: relative; + display: inline-flex; + justify-content: center; + margin-top: 1em; + background-color: var(--doc-font-color); + border-radius: 0.25em; + padding: 0.5em; + color: var(--color-white); + cursor: auto; + opacity: 0; + transition: opacity 0.5s ease 0.75s; +} + +.source-toolbox .copy-toast::after { + content: ""; position: absolute; - top: 0.25rem; - right: 0.25rem; + top: 0; + width: 1em; + height: 1em; + border: 0.55em solid transparent; + border-left-color: var(--doc-font-color); + transform: rotate(-90deg) translateX(50%) translateY(50%); + transform-origin: left; } -.doc .listingblock:hover code[data-lang]::before { - display: block; +.source-toolbox .copy-button.clicked .copy-toast { + opacity: 1; + transition: none; } .doc .hdlist1, @@ -629,10 +700,10 @@ font-family: var(--body-font-family); font-size: calc(13.5 / var(--rem-base) * 1rem); font-style: normal; - height: 1.25em; line-height: 1.2; text-align: center; width: 1.25em; + height: 1.25em; letter-spacing: -0.25ex; text-indent: -0.25ex; } diff --git a/src/css/vars.css b/src/css/vars.css index 888cea8..56d5454 100644 --- a/src/css/vars.css +++ b/src/css/vars.css @@ -103,7 +103,7 @@ --kbd-border-color: var(--color-gray-10); --pre-background: var(--panel-background); --pre-border-color: var(--panel-border-color); - --pre-annotation-font-color: var(--color-gray-10); + --pre-annotation-font-color: var(--color-gray-50); --quote-background: var(--panel-background); --quote-border-color: var(--color-gray-70); --quote-font-color: var(--color-gray-70); diff --git a/src/img/octicons.svg b/src/img/octicons.svg new file mode 100644 index 0000000..3503b58 --- /dev/null +++ b/src/img/octicons.svg @@ -0,0 +1,36 @@ + + Octicons (subset) + Octicons v11.2.0 by GitHub - https://primer.style/octicons/ - License: MIT + + + + @primer/octicons + 11.2.0 + A scalable set of icons handcrafted with <3 by GitHub + image/svg+xml + + + GitHub + + + + + Copyright (c) 2020 GitHub Inc. + + + + https://primer.style/octicons/ + + + + + + + + + diff --git a/src/js/06-copy-to-clipboard.js b/src/js/06-copy-to-clipboard.js new file mode 100644 index 0000000..4f4b802 --- /dev/null +++ b/src/js/06-copy-to-clipboard.js @@ -0,0 +1,66 @@ +;(function () { + 'use strict' + ;[].slice.call(document.querySelectorAll('.doc pre.highlight, .doc .literalblock pre')).forEach(function (pre) { + var code, language, lang, copy, toast, toolbox + if (pre.classList.contains('highlight')) { + code = pre.querySelector('code') + if ((language = code.dataset.lang) && language !== 'console') { + ;(lang = document.createElement('span')).className = 'source-lang' + lang.appendChild(document.createTextNode(language)) + } + } else if (pre.innerText.startsWith('$ ')) { + var block = pre.parentNode.parentNode + block.classList.remove('literalblock') + block.classList.add('listingblock') + pre.classList.add('highlightjs') + pre.classList.add('highlight') + ;(code = document.createElement('code')).className = 'language-console hljs' + code.dataset.lang = 'console' + code.appendChild(pre.firstChild) + pre.appendChild(code) + } else { + return + } + ;(toolbox = document.createElement('div')).className = 'source-toolbox' + if (lang) toolbox.appendChild(lang) + if (window.navigator.clipboard) { + ;(copy = document.createElement('button')).className = 'copy-button' + copy.setAttribute('title', 'Copy to clipboard') + var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg') + svg.setAttribute('aria-hidden', 'true') + svg.setAttribute('class', 'copy-icon') + var use = document.createElementNS('http://www.w3.org/2000/svg', 'use') + use.setAttribute('href', window.uiRootPath + '/img/octicons.svg#symbol-clippy-16') + svg.appendChild(use) + copy.appendChild(svg) + ;(toast = document.createElement('span')).className = 'copy-toast' + toast.appendChild(document.createTextNode('Copied!')) + copy.appendChild(toast) + toolbox.appendChild(copy) + } + pre.appendChild(toolbox) + if (copy) copy.addEventListener('click', writeToClipboard.bind(copy, code)) + }) + + function extractCommands (text) { + var cmdRx = /^\$ (\S[^\\\n]*(\\\n(?!\$ )[^\\\n]*)*)(?=\n|$)/gm + var cleanupRx = /( )? *\\\n */g + var cmds = [] + var m + while ((m = cmdRx.exec(text))) cmds.push(m[1].replace(cleanupRx, '$1')) + return cmds.join(' && ') + } + + function writeToClipboard (code) { + var text = code.innerText + if (code.dataset.lang === 'console' && text.startsWith('$ ')) text = extractCommands(text) + window.navigator.clipboard.writeText(text).then( + function () { + this.classList.add('clicked') + this.offsetHeight // eslint-disable-line no-unused-expressions + this.classList.remove('clicked') + }.bind(this), + function () {} + ) + } +})() diff --git a/src/partials/footer-scripts.hbs b/src/partials/footer-scripts.hbs index d106cd3..b4f5fff 100644 --- a/src/partials/footer-scripts.hbs +++ b/src/partials/footer-scripts.hbs @@ -1,2 +1,3 @@ + -- 2.11.0