vitepress-plugin-sfc 2.0.0-beta.1 → 2.0.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,5 +1,9 @@
1
- import{Buffer as e}from"node:buffer";import t from"node:crypto";function n(e,t,n){function r(e){return e.trim().split(` `,2)[0]===t}function i(e,n,r,i,a){return e[n].nesting===1&&e[n].attrJoin(`class`,t),a.renderToken(e,n,r,i,a)}n||={};let a=n.marker||`:`,o=a.charCodeAt(0),s=a.length,c=n.validate||r,l=n.render||i;function u(e,n,r,i){let l,u=!1,d=e.bMarks[n]+e.tShift[n],f=e.eMarks[n];if(o!==e.src.charCodeAt(d))return!1;for(l=d+1;l<=f&&a[(l-d)%s]===e.src[l];l++);let p=Math.floor((l-d)/s);if(p<3)return!1;l-=(l-d)%s;let m=e.src.slice(d,l),h=e.src.slice(l,f);if(!c(h,m))return!1;if(i)return!0;let g=n;for(;g++,!(g>=r||(d=e.bMarks[g]+e.tShift[g],f=e.eMarks[g],d<f&&e.sCount[g]<e.blkIndent));)if(o===e.src.charCodeAt(d)&&!(e.sCount[g]-e.blkIndent>=4)){for(l=d+1;l<=f&&a[(l-d)%s]===e.src[l];l++);if(!(Math.floor((l-d)/s)<p)&&(l-=(l-d)%s,l=e.skipSpaces(l),!(l<f))){u=!0;break}}let _=e.parentType,v=e.lineMax;e.parentType=`container`,e.lineMax=g;let y=e.push(`container_`+t+`_open`,`div`,1);y.markup=m,y.block=!0,y.info=h,y.map=[n,g],e.md.block.tokenize(e,n+1,g);let b=e.push(`container_`+t+`_close`,`div`,-1);return b.markup=e.src.slice(d,l),b.block=!0,e.parentType=_,e.lineMax=v,e.line=g+ +!!u,!0}e.block.ruler.before(`fence`,`container_`+t,u,{alt:[`paragraph`,`reference`,`blockquote`,`list`]}),e.renderer.rules[`container_`+t+`_open`]=l,e.renderer.rules[`container_`+t+`_close`]=l}const r=/<script\b[^>]*>([\s\S]*?)<\/script>/,i=/^virtual:VP_SFC_/;function a(e){return t.createHash(`md5`).update(e).digest(`hex`)}function o(t,o){let{render:s=!0}=o||{};t.use(n,`sfc`,{validate:e=>RegExp(`^sfc`).test(e.trim()),render:(n,r)=>{if(!s)return;let i=n[r];if(i.nesting===1){let{content:n,name:r}=i.meta?.sfc||{},a=t.options.highlight?.(n,`vue`,``)??``,o=`<template #v3><${r} /></template>`;return`<ComponentView highlightedSrc="${e.from(a).toString(`base64`)}">\n${o}`}else return`</ComponentView>`}}),t.core.ruler.after(`normalize`,`inject_script`,e=>{let t=`
2
- <script setup><\/script>
3
- `;if(!new RegExp(r,`gi`).test(e.src)){let n=e.src.replace(/---([\s\S]*?)---/gi,e=>e+t);e.src=n===e.src?n+t:n}}),t.core.ruler.after(`block`,`sfc_extract_and_import`,e=>{let t=e.src.split(/\r?\n/),n=[],o,s,c=(e,n)=>{let{type:r,nesting:o,map:s}=e;if(r!==`container_sfc_open`||o!==1||!s)return;let[c,l]=s,u=t.slice(c+1,l).join(`
4
- `),d=a(u),f=`VP_SFC_${d}`,p=`virtual:${f}.vue`;if(!i.test(p))throw Error(`[vitepress-plugin-sfc] Plugin-self Error: Unmatched importer syntax`);let m={content:u,name:f,hash:d,importer:p};return n?.(m),m};for(let t of e.tokens){let{type:e,content:i,map:a}=t;!o&&e===`html_block`&&new RegExp(r,`i`).test(i)&&(o=t);let l=c(t,e=>{t.meta={...t.meta??{},sfc:e},s=t});if(l&&n.push(l),s&&s!==t){let[e]=a||[-1],[n,r]=s.map;e>n&&e<r?t.content=``:s=void 0}}e.env.sfcs=n;let l=n.map(({name:e,importer:t})=>`import ${e} from '${t}'`).join(`
5
- `);l&&o&&(o.content=o.content.replace(/<script\b[^>]*>([\s\S]*?)<\/script>/i,(e,t)=>e.replace(t,`\n${l}\n`+t)))})}const s=o;function c(e){let{md:t}=e||{},n=new Map;return{name:`vite-plugin-sfc-registry`,enforce:`pre`,resolveId:{order:`pre`,filter:{id:new RegExp(i,`gi`)},handler(e){if(n.has(e))return e}},load:{order:`pre`,filter:{id:new RegExp(i,`gi`)},handler(e){if(n.has(e))return n.get(e)}},transform(e,r){if(/.md$/gi.test(r)){let r={};t.use(s,{render:!1}).render(e,r);let i=r.sfcs||[];for(let{importer:e,content:t}of i)n.set(e,t)}}}}export{r as SCRIPT_RE,i as SFC_IMPORTER_RE,s as sfcPlugin,c as sfcRegistryVitePlugin};
1
+ import e from"node:crypto";import{createMarkdownRenderer as t}from"vitepress";function n(e,t,n){function r(e){return e.trim().split(` `,2)[0]===t}function i(e,n,r,i,a){return e[n].nesting===1&&e[n].attrJoin(`class`,t),a.renderToken(e,n,r,i,a)}n||={};let a=n.marker||`:`,o=a.charCodeAt(0),s=a.length,c=n.validate||r,l=n.render||i;function u(e,n,r,i){let l,u=!1,d=e.bMarks[n]+e.tShift[n],f=e.eMarks[n];if(o!==e.src.charCodeAt(d))return!1;for(l=d+1;l<=f&&a[(l-d)%s]===e.src[l];l++);let p=Math.floor((l-d)/s);if(p<3)return!1;l-=(l-d)%s;let m=e.src.slice(d,l),h=e.src.slice(l,f);if(!c(h,m))return!1;if(i)return!0;let g=n;for(;g++,!(g>=r||(d=e.bMarks[g]+e.tShift[g],f=e.eMarks[g],d<f&&e.sCount[g]<e.blkIndent));)if(o===e.src.charCodeAt(d)&&!(e.sCount[g]-e.blkIndent>=4)){for(l=d+1;l<=f&&a[(l-d)%s]===e.src[l];l++);if(!(Math.floor((l-d)/s)<p)&&(l-=(l-d)%s,l=e.skipSpaces(l),!(l<f))){u=!0;break}}let _=e.parentType,v=e.lineMax;e.parentType=`container`,e.lineMax=g;let y=e.push(`container_`+t+`_open`,`div`,1);y.markup=m,y.block=!0,y.info=h,y.map=[n,g],e.md.block.tokenize(e,n+1,g);let b=e.push(`container_`+t+`_close`,`div`,-1);return b.markup=e.src.slice(d,l),b.block=!0,e.parentType=_,e.lineMax=v,e.line=g+ +!!u,!0}e.block.ruler.before(`fence`,`container_`+t,u,{alt:[`paragraph`,`reference`,`blockquote`,`list`]}),e.renderer.rules[`container_`+t+`_open`]=l,e.renderer.rules[`container_`+t+`_close`]=l}const r=/<script\b[^>]*>([\s\S]*?)<\/script>/,i=/^virtual:VP_SFC_/;function a(){return{content:`<script setup>
2
+ <\/script>`,tagOpen:`<script setup>`,type:`script`,contentStripped:`
3
+ `,tagClose:`<\/script>`}}function o(e){let t=s(e),n=`VP_SFC_${t}`,r=`virtual:${n}.vue`;return{hash:t,name:n,importer:r,renderedContent:`// [vitepress-plugin-sfc] injected vue virtual imports
4
+ // REGION START
5
+ import ${n} from '${r}'
6
+ // REGION END
7
+ `}}function s(t){return e.createHash(`sha256`).update(t).digest(`hex`)}function c(e){if(!e)return{};let t=e.split(`,`);return Object.fromEntries(t.map(e=>{let[,t,n]=e.match(/([^=]+)=?(.*)/)||[];return t===`disableRender`?[`disableRender`,!n||n===`true`?`true`:`false`]:t&&n?[t,n]:void 0}).filter(Boolean))}function l(e){e.use(n,`sfc`,{validate:e=>RegExp(`^sfc`).test(e.trim()),render:(e,t,n,r)=>{let{__matched_sfc_set__:i,sfcBlocks:o}=r;o.scripts.length||o.scripts.push(a());let[s]=o.scripts??[],c=e[t];if(c.nesting===1){let{hash:e,name:t,content:r,renderedContent:a,markupParams:o}=c.meta.sfc;if(!i.has(e)){let{tagOpen:t,tagClose:n,contentStripped:r}=s,o=r+a;s.contentStripped=o,s.content=t+o+n,i.add(e)}let{render:l=``,disableRender:u}=o;return`<ComponentView render="${l}" :disableRender="${u}">\n<${t} />\n<template #highlight>${n.highlight(r,`vue`,``)}</template>\n</ComponentView>\n`}else return``}}),e.core.ruler.after(`block`,`extract_sfc`,e=>{let t=e.src.split(`
8
+ `),n=e.env.sfcs||[],r;function i(e){let{type:n,nesting:r,map:i,info:a}=e;if(n!==`container_sfc_open`||r!==1||!i)return;let[s,l]=i,u=t.slice(s+1,l).join(`
9
+ `),d=o(u),[,f]=(a??``).split(` `);return{...d,content:u,markupParams:c(f)}}for(let t of e.tokens){let{map:e}=t,a=i(t);if(a){r=t,t.meta={...t.meta,sfc:a};let e=n.some(({name:e})=>a?.name===e);a&&!e&&n.push(a)}if(r&&r!==t){let[n]=e||[-1],[i,a]=r.map;n>i&&n<a?t.content=``:r=void 0}}let a=e.env;a.sfcs=[...n],a.__matched_sfc_set__=new Set})}const u=l;async function d(){let e=await t(``,{html:!0});e.use(u);let n=new Map;return{name:`vite-plugin-sfc-registry`,enforce:`pre`,resolveId:{filter:{id:new RegExp(i,`gi`)},handler(e){if(n.has(e))return e}},load:{filter:{id:new RegExp(i,`gi`)},handler(e){if(n.has(e))return n.get(e)}},transform(t,r){if(/.md$/gi.test(r)){let r={};e.render(t,r);let i=r.sfcs||[];for(let{importer:e,content:t}of i)n.set(e,t)}}}}export{r as SCRIPT_RE,i as SFC_IMPORTER_RE,a as createDefaultSfcBlock,o as createVirtualImport,u as sfcPlugin,d as sfcRegistryVitePlugin};
package/package.json CHANGED
@@ -1,10 +1,32 @@
1
1
  {
2
2
  "name": "vitepress-plugin-sfc",
3
- "version": "2.0.0-beta.1",
4
- "description": "VitePress plugin that renders inline Vue SFC with tabs showing preview and source code",
3
+ "version": "2.0.0-beta.3",
4
+ "description": "Allows you to write Vue SFC within Markdown files and render them in VitePress.",
5
+ "keywords": [
6
+ "component",
7
+ "plugin",
8
+ "tabs",
9
+ "vitepress",
10
+ "vitepress plugin",
11
+ "vitepress-plugin",
12
+ "vue"
13
+ ],
14
+ "bugs": {
15
+ "url": "https://github.com/heroui-vue/vitepress-plugin-sfc/issues"
16
+ },
17
+ "license": "MIT",
18
+ "author": "hotdogc1017@gmail.com",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/heroui-vue/vitepress-plugin-sfc"
22
+ },
23
+ "files": [
24
+ "dist",
25
+ "src/client"
26
+ ],
5
27
  "type": "module",
