From 024679fcaf1883f47aadc67020a2220f6303f396 Mon Sep 17 00:00:00 2001 From: selemondev Date: Sat, 4 Jan 2025 15:57:01 +0300 Subject: [PATCH 1/2] docs: add shiki transformer copy button --- examples/vue3-marquee/package.json | 1 + examples/vue3-marquee/src/App.vue | 5 +- .../vue3-marquee/src/components/CodeBlock.vue | 12 +- .../vue3-marquee/src/lib/utils/codeToHtml.ts | 10 +- .../src/lib/utils/transformerCopyButton.ts | 118 ++++++++++++++++++ .../src/types/CopyButtonOptions.interface.ts | 5 + examples/vue3-marquee/tsconfig.app.json | 2 +- 7 files changed, 145 insertions(+), 8 deletions(-) create mode 100644 examples/vue3-marquee/src/lib/utils/transformerCopyButton.ts create mode 100644 examples/vue3-marquee/src/types/CopyButtonOptions.interface.ts diff --git a/examples/vue3-marquee/package.json b/examples/vue3-marquee/package.json index 3997df4..de95e35 100644 --- a/examples/vue3-marquee/package.json +++ b/examples/vue3-marquee/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@selemondev/vue3-marquee": "0.0.8", + "hastscript": "^9.0.0", "vue": "^3.4.15" }, "devDependencies": { diff --git a/examples/vue3-marquee/src/App.vue b/examples/vue3-marquee/src/App.vue index eeb263d..8dc844e 100644 --- a/examples/vue3-marquee/src/App.vue +++ b/examples/vue3-marquee/src/App.vue @@ -92,7 +92,7 @@ import { installCmd, globalImportSnippet, localImportSnippet, nuxtPluginSnippet,

Code

- +

