Skip to content

Commit fc62dc0

Browse files
authored
Merge pull request #22 from selemondev/docs/shiki-copy-button
docs: add Shiki transformer - Copy button
2 parents 1226367 + 8889b75 commit fc62dc0

File tree

8 files changed

+166
-8
lines changed

8 files changed

+166
-8
lines changed

examples/vue3-marquee/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
},
1313
"dependencies": {
1414
"@selemondev/vue3-marquee": "0.0.8",
15+
"hastscript": "^9.0.0",
1516
"vue": "^3.4.15"
1617
},
1718
"devDependencies": {

examples/vue3-marquee/src/App.vue

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ import { installCmd, globalImportSnippet, localImportSnippet, nuxtPluginSnippet,
9292
</div>
9393
<div class="space-y-1">
9494
<h3 class="font-semibold">Code</h3>
95-
<CodeBlock :code="fadeCodeSnippet" lang="html" theme="vitesse-light" />
95+
<CodeBlock :code="fadeCodeSnippet" lang="vue-html" theme="vitesse-light" />
9696
</div>
9797
<hr class="border-stone-200" />
9898
<div class="space-y-1">
@@ -178,6 +178,7 @@ pre {
178178
border-radius: 10px;
179179
overflow: scroll;
180180
-ms-overflow-style: none;
181-
scrollbar-width: none;
181+
scrollbar-width: none;
182+
position: relative;
182183
}
183184
</style>

examples/vue3-marquee/src/components/CodeBlock.vue

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ const props = defineProps<{
99
1010
const codeToHtml = ref('');
1111
12-
watch(props, async (val: { code: string, lang: string, theme: string}) => {
13-
if(val) {
12+
watch(props, async (val: { code: string, lang: string, theme: string }) => {
13+
if (val) {
1414
return codeToHtml.value = await convertCodeToHtml(val.code, val.lang, val.theme)
1515
}
1616
}, {
@@ -19,5 +19,9 @@ watch(props, async (val: { code: string, lang: string, theme: string}) => {
1919
</script>
2020

2121
<template>
22-
<div v-html="codeToHtml"/>
23-
</template>
22+
<div v-html="codeToHtml" />
23+
</template>
24+
25+
<style>
26+
27+
</style>
Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
import { codeToHtml } from "shiki"
2+
import { transformerCopyButton } from "./transformerCopyButton"
23
export const convertCodeToHtml = async (code: string, lang: string, theme: string) => {
34
return await codeToHtml(code, {
45
lang,
5-
theme
6+
theme,
7+
transformers: [
8+
transformerCopyButton({
9+
duration: 3000,
10+
successIcon: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1em' height='1em' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='rgba(128,128,128,1)' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M16 3h2.6A2.4 2.4 0 0 1 21 5.4v15.2a2.4 2.4 0 0 1-2.4 2.4H5.4A2.4 2.4 0 0 1 3 20.6V5.4A2.4 2.4 0 0 1 5.4 3H8m0 11l3 3l5-7M8.8 1h6.4a.8.8 0 0 1 .8.8v2.4a.8.8 0 0 1-.8.8H8.8a.8.8 0 0 1-.8-.8V1.8a.8.8 0 0 1 .8-.8'/%3E%3C/svg%3E",
11+
copyIcon: "data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20fill='none'%20stroke='rgba(128,128,128,1)'%20stroke-linecap='round'%20stroke-linejoin='round'%20stroke-width='2'%20viewBox='0%200%2024%2024'%3E%3Crect%20width='8'%20height='4'%20x='8'%20y='2'%20rx='1'%20ry='1'/%3E%3Cpath%20d='M16%204h2a2%202%200%200%201%202%202v14a2%202%200%200%201-2%202H6a2%202%200%200%201-2-2V6a2%202%200%200%201%202-2h2'/%3E%3C/svg%3E",
12+
})
13+
]
614
})
715
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// Credits - Rehype pretty & JoshNuss
2+
3+
import type { CopyButtonOptions } from "@/types/CopyButtonOptions.interface";
4+
import type { ShikiTransformer } from "shiki";
5+
import { h } from "hastscript";
6+
7+
export const transformerCopyButton = (
8+
options: CopyButtonOptions = {
9+
duration: 3000
10+
}
11+
): ShikiTransformer => {
12+
return {
13+
name: 'shiki-transformer-copy-button',
14+
code(node) {
15+
const button = h('button', {
16+
class: 'shiki-transformer-button-copy',
17+
'data-code': this.source,
18+
onclick: `
19+
navigator.clipboard.writeText(this.dataset.code);
20+
this.classList.add('shiki-transformer-button-copied');
21+
setTimeout(() => this.classList.remove('shiki-transformer-button-copied'), ${options.duration})
22+
`
23+
}, [
24+
h('span', { class: 'ready' }),
25+
h('span', { class: 'success' })
26+
]);
27+
node.children.push(button)
28+
node.children.push({
29+
type: 'element',
30+
tagName: 'style',
31+
properties: {},
32+
children: [
33+
{
34+
type: 'text',
35+
value: buttonStyles({
36+
successIcon: options.successIcon,
37+
copyIcon: options.copyIcon
38+
})
39+
}
40+
]
41+
})
42+
}
43+
}
44+
}
45+
46+
function buttonStyles({
47+
successIcon = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1em' height='1em' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='rgba(128,128,128,1)' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M16 3h2.6A2.4 2.4 0 0 1 21 5.4v15.2a2.4 2.4 0 0 1-2.4 2.4H5.4A2.4 2.4 0 0 1 3 20.6V5.4A2.4 2.4 0 0 1 5.4 3H8m0 11l3 3l5-7M8.8 1h6.4a.8.8 0 0 1 .8.8v2.4a.8.8 0 0 1-.8.8H8.8a.8.8 0 0 1-.8-.8V1.8a.8.8 0 0 1 .8-.8'/%3E%3C/svg%3E",
48+
copyIcon = "data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20fill='none'%20stroke='rgba(128,128,128,1)'%20stroke-linecap='round'%20stroke-linejoin='round'%20stroke-width='2'%20viewBox='0%200%2024%2024'%3E%3Crect%20width='8'%20height='4'%20x='8'%20y='2'%20rx='1'%20ry='1'/%3E%3Cpath%20d='M16%204h2a2%202%200%200%201%202%202v14a2%202%200%200%201-2%202H6a2%202%200%200%201-2-2V6a2%202%200%200%201%202-2h2'/%3E%3C/svg%3E",
49+
}: {
50+
successIcon?: string,
51+
copyIcon?: string
52+
}) {
53+
let buttonStyle =
54+
`
55+
:root {
56+
--border-color: #e2e2e3;
57+
--background-color: #f6f6f7;
58+
--hover-background-color: #ffff
59+
}
60+
61+
pre:has(code) {
62+
position: relative;
63+
}
64+
65+
pre button.shiki-transformer-button-copy {
66+
position: absolute;
67+
top: 12px;
68+
right: 12px;
69+
z-index: 3;
70+
border: 1px solid var(--border-color);
71+
border-radius: 4px;
72+
width: 30px;
73+
height: 30px;
74+
display: flex;
75+
justify-content: center;
76+
place-items: center;
77+
background-color: var(--background-color);
78+
cursor: pointer;
79+
background-repeat: no-repeat;
80+
transition: var(--border-color) .25s, var(--background-color) .25s, opacity .25s;
81+
82+
&:hover {
83+
background-color: var(--hover-background-color);
84+
}
85+
86+
& span {
87+
width: 100%;
88+
aspect-ratio: 1 / 1;
89+
background-repeat: no-repeat;
90+
background-position: center;
91+
background-size: cover;
92+
}
93+
94+
& .ready {
95+
width: 20px;
96+
height: 20px;
97+
background-image: url("${copyIcon}");
98+
}
99+
100+
& .success {
101+
display: none;
102+
width: 20px;
103+
height: 20px;
104+
background-image: url("${successIcon}");
105+
}
106+
107+
&.shiki-transformer-button-copied {
108+
& .success {
109+
display: block;
110+
}
111+
112+
& .ready {
113+
display: none;
114+
}
115+
}
116+
}`
117+
return buttonStyle
118+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export interface CopyButtonOptions {
2+
duration?: number;
3+
copyIcon?: string;
4+
successIcon?: string
5+
}

examples/vue3-marquee/tsconfig.app.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"compilerOptions": {
66
"composite": true,
77
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
8-
8+
"lib": ["ES2021"],
99
"baseUrl": ".",
1010
"paths": {
1111
"@/*": ["./src/*"]

pnpm-lock.yaml

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)