feat: add copy code element (#473) (#658)

close #473

Co-authored-by: Kia Ishii <kia.king.08@gmail.com>
pull/664/head
Divyansh Singh 3 years ago committed by GitHub
parent 607c3d33f3
commit 6028cacb81
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,5 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { useRoute, useData } from 'vitepress' import { useRoute, useData } from 'vitepress'
import { useCopyCode } from '../composables/copy-code'
import { useSidebar } from '../composables/sidebar' import { useSidebar } from '../composables/sidebar'
import NotFound from '../NotFound.vue' import NotFound from '../NotFound.vue'
import VPPage from './VPPage.vue' import VPPage from './VPPage.vue'
@ -9,6 +10,8 @@ import VPDoc from './VPDoc.vue'
const route = useRoute() const route = useRoute()
const { frontmatter } = useData() const { frontmatter } = useData()
const { hasSidebar } = useSidebar() const { hasSidebar } = useSidebar()
useCopyCode()
</script> </script>
<template> <template>

@ -0,0 +1,48 @@
import { nextTick, watch } from 'vue'
import { inBrowser, useData } from 'vitepress'
export function useCopyCode() {
const { page } = useData()
if (inBrowser)
watch(
() => page.value.relativePath,
() => {
nextTick(() => {
document
.querySelectorAll<HTMLSpanElement>(
'.vp-doc div[class*="language-"]>span.copy'
)
.forEach(handleElement)
})
},
{ immediate: true, flush: 'post' }
)
}
function handleElement(el: HTMLElement) {
el.onclick = () => {
const parent = el.parentElement
if (!parent) {
return
}
const isShell =
parent.classList.contains('language-sh') ||
parent.classList.contains('language-bash')
let { innerText: text = '' } = parent
if (isShell) {
text = text.replace(/^ *\$ /gm, '')
}
navigator.clipboard.writeText(text).then(() => {
el.classList.add('copied')
setTimeout(() => {
el.classList.remove('copied')
}, 3000)
})
}
}

@ -366,6 +366,60 @@
transition: border-color 0.5s, color 0.5s; transition: border-color 0.5s, color 0.5s;
} }
.vp-doc [class*='language-'] > span.copy {
position: absolute;
top: 8px;
right: 8px;
z-index: 2;
display: block;
justify-content: center;
align-items: center;
border-radius: 4px;
width: 40px;
height: 40px;
background-color: var(--vp-code-block-bg);
opacity: 0;
cursor: pointer;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' height='20' width='20' stroke='rgba(235,235,235,0.38)' stroke-width='2' class='h-6 w-6' viewBox='0 0 24 24'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2M9 5a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2M9 5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2'/%3E%3C/svg%3E");
background-position: 50%;
background-size: 20px;
background-repeat: no-repeat;
transition: opacity 0.25s;
}
.vp-doc [class*='language-']:hover > span.copy {
opacity: 1;
}
.vp-doc [class*='language-'] > span.copy:hover {
background-color: var(--vp-code-copy-code-hover-bg);
}
.vp-doc [class*='language-'] > span.copy.copied,
.vp-doc [class*='language-'] > span.copy:hover.copied {
border-radius: 0 4px 4px 0;
background-color: var(--vp-code-copy-code-hover-bg);
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' height='20' width='20' stroke='rgba(235,235,235,0.5)' stroke-width='2' class='h-6 w-6' viewBox='0 0 24 24'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2M9 5a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2M9 5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2m-6 9 2 2 4-4'/%3E%3C/svg%3E");
}
.vp-doc [class*='language-'] > span.copy.copied::before,
.vp-doc [class*='language-'] > span.copy:hover.copied::before {
position: relative;
left: -65px;
display: block;
border-radius: 4px 0 0 4px;
padding-top: 8px;
width: 64px;
height: 40px;
text-align: center;
font-size: 12px;
font-weight: 500;
color: var(--vp-c-text-dark-2);
background-color: var(--vp-code-copy-code-hover-bg);
white-space: nowrap;
content: "Copied";
}
.vp-doc [class*='language-']:before { .vp-doc [class*='language-']:before {
position: absolute; position: absolute;
top: 6px; top: 6px;
@ -374,7 +428,11 @@
font-size: 12px; font-size: 12px;
font-weight: 500; font-weight: 500;
color: var(--vp-c-text-dark-3); color: var(--vp-c-text-dark-3);
transition: color 0.5s; transition: color 0.5s, opacity 0.5s;
}
.vp-doc [class*='language-']:hover:before {
opacity: 0;
} }
.vp-doc [class~='language-c']:before { content: 'c'; } .vp-doc [class~='language-c']:before { content: 'c'; }

@ -197,6 +197,8 @@
--vp-code-line-highlight-color: rgba(0, 0, 0, 0.5); --vp-code-line-highlight-color: rgba(0, 0, 0, 0.5);
--vp-code-line-number-color: var(--vp-c-text-dark-3); --vp-code-line-number-color: var(--vp-c-text-dark-3);
--vp-code-copy-code-hover-bg: rgba(255, 255, 255, 0.05);
} }
.dark { .dark {

@ -15,6 +15,6 @@ export const preWrapperPlugin = (md: MarkdownIt) => {
const [tokens, idx] = args const [tokens, idx] = args
const token = tokens[idx] const token = tokens[idx]
const rawCode = fence(...args) const rawCode = fence(...args)
return `<div class="language-${token.info.trim()}">${rawCode}</div>` return `<div class="language-${token.info.trim()}"><span class="copy" />${rawCode}</div>`
} }
} }

Loading…
Cancel
Save