6
- "main": "dist/index.js",
7
- "module": "dist/index.js",
28
+ "main": "dist/index.mjs",
29
+ "module": "dist/index.mjs",
8
30
  "exports": {
9
31
  ".": {
10
32
  "types": "./dist/index.d.mts",
@@ -12,50 +34,35 @@
12
34
  },
13
35
  "./client": "./src/client/index.ts"
14
36
  },
15
- "files": [
16
- "dist",
17
- "src/client"
18
- ],
19
- "types": "./dist/index.d.mts",
20
- "scripts": {
21
- "dev": "tsdown -w",
22
- "build": "tsdown --minify",
23
- "bump": "bumpp --sign",
24
- "pub": "npm run build && npm publish --registry=https://registry.npmjs.org/",
25
- "dev:docs": "pnpm -C playground dev"
26
- },
27
- "keywords": [
28
- "vitepress",
29
- "vitepress-plugin",
30
- "vitepress plugin",
31
- "plugin",
32
- "vue",
33
- "component",
34
- "tabs"
35
- ],
36
37
  "publishConfig": {
37
38
  "access": "public",
38
39
  "registry": "https://registry.npmjs.org/"
39
40
  },
40
- "author": "hotdogc1017@gmail.com",
41
- "license": "MIT",
42
- "repository": {
43
- "type": "git",
44
- "url": "https://github.com/heroui-vue/vitepress-plugin-sfc"
45
- },
46
41
  "devDependencies": {
47
- "@babel/parser": "8.0.0-rc.5",
48
- "@babel/traverse": "8.0.0-rc.5",
49
- "@rolldown/pluginutils": "^1.0.1",
42
+ "@tsdown/css": "^0.23.2",
50
43
  "@types/markdown-it": "^14.2.0",
51
44
  "@types/markdown-it-container": "^2.0.10",
52
45
  "@types/node": "^22.17.0",
53
46
  "bumpp": "^10.1.0",
54
47
  "markdown-it-container": "^4.0.0",
48
+ "oxfmt": "^0.73.0",
49
+ "oxlint": "^1.88.0",
55
50
  "tsdown": "0.21.10",
56
51
  "typescript": "^5.8.3",
57
52
  "unplugin-vue": "^6.2.0",
58
53
  "vitepress": "next",
54
+ "vitest": "^5.0.4",
59
55
  "vue": "^3.5.0"
56
+ },
57
+ "scripts": {
58
+ "dev": "tsdown -w",
59
+ "build": "tsdown --minify",
60
+ "bump": "bumpp --sign",
61
+ "pub": "npm run build && npm publish --registry=https://registry.npmjs.org/",
62
+ "dev:docs": "pnpm -C playground dev",
63
+ "fmt": "oxfmt",
64
+ "fmt:check": "oxfmt --check",
65
+ "lint": "oxlint",
66
+ "test": "vitest run -r __tests__"
60
67
  }