@@ -178,6 +178,7 @@ pre { border-radius: 10px; overflow: scroll; -ms-overflow-style: none; - scrollbar-width: none; + scrollbar-width: none; + position: relative; } \ No newline at end of file diff --git a/examples/vue3-marquee/src/components/CodeBlock.vue b/examples/vue3-marquee/src/components/CodeBlock.vue index e25053a..81d111f 100644 --- a/examples/vue3-marquee/src/components/CodeBlock.vue +++ b/examples/vue3-marquee/src/components/CodeBlock.vue @@ -9,8 +9,8 @@ const props = defineProps<{ const codeToHtml = ref(''); -watch(props, async (val: { code: string, lang: string, theme: string}) => { - if(val) { +watch(props, async (val: { code: string, lang: string, theme: string }) => { + if (val) { return codeToHtml.value = await convertCodeToHtml(val.code, val.lang, val.theme) } }, { @@ -19,5 +19,9 @@ watch(props, async (val: { code: string, lang: string, theme: string}) => { \ No newline at end of file +
+ + + \ No newline at end of file diff --git a/examples/vue3-marquee/src/lib/utils/codeToHtml.ts b/examples/vue3-marquee/src/lib/utils/codeToHtml.ts index 1deecba..fcb3cf9 100644 --- a/examples/vue3-marquee/src/lib/utils/codeToHtml.ts +++ b/examples/vue3-marquee/src/lib/utils/codeToHtml.ts @@ -1,7 +1,15 @@ import { codeToHtml } from "shiki" +import { transformerCopyButton } from "./transformerCopyButton" export const convertCodeToHtml = async (code: string, lang: string, theme: string) => { return await codeToHtml(code, { lang, - theme + theme, + transformers: [ + transformerCopyButton({ + duration: 3000, + 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", + 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", + }) + ] }) } \ No newline at end of file diff --git a/examples/vue3-marquee/src/lib/utils/transformerCopyButton.ts b/examples/vue3-marquee/src/lib/utils/transformerCopyButton.ts new file mode 100644 index 0000000..efa6f6d --- /dev/null +++ b/examples/vue3-marquee/src/lib/utils/transformerCopyButton.ts @@ -0,0 +1,118 @@ +// Credits - Rehype pretty & JoshNuss + +import type { CopyButtonOptions } from "@/types/CopyButtonOptions.interface"; +import type { ShikiTransformer } from "shiki"; +import { h } from "hastscript"; + +export const transformerCopyButton = ( + options: CopyButtonOptions = { + duration: 3000 + } +): ShikiTransformer => { + return { + name: 'shiki-transformer-copy-button', + code(node) { + const button = h('button', { + class: 'shiki-transformer-button-copy', + 'data-code': this.source, + onclick: ` + navigator.clipboard.writeText(this.dataset.code); + this.classList.add('shiki-transformer-button-copied'); + setTimeout(() => this.classList.remove('shiki-transformer-button-copied'), ${options.duration}) + ` + }, [ + h('span', { class: 'ready' }), + h('span', { class: 'success' }) + ]); + node.children.push(button) + node.children.push({ + type: 'element', + tagName: 'style', + properties: {}, + children: [ + { + type: 'text', + value: buttonStyles({ + successIcon: options.successIcon, + copyIcon: options.copyIcon + }) + } + ] + }) + } + } +} + +function buttonStyles({ + 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", + 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", +}: { + successIcon?: string, + copyIcon?: string +}) { + let buttonStyle = + ` +:root { +--border-color: #e2e2e3; +--background-color: #f6f6f7; +--hover-background-color: #ffff +} + +pre:has(code) { + position: relative; +} + +pre button.shiki-transformer-button-copy { + position: absolute; + top: 12px; + right: 12px; + z-index: 3; + border: 1px solid var(--border-color); + border-radius: 4px; + width: 30px; + height: 30px; + display: flex; + justify-content: center; + place-items: center; + background-color: var(--background-color); + cursor: pointer; + background-repeat: no-repeat; + transition: var(--border-color) .25s, var(--background-color) .25s, opacity .25s; + + &:hover { + background-color: var(--hover-background-color); + } + + & span { + width: 100%; + aspect-ratio: 1 / 1; + background-repeat: no-repeat; + background-position: center; + background-size: cover; + } + + & .ready { + width: 20px; + height: 20px; + background-image: url("${copyIcon}"); + } + + & .success { + display: none; + width: 20px; + height: 20px; + background-image: url("${successIcon}"); + } + + &.shiki-transformer-button-copied { + & .success { + display: block; + } + + & .ready { + display: none; + } + } +}` + return buttonStyle +} \ No newline at end of file diff --git a/examples/vue3-marquee/src/types/CopyButtonOptions.interface.ts b/examples/vue3-marquee/src/types/CopyButtonOptions.interface.ts new file mode 100644 index 0000000..e2133f6 --- /dev/null +++ b/examples/vue3-marquee/src/types/CopyButtonOptions.interface.ts @@ -0,0 +1,5 @@ +export interface CopyButtonOptions { + duration?: number; + copyIcon?: string; + successIcon?: string +} \ No newline at end of file diff --git a/examples/vue3-marquee/tsconfig.app.json b/examples/vue3-marquee/tsconfig.app.json index 2a9a8d2..4ce638c 100644 --- a/examples/vue3-marquee/tsconfig.app.json +++ b/examples/vue3-marquee/tsconfig.app.json @@ -5,7 +5,7 @@ "compilerOptions": { "composite": true, "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", - + "lib": ["ES2021"], "baseUrl": ".", "paths": { "@/*": ["./src/*"] From 8889b750167b0110ffc22788f5bf0deda5565c13 Mon Sep 17 00:00:00 2001 From: selemondev Date: Sat, 4 Jan 2025 15:58:07 +0300 Subject: [PATCH 2/2] chore: update pnpm-lock file --- pnpm-lock.yaml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6dd9e25..1ddcce0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,6 +20,9 @@ importers: '@selemondev/vue3-marquee': specifier: 0.0.8 version: 0.0.8(tailwindcss@3.4.1)(typescript@5.3.3) + hastscript: + specifier: ^9.0.0 + version: 9.0.0 vue: specifier: ^3.4.15 version: 3.4.19(typescript@5.3.3) @@ -768,12 +771,18 @@ packages: resolution: {integrity: sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==} engines: {node: '>= 0.4'} + hast-util-parse-selector@4.0.0: + resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} + hast-util-to-html@9.0.4: resolution: {integrity: sha512-wxQzXtdbhiwGAUKrnQJXlOPmHnEehzphwkK7aluUPQ+lEc1xefC8pblMgpp2w5ldBTEfveRIrADcrhGIWrlTDA==} hast-util-whitespace@3.0.0: resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + hastscript@9.0.0: + resolution: {integrity: sha512-jzaLBGavEDKHrc5EfFImKN7nZKKBdSLIdGvCwDZ9TfzbF2ffXiov8CKE445L2Z1Ek2t/m4SKQ2j6Ipv7NyUolw==} + he@1.2.0: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true @@ -1959,6 +1968,10 @@ snapshots: dependencies: function-bind: 1.1.2 + hast-util-parse-selector@4.0.0: + dependencies: + '@types/hast': 3.0.4 + hast-util-to-html@9.0.4: dependencies: '@types/hast': 3.0.4 @@ -1977,6 +1990,14 @@ snapshots: dependencies: '@types/hast': 3.0.4 + hastscript@9.0.0: + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + hast-util-parse-selector: 4.0.0 + property-information: 6.5.0 + space-separated-tokens: 2.0.2 + he@1.2.0: {} html-void-elements@3.0.0: {}