diff --git a/docs/config/README.md b/docs/config/README.md index 1899496557..9054ca31b3 100644 --- a/docs/config/README.md +++ b/docs/config/README.md @@ -197,6 +197,13 @@ module.exports = { } ``` +### markdown.enableCbCopy + +- Type: `boolean` +- Default: `undefined` + +Whether to show icon to copy the code snippet into the clipboard. + ## Build Pipeline ### postcss diff --git a/lib/app/app.js b/lib/app/app.js index 511abbd472..fb0108b764 100644 --- a/lib/app/app.js +++ b/lib/app/app.js @@ -6,6 +6,7 @@ import { routes } from '@temp/routes' import { siteData } from '@temp/siteData' import enhanceApp from '@temp/enhanceApp' import themeEnhanceApp from '@temp/themeEnhanceApp' +import { enableCbCopy } from './clipboardCopy' // generated from user config import('@temp/style.styl') @@ -14,6 +15,7 @@ import('@temp/style.styl') import Content from './components/Content' import OutboundLink from './components/OutboundLink.vue' import ClientOnly from './components/ClientOnly' +import ClipboardCopy from './components/ClipboardCopy.vue' // suggest dev server restart on base change if (module.hot) { @@ -38,6 +40,7 @@ Vue.component('OutboundLink', OutboundLink) Vue.component('Badge', () => import('./components/Badge.vue')) // component for client-only content Vue.component('ClientOnly', ClientOnly) +Vue.component('ClipboardCopy', ClipboardCopy) // global helper for adding base path to absolute urls Vue.prototype.$withBase = function (path) { @@ -82,6 +85,10 @@ export function createApp () { } }) + router.afterEach((to, from) => { + enableCbCopy() + }) + const options = {} themeEnhanceApp({ Vue, options, router, siteData }) diff --git a/lib/app/clipboardCopy.js b/lib/app/clipboardCopy.js new file mode 100644 index 0000000000..e4bbe6be25 --- /dev/null +++ b/lib/app/clipboardCopy.js @@ -0,0 +1,20 @@ + +import { copyToClipboard } from './util' + +const generateCopyButton = (parent) => { + if (parent.classList.contains('codecopy-enabled')) return + + const copyElement = document.createElement('span') + copyElement.className = 'code-copy' + copyElement.title = 'Click to copy to clipboard' + copyElement.addEventListener('click', () => { + copyToClipboard(parent.innerText) + }) + parent.appendChild(copyElement) + parent.classList.add('codecopy-enabled') +} + +export function enableCbCopy () { + const codeBlocks = document.querySelectorAll('div[class*="language-"] pre') + codeBlocks.forEach(generateCopyButton) +} diff --git a/lib/app/components/ClipboardCopy.vue b/lib/app/components/ClipboardCopy.vue new file mode 100644 index 0000000000..ad0a668260 --- /dev/null +++ b/lib/app/components/ClipboardCopy.vue @@ -0,0 +1,23 @@ + + + diff --git a/lib/app/components/copy.svg b/lib/app/components/copy.svg new file mode 100644 index 0000000000..7b49bda364 --- /dev/null +++ b/lib/app/components/copy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lib/app/util.js b/lib/app/util.js index c11172240e..b9cd9ab43f 100644 --- a/lib/app/util.js +++ b/lib/app/util.js @@ -17,3 +17,24 @@ export function findPageForPath (pages, path) { frontmatter: {} } } + +export function copyToClipboard (str) { + const el = document.createElement('textarea') + el.value = str + el.setAttribute('readonly', '') + el.style.position = 'absolute' + el.style.left = '-9999px' + document.body.appendChild(el) + const selected = + document.getSelection().rangeCount > 0 + ? document.getSelection().getRangeAt(0) + : false + el.select() + document.execCommand('copy') + document.body.removeChild(el) + if (selected) { + document.getSelection().removeAllRanges() + document.getSelection().addRange(selected) + } +} +