61
- }
68
+ }
@@ -1,181 +1,46 @@
1
- <script setup lang="ts" generic="T">
2
- import { computed, ref, useId, watch } from "vue";
3
- import { useData } from "vitepress";
4
- import { useLocaleMappings } from "./useLocaleMappings";
1
+ <script setup lang="ts">
2
+ import { inject, computed, getCurrentInstance, withDefaults, type Component } from "vue";
3
+ import { configSymbol, type Config, type ComponentViewProps } from "./index";
5
4
 
6
- import type { LocaleMappings } from "./useLocaleMappings";
7
-
8
- const localMappings: LocaleMappings = {
9
- en: {
10
- previewLabel: "Preview",
11
- codeLabel: "Source Code",
12
- },
13
- zh: {
14
- previewLabel: "预览",
15
- codeLabel: "源代码",
16
- },
17
- };
18
-
19
- const props = defineProps<{
20
- src: string;
21
- highlightedSrc?: string;
22
- previewLabel?: string;
23
- codeLabel?: string;
24
- }>();
25
-
26
- const { lang } = useData();
27
- const { get } = useLocaleMappings();
5
+ interface Props extends Omit<ComponentViewProps, "render"> {
6
+ render: Component | undefined;
7
+ }
28
8
 
29
- const labels = computed(() => {
30
- const mappings = { ...localMappings, ...(get() ?? {}) };
31
- // Default to English mappings if no locale-specific mappings are found
32
- const { previewLabel: preview, codeLabel: code } =
33
- mappings[lang.value] ?? localMappings.en;
34
- return [
35
- {
36
- label: preview,
37
- value: "preview",
38
- },
39
- { label: code, value: "code" },
40
- ] as const;
9
+ const props = withDefaults(defineProps<ComponentViewProps>(), {
10
+ disableRender: false,
41
11
  });
42
12
 
43
- const decodedSrc = ref("");
44
- const highlightedSrc = ref("");
45
- const activedTab = ref<"preview" | "code">("preview");
46
- const copied = ref(false);
47
- const tabGroupId = useId();
48
-
49
- watch(
50
- [() => props.src, () => props.highlightedSrc],
51
- async ([src, highlight = ""]) => {
52
- try {
53
- decodedSrc.value = atob(src);
54
- } catch (e) {
55
- decodedSrc.value = src;
56
- }
57
-
58
- try {
59
- highlightedSrc.value = atob(highlight);
60
- } catch (e) {
61
- highlightedSrc.value = highlight;
62
- }
63
-
64
- try {
65
- atob(src);
66
- } catch (e) {}
67
- },
68
- {
69
- immediate: true,
70
- },
71
- );
72
-
73
- async function copyCode() {
74
- await navigator.clipboard.writeText(decodedSrc.value);
75
- copied.value = true;
76
- setTimeout(() => {
77
- copied.value = false;
78
- }, 2000);
79
- }
13
+ const config = computed<Props>(() => {
14
+ const { render, disableRender } = inject<Config>(configSymbol);
15
+ const resolveConfig = {
16
+ render: render || props.render,
17
+ disableRender:
18
+ disableRender === null || disableRender === undefined ? props.disableRender : disableRender,
19
+ };
20
+
21
+ const renderInstance = getCurrentInstance().appContext.app.component(
22
+ resolveConfig.render as string,
23
+ );
24
+ if (!renderInstance) {
25
+ console.warn(`[vitepress-plugin-sfc] Unregistered component: ${resolveConfig.render}`);
26
+ }
27
+ return {
28
+ ...resolveConfig,
29
+ render: renderInstance,
30
+ };
31
+ });
80
32
  </script>
81
33
 
82
34
  <template>
83
- <div class="vp-component-tabs">
84
- <header>
85
- <template v-for="{ label, value } in labels" :key="value">
86
- <input
87
- :id="`tab-${tabGroupId}-${value}`"
88
- type="radio"
89
- :name="`vp-component-tabs-${tabGroupId}`"
90
- style="position: fixed; opacity: 0; pointer-events: none"
91
- :data-checked="activedTab === value"
92
- />
93
- <label
94
- :for="`tab-${tabGroupId}-${value}`"
95
- @click="activedTab = value"
96
- >{{ label }}</label
97
- >
98
- </template>
99
- </header>
100
-
101
- <main v-show="activedTab === 'preview'">
102
- <!-- <slot /> -->
103
- <slot name="v3" />
104
- </main>
105
-
106
- <div
107
- v-show="activedTab === 'code'"
108
- :class="`language-vue${activedTab === 'code' ? ' active' : ''}`"
109
- style="margin: 0px"
110
- >
111
- <button title="" class="copy"></button>
112
- <span class="lang">Vue</span>
113
- <span v-html="highlightedSrc" />
114
- </div>
115
- </div>
35
+ <div>
36
+ <slot v-if="!config.render || config.disableRender"></slot>
37
+ <component v-else :is="config.render">
38
+ <slot />
39
+ <template #highlight>
40
+ <slot name="highlight"></slot>
41
+ </template>
42
+ </component>
43
+ </div>
116
44
  </template>
117
45
 
118
- <style>
119
- .vp-component-tabs {
120
- margin: 16px 0;
121
- display: grid;
122
- grid-template-columns: 1fr;
123
- grid-template-rows: min-content 1fr;
124
- row-gap: 16px;
125
- }
126
-
127
- .vp-component-tabs > header {
128
- display: flex;
129
- position: relative;
130
- border-radius: 8px;
131
- overflow-x: auto;
132
- overflow-y: hidden;
133
- }
134
-
135
- .vp-component-tabs > header input + label {
136
- box-sizing: border-box;
137
- display: inline-block;
138
- position: relative;
139
- padding: 8px 12px;
140
- font-size: 14px;
141
- font-weight: 500;
142
- color: var(--vp-code-tab-text-color);
143
- white-space: nowrap;
144
- cursor: pointer;
145
- transition:
146
- color 0.25s,
147
- background-color 0.25s;
148
- }
149
-
150
- .vp-component-tabs > header input + label::after {
151
- content: "";
152
- position: absolute;
153
- height: 4px;
154
- border-radius: 2px;
155
- left: 8px;
156
- right: 8px;
157
- bottom: -1px;
158
- transition: background-color 0.25s;
159
- }
160
- .vp-component-tabs > header input[data-checked="true"] + label::after {
161
- background-color: var(--vp-code-tab-active-bar-color);
162
- }
163
- .vp-component-tabs > header input[data-checked="true"] + label,
164
- .vp-component-tabs > header label:hover {
165
- color: var(--vp-code-tab-active-text-color);
166
- }
167
-
168
- .vp-component-tabs > main {
169
- padding: 20px;
170
- background-color: var(--vp-nav-bg-color);
171
- border-radius: 8px;
172
- outline: 1px solid var(--vp-c-divider);
173
- }
174
-
175
- .vp-component-tabs > section {
176
- background-color: var(--vp-code-block-bg);
177
- border-radius: 8px;
178
- padding: 24px;
179
- overflow: auto;
180
- }
181
- </style>
46
+ <style></style>
@@ -0,0 +1,126 @@
1
+ <script setup lang="ts">
2
+ import { computed, ref, useId, onMounted } from "vue";
3
+ import { useData } from "vitepress";
4
+ import { type GroupTabsProps } from "./index";
5
+
6
+ const DEFAULT_LOCALE: GroupTabsProps["locale"] = {
7
+ en: {
8
+ label: {
9
+ preview: "Preview",
10
+ code: "Source Code",
11
+ },
12
+ },
13
+ zh: {
14
+ label: {
15
+ preview: "预览",
16
+ code: "源代码",
17
+ },
18
+ },
19
+ };
20
+
21
+ const props = defineProps<GroupTabsProps>();
22
+
23
+ const { lang } = useData();
24
+
25
+ const currentLocale = computed(() => {
26
+ const locale = props.locale;
27
+ return locale?.[lang.value] || DEFAULT_LOCALE[lang.value];
28
+ });
29
+
30
+ const activedTab = ref<"preview" | "code">("preview");
31
+ const tabGroupId = useId();
32
+ </script>
33
+
34
+ <template>
35
+ <div class="vp-component-tabs">
36
+ <header>
37
+ <template v-for="(value, key) in currentLocale.label" :key="key">
38
+ <input
39
+ :id="`tab-${tabGroupId}-${key}`"
40
+ type="radio"
41
+ :name="`vp-component-tabs-${tabGroupId}`"
42
+ style="position: fixed; opacity: 0; pointer-events: none"
43
+ :data-checked="activedTab === key" />
44
+ <label :for="`tab-${tabGroupId}-${key}`" @click="activedTab = key">{{ value }}</label>
45
+ </template>
46
+ </header>
47
+
48
+ <main v-show="activedTab === 'preview'">
49
+ <slot />
50
+ </main>
51
+
52
+ <div
53
+ v-show="activedTab === 'code'"
54
+ :class="`language-vue${activedTab === 'code' ? ' active' : ''}`"
55
+ style="margin: 0px">
56
+ <button title="copy source code" class="copy"></button>
57
+ <span class="lang">Vue</span>
58
+ <slot name="highlight" />
59
+ </div>
60
+ </div>
61
+ </template>
62
+
63
+ <style>
64
+ .vp-component-tabs {
65
+ margin: 16px 0;
66
+ display: grid;
67
+ grid-template-columns: 1fr;
68
+ grid-template-rows: min-content 1fr;
69
+ row-gap: 16px;
70
+ }
71
+
72
+ .vp-component-tabs > header {
73
+ display: flex;
74
+ position: relative;
75
+ border-radius: 8px;
76
+ overflow-x: auto;
77
+ overflow-y: hidden;
78
+ }
79
+
80
+ .vp-component-tabs > header input + label {
81
+ box-sizing: border-box;
82
+ display: inline-block;
83
+ position: relative;
84
+ padding: 8px 12px;
85
+ font-size: 14px;
86
+ font-weight: 500;
87
+ color: var(--vp-code-tab-text-color);
88
+ white-space: nowrap;
89
+ cursor: pointer;
90
+ transition:
91
+ color 0.25s,
92
+ background-color 0.25s;
93
+ }
94
+
95
+ .vp-component-tabs > header input + label::after {
96
+ content: "";
97
+ position: absolute;
98
+ height: 4px;
99
+ border-radius: 2px;
100
+ left: 8px;
101
+ right: 8px;
102
+ bottom: -1px;
103
+ transition: background-color 0.25s;
104
+ }
105
+ .vp-component-tabs > header input[data-checked="true"] + label::after {
106
+ background-color: var(--vp-code-tab-active-bar-color);
107
+ }
108
+ .vp-component-tabs > header input[data-checked="true"] + label,
109
+ .vp-component-tabs > header label:hover {
110
+ color: var(--vp-code-tab-active-text-color);
111
+ }
112
+
113
+ .vp-component-tabs > main {
114
+ padding: 20px;
115
+ background-color: var(--vp-nav-bg-color);
116
+ border-radius: 8px;
117
+ outline: 1px solid var(--vp-c-divider);
118
+ }
119
+
120
+ .vp-component-tabs > section {
121
+ background-color: var(--vp-code-block-bg);
122
+ border-radius: 8px;
123
+ padding: 24px;
124
+ overflow: auto;
125
+ }
126
+ </style>
@@ -1,23 +1,49 @@
1
1
  import ComponentView from "./ComponentView.vue";
2
- import { useLocaleMappings } from "./useLocaleMappings";
2
+ import type { EnhanceAppContext } from "vitepress";
3
3
 
4
- import type { App } from "vue";
5
- import type { LocaleMappings } from "./useLocaleMappings";
4
+ export interface ComponentViewProps {
5
+ render?: "GroupTabs" | "SourceCodeButton" | (string & {});
6
+ disableRender?: boolean;
7
+ }
6
8
 
7
- export const COMPONENT_NAME = "ComponentView";
9
+ export interface GroupTabsProps {
10
+ locale?: Record<string, { label: { preview: string; code: string } }>;
11
+ }
8
12
 
9
- export function enhanceAppWithComponentView(
10
- app: App,
11
- options?: {
12
- localeMappings?: LocaleMappings;
13
- },
14
- ): void {
15
- const { localeMappings = {} } = options || {};
13
+ // export interface SourceCodeButtonProps {
14
+ // icon: string;
15
+ // }
16
16
 
17
- app.component(COMPONENT_NAME, ComponentView);
17
+ export type Config = ComponentViewProps & {
18
+ groupTabs?: GroupTabsProps;
19
+ };
18
20
 
19
- const { set } = useLocaleMappings(app);
20
- set(localeMappings);
21
- }
21
+ export const configSymbol = Symbol("__vitepress_plugin_sfc_config");
22
+
23
+ const DEFUALT_CONFIG: Config = {
24
+ render: "GroupTabs",
25
+ };
26
+
27
+ export async function enhanceAppWithComponentView(app: EnhanceAppContext["app"], config?: Config) {
28
+ app.component("ComponentView", ComponentView);
22
29
 
23
- export { ComponentView };
30
+ const resolvedConfig = { ...DEFUALT_CONFIG, ...config };
31
+
32
+ if (app) {
33
+ app.provide(configSymbol, resolvedConfig);
34
+ } else {
35
+ throw new Error(
36
+ "[vitepress-plugin-sfc] The `useConfig` must be called within a Vue component if not providing an app",
37
+ );
38
+ }
39
+
40
+ const { render, disableRender } = resolvedConfig;
41
+ if (disableRender) {
42
+ return;
43
+ }
44
+
45
+ if (render === "GroupTabs") {
46
+ const importer = await import("./GroupTabs.vue");
47
+ app.component("GroupTabs", importer.default);
48
+ }
49
+ }
@@ -1,36 +0,0 @@
1
- import { provide, inject, getCurrentInstance } from "vue";
2
- import type { App } from "vue";
3
-
4
- export type LocaleMappings = Record<
5
- string,
6
- { previewLabel: string; codeLabel: string }
7
- >;
8
-
9
- const localeMappingSymbol = Symbol(
10
- "__vitepress_plugin_component_localeMapping",
11
- );
12
-
13
- /**
14
- * Initialize locale mappings for the application.
15
- * @param app - The Vue application instance. If not provided, the mappings will be provided to the current component.
16
- */
17
- export function useLocaleMappings(app?: App) {
18
- return {
19
- set: (localeMapping: LocaleMappings) => {
20
- const instance = getCurrentInstance();
21
- if (app) {
22
- app?.provide(localeMappingSymbol, localeMapping);
23
- } else {
24
- if (!instance) {
25
- throw new Error(
26
- "useLocaleMappings must be called within a Vue component if not providing an app",
27
- );
28
- }
29
- provide(localeMappingSymbol, localeMapping);
30
- }
31
- },
32
- get: () => {
33
- return inject<LocaleMappings | undefined>(localeMappingSymbol, undefined);
34
- },
35
- };
36
- }