Skip to content

Commit 675a68c

Browse files
committed
feat: enhance documentation and examples with new components and markdown plugin
1 parent 9aa607a commit 675a68c

File tree

19 files changed

+593
-194
lines changed

19 files changed

+593
-194
lines changed
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
<script setup lang="ts">
2+
import { useClipboard } from '@vueuse/core'
3+
import { useData } from 'vitepress'
4+
import { computed, ref } from 'vue'
5+
import { GITHUB_EDIT_URL } from '../config/constants'
6+
import ExampleComponent from './ExampleComponent.vue'
7+
import ExampleOperate from './ExampleOperate.vue'
8+
import ExampleSourceCode from './ExampleSourceCode.vue'
9+
10+
const props = defineProps({
11+
path: {
12+
type: String,
13+
required: true,
14+
},
15+
source: {
16+
type: String,
17+
required: true,
18+
},
19+
rawSource: {
20+
type: String,
21+
required: true,
22+
},
23+
})
24+
25+
const { isDark } = useData()
26+
27+
const components = import.meta.glob('../../examples/**/*.vue', { eager: true })
28+
29+
const pathComponents = computed(() => {
30+
const _obj = {}
31+
Object.keys(components).forEach((key) => {
32+
_obj[key.replace('../../components/', '').replace('.vue', '')]
33+
= components[key].default
34+
})
35+
return _obj
36+
})
37+
38+
const showCode = ref(false)
39+
40+
function toggleHandle() {
41+
showCode.value = !showCode.value
42+
}
43+
44+
const { copy } = useClipboard({
45+
source: decodeURIComponent(props.rawSource),
46+
read: false,
47+
})
48+
49+
function copyHandle() {
50+
copy()
51+
}
52+
53+
const path = computed(() => {
54+
return props.path.replace(/\/\//g, '/')
55+
})
56+
57+
function openHandle() {
58+
window.open(`${GITHUB_EDIT_URL}/docs/${path.value}`)
59+
}
60+
</script>
61+
62+
<template>
63+
<ClientOnly>
64+
<div class="example" :class="{ dark: isDark }">
65+
<div class="example-component-wrapper">
66+
<ExampleComponent :file="path" :comp="pathComponents[path]" />
67+
</div>
68+
<div class="example-operate-wrapper">
69+
<ExampleOperate @toggle="toggleHandle" @copy="copyHandle" @open="openHandle" />
70+
</div>
71+
<div v-show="showCode" class="example-source-code-wrapper">
72+
<ExampleSourceCode :source="source" />
73+
</div>
74+
</div>
75+
</ClientOnly>
76+
</template>
77+
78+
<style scoped>
79+
.example {
80+
border: 1px solid;
81+
border-color: var(--vp-c-divider);
82+
border-radius: 4px;
83+
padding: 0;
84+
}
85+
86+
.example-component-wrapper,
87+
.example-operate-wrapper {
88+
padding: 0.5rem;
89+
}
90+
91+
.example-source-code-wrapper,
92+
.example-operate-wrapper {
93+
border-top: 1px solid;
94+
border-color: var(--vp-c-divider);
95+
}
96+
97+
.example-operate-wrapper {
98+
background-color: var(--vp-c-bg-soft);
99+
}
100+
</style>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<script setup lang="ts">
2+
defineProps({
3+
file: {
4+
type: String,
5+
required: true,
6+
},
7+
comp: {
8+
type: Object,
9+
required: true,
10+
},
11+
})
12+
</script>
13+
14+
<template>
15+
<div class="example-component">
16+
<ClientOnly>
17+
<component :is="comp" v-if="comp" v-bind="$attrs" />
18+
</ClientOnly>
19+
</div>
20+
</template>
21+
22+
<style scoped>
23+
.example-component {
24+
padding: 1.5rem;
25+
overflow: auto;
26+
display: flex;
27+
justify-content: center;
28+
align-items: center;
29+
flex-direction: column;
30+
}
31+
</style>
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
<script setup lang="ts">
2+
import { ref } from 'vue'
3+
4+
const emit = defineEmits(['toggle', 'copy', 'open'])
5+
6+
const code = ref(false)
7+
8+
function toggleSourceCode() {
9+
code.value = !code.value
10+
emit('toggle')
11+
}
12+
13+
const copy = ref(false)
14+
function copySourceCode() {
15+
copy.value = true
16+
setTimeout(() => {
17+
copy.value = false
18+
}, 3000)
19+
emit('copy')
20+
}
21+
22+
function openSourceCode() {
23+
emit('open')
24+
}
25+
</script>
26+
27+
<template>
28+
<div class="example-operate">
29+
<i class="icon" :title="!code ? '展示代码' : '隐藏代码'" @click="toggleSourceCode">
30+
<svg v-if="!code" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 32 32">
31+
<path
32+
fill="currentColor"
33+
d="m31 16l-7 7l-1.41-1.41L28.17 16l-5.58-5.59L24 9zM1 16l7-7l1.41 1.41L3.83 16l5.58 5.59L8 23zm11.42 9.484L17.64 6l1.932.517L14.352 26z"
34+
/>
35+
</svg>
36+
<svg v-else xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 32 32">
37+
<path
38+
fill="currentColor"
39+
d="m17.713 13.471l1.863-6.953L17.645 6l-1.565 5.838zm6.494 6.494l1.414 1.414L31 16l-7-7l-1.414 1.414L28.172 16zM30 28.586L3.414 2L2 3.414l5.793 5.793L1 16l7 7l1.414-1.414L3.828 16l5.379-5.379l5.677 5.677l-2.461 9.184l1.932.518l2.162-8.069L28.586 30z"
40+
/>
41+
</svg>
42+
</i>
43+
<i class="icon" :title="copy ? '已复制到剪贴板' : '复制代码'" @click="copySourceCode">
44+
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 32 32">
45+
<path
46+
fill="currentColor"
47+
d="M28 10v18H10V10zm0-2H10a2 2 0 0 0-2 2v18a2 2 0 0 0 2 2h18a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2"
48+
/>
49+
<path fill="currentColor" d="M4 18H2V4a2 2 0 0 1 2-2h14v2H4Z" />
50+
</svg>
51+
</i>
52+
<i class="icon" title="访问源码" @click="openSourceCode">
53+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24">
54+
<path
55+
fill="currentColor"
56+
d="M5.544 2.673a1.549 1.549 0 0 1 1.8.097v.001c.246.198.426.466.515.769l1.446 4.428h5.39L16.14 3.54a1.54 1.54 0 0 1 .515-.769l.004-.004a1.554 1.554 0 0 1 1.795-.095l.002.001c.274.174.486.43.605.732l.004.01l2.473 6.451a5.45 5.45 0 0 1-1.813 6.303l-6.73 5.064h-.002a1.66 1.66 0 0 1-2 0l-6.731-5.065a5.452 5.452 0 0 1-1.806-6.294l2.48-6.469c.12-.302.333-.558.607-.732m.811 2.063L4.16 10.464c-.28.737-.337 1.604-.12 2.362c.217.755.671 1.42 1.295 1.896l6.66 5.01l6.653-5.005a3.65 3.65 0 0 0 1.308-1.904c.22-.76.159-1.638-.123-2.378l-2.189-5.71L16 9.769H8z"
57+
/>
58+
</svg>
59+
</i>
60+
</div>
61+
</template>
62+
63+
<style scoped>
64+
.example-operate {
65+
display: flex;
66+
align-items: center;
67+
justify-content: flex-end;
68+
}
69+
70+
.example-operate .icon {
71+
cursor: pointer;
72+
color: #aaa;
73+
position: relative;
74+
width: 2rem;
75+
height: 2rem;
76+
display: flex;
77+
align-items: center;
78+
justify-content: center;
79+
}
80+
81+
.example-operate .icon:hover {
82+
opacity: 0.8;
83+
transition: all 0.3s ease-in-out;
84+
}
85+
86+
.example-operate .icon:hover ::after {
87+
cursor: auto;
88+
content: attr(title);
89+
position: absolute;
90+
top: -100%;
91+
left: 50%;
92+
transform: translateX(-50%);
93+
padding: 0.25rem 0.5rem;
94+
background-color: #333;
95+
color: #fff;
96+
border-radius: 0.25rem;
97+
font-size: 0.75rem;
98+
white-space: nowrap;
99+
z-index: 999;
100+
font-style: normal;
101+
}
102+
</style>
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<script setup lang="ts">
2+
import { computed } from 'vue'
3+
4+
const props = defineProps({
5+
source: {
6+
type: String,
7+
required: true,
8+
},
9+
})
10+
11+
const decoded = computed(() => {
12+
return decodeURIComponent(props.source)
13+
})
14+
</script>
15+
16+
<template>
17+
<div class="example-source-code language-vue" v-html="decoded" />
18+
</template>
19+
20+
<style scoped>
21+
.example-source-code {
22+
overflow-x: auto;
23+
padding: 0 !important;
24+
margin: 0 !important;
25+
border-radius: 0 !important;
26+
z-index: 99;
27+
}
28+
29+
.language-vue{
30+
position: unset !important;
31+
}
32+
</style>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import Example from './Example.vue'
2+
3+
export {
4+
Example,
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export const GITHUB_URL = 'https://github.com/starter-collective/starter-lib-vue3'
2+
3+
export const GITHUB_EDIT_URL = `${GITHUB_URL}/-/tree/master/`
4+
5+
export const WEBSITE_URL = 'https://starter-lib-vue3.netlify.app/'

docs/.vitepress/config/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ export default defineConfig({
1414
// Vite config.
1515
// https://vitejs.dev
1616
vite: {
17+
resolve: {
18+
conditions: ['dev'],
19+
alias: {
20+
'starter-lib-vue3': '/src/index.ts',
21+
},
22+
},
1723
server: {
1824
host: true,
1925
port: 9865,

docs/.vitepress/config/shared.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { defineConfig } from 'vitepress'
2+
import { MarkdownPlugin } from '../plugins'
3+
import { GITHUB_URL, WEBSITE_URL } from './constants'
24

35
export const shared = defineConfig({
46
title: 'Vue3 Component Library Starter Template',
@@ -9,7 +11,7 @@ export const shared = defineConfig({
911
cleanUrls: true,
1012
metaChunk: true,
1113
sitemap: {
12-
hostname: 'https://starter-lib-vue3.netlify.app/',
14+
hostname: WEBSITE_URL,
1315
transformItems(items) {
1416
return items.filter(item => !item.url.includes('migration'))
1517
},
@@ -23,18 +25,19 @@ export const shared = defineConfig({
2325
['meta', { property: 'og:title', content: 'Vue3 Component Library Starter Template' }],
2426
['meta', { property: 'og:site_name', content: 'Vue3 Component Library Starter Template' }],
2527
['meta', { property: 'og:image', content: '/logo.png' }],
26-
['meta', { property: 'og:url', content: 'https://starter-lib-vue3.netlify.app/' }],
28+
['meta', { property: 'og:url', content: WEBSITE_URL }],
2729
],
2830
themeConfig: {
2931
logo: '/logo.png',
3032
socialLinks: [
31-
{ icon: 'github', link: 'https://github.com/starter-collective/starter-lib-vue3' },
33+
{ icon: 'github', link: GITHUB_URL },
3234
],
3335
search: {
3436
provider: 'local',
3537
},
3638
},
3739
markdown: {
40+
config: md => MarkdownPlugin(md),
3841
theme: {
3942
light: 'github-light',
4043
dark: 'github-dark',

docs/.vitepress/plugins/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './markdown'
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import type MarkdownIt from 'markdown-it'
2+
import type Renderer from 'markdown-it/lib/renderer.mjs'
3+
import type Token from 'markdown-it/lib/token.mjs'
4+
import { readFileSync } from 'node:fs'
5+
import { resolve } from 'node:path'
6+
import { cwd } from 'node:process'
7+
import markdownItContainer from 'markdown-it-container'
8+
import { createHighlighter } from 'shiki'
9+
10+
interface ContainerOpts {
11+
marker?: string
12+
validate?: (params: string) => boolean
13+
render?: (
14+
tokens: Token[],
15+
index: number,
16+
options: any,
17+
env: any,
18+
self: Renderer
19+
) => string
20+
}
21+
22+
export async function MarkdownPlugin(md: MarkdownIt): Promise<void> {
23+
const highlighter = await createHighlighter({
24+
themes: ['github-light', 'github-dark'],
25+
langs: ['vue', 'vue-html', 'typescript', 'javascript'],
26+
})
27+
28+
md.use(markdownItContainer, 'example', {
29+
validate(params) {
30+
// eslint-disable-next-line regexp/no-super-linear-backtracking
31+
return !!params.trim().match(/^example\s*(.*)$/)
32+
},
33+
render(tokens, idx) {
34+
if (tokens[idx].nesting === 1) {
35+
let source = ''
36+
const sourceFileToken = tokens[idx + 2]
37+
const sourceFile = sourceFileToken.children?.[0].content ?? ''
38+
if (sourceFileToken.type === 'inline') {
39+
source = readFileSync(
40+
resolve(cwd(), 'docs/examples', `${sourceFile}.vue`),
41+
'utf-8',
42+
)
43+
}
44+
if (!source) {
45+
throw new Error(`Incorrect source file: ${sourceFile}`)
46+
}
47+
const shikiSource = highlighter.codeToHtml(source, {
48+
lang: 'vue',
49+
themes: {
50+
light: 'github-light',
51+
dark: 'github-dark',
52+
},
53+
})
54+
return `<Example source="${encodeURIComponent(shikiSource)}" path="${sourceFile}" raw-source="${encodeURIComponent(source)}">`
55+
}
56+
else {
57+
return '</Example>'
58+
}
59+
},
60+
} as ContainerOpts)
61+
}

0 commit comments

Comments
 (0)