Skip to content

Commit 546744f

Browse files
alexzhang1030sxzz
andauthored
feat(docs): add code block (#96)
Co-authored-by: 三咲智子 <sxzz@sxzz.moe>
1 parent b57091b commit 546744f

18 files changed

Lines changed: 347 additions & 16 deletions

docs/.vitepress/config.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { defineConfig } from 'vitepress'
2-
import { nav, sidebar } from './data'
2+
import { markdownConfig, nav, sidebar } from './configs'
33

44
export default defineConfig({
55
lang: 'en-US',
@@ -37,10 +37,7 @@ gtag('config', 'G-29NKGSL23C');`,
3737
description: 'Explore and extend more macros and syntax sugar to Vue.',
3838
lastUpdated: true,
3939
cleanUrls: 'with-subfolders',
40-
markdown: {
41-
theme: 'material-palenight',
42-
lineNumbers: true,
43-
},
40+
markdown: markdownConfig,
4441

4542
vue: {
4643
reactivityTransform: true,

docs/.vitepress/configs/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './navs'
2+
export * from './markdown'
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { useCodeGroup, useCodeGroupItem } from '../theme/components/markdown'
2+
import type { MarkdownOptions } from 'vitepress'
3+
4+
/**
5+
* vitepress markdown config
6+
* @see https://vitepress.vuejs.org/config/app-configs.html#markdown
7+
*/
8+
export const markdownConfig: MarkdownOptions = {
9+
lineNumbers: true,
10+
theme: 'material-palenight',
11+
config: (md) => {
12+
md.use(useCodeGroup.container, useCodeGroup.type, {
13+
render: useCodeGroup.render,
14+
})
15+
md.use(useCodeGroupItem.container, useCodeGroupItem.type, {
16+
render: useCodeGroupItem.render,
17+
})
18+
},
19+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { defineComponent, h, ref } from 'vue'
2+
import type { Component, ComponentOptions, VNode } from 'vue'
3+
4+
export const CodeGroup: ComponentOptions = defineComponent({
5+
name: 'CodeGroup',
6+
7+
setup(_, { slots }) {
8+
// index of current active item
9+
const activeIndex = ref(-1)
10+
11+
return () => {
12+
// NOTICE: here we put the `slots.default()` inside the render function to make
13+
// the slots reactive, otherwise the slot content won't be changed once the
14+
// `setup()` function of current component is called
15+
16+
// get children code-group-item
17+
const items = (slots.default?.() || [])
18+
.filter((vnode) => (vnode.type as Component).name === 'CodeGroupItem')
19+
.map((vnode) => {
20+
if (vnode.props === null) vnode.props = {}
21+
22+
return vnode as VNode & { props: Exclude<VNode['props'], null> }
23+
})
24+
25+
// do not render anything if there is no code-group-item
26+
if (items.length === 0) return null
27+
28+
if (activeIndex.value < 0 || activeIndex.value > items.length - 1) {
29+
// if `activeIndex` is invalid
30+
31+
// find the index of the code-group-item with `active` props
32+
activeIndex.value = items.findIndex(
33+
(vnode) => vnode.props.active === '' || vnode.props.active === true
34+
)
35+
36+
// if there is no `active` props on code-group-item, set the first item active
37+
if (activeIndex.value === -1) activeIndex.value = 0
38+
} else {
39+
// set the active item
40+
items.forEach((vnode, i) => {
41+
vnode.props.active = i === activeIndex.value
42+
})
43+
}
44+
45+
return h('div', { class: 'code-group' }, [
46+
h(
47+
'div',
48+
{ class: 'code-group__nav' },
49+
h(
50+
'ul',
51+
{ class: 'code-group__ul' },
52+
items.map((vnode, i) => {
53+
const isActive = i === activeIndex.value
54+
55+
return h(
56+
'li',
57+
{ class: 'code-group__li' },
58+
h(
59+
'button',
60+
{
61+
class: {
62+
'code-group__nav-tab': true,
63+
'code-group__nav-tab-active': isActive,
64+
},
65+
ariaPressed: isActive,
66+
ariaExpanded: isActive,
67+
onClick: () => (activeIndex.value = i),
68+
},
69+
vnode.props.title
70+
)
71+
)
72+
})
73+
)
74+
),
75+
items,
76+
])
77+
}
78+
},
79+
})
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<script lang="tsx">
2+
import { defineComponent, ref } from 'vue'
3+
import type { Component } from 'vue'
4+
export default defineComponent({
5+
name: 'CodeGroup',
6+
setup(_, { slots }) {
7+
const currentIndex = ref(0)
8+
return () => {
9+
// use jsx instead of template because we need to change slots
10+
// so jsx could handle but template not
11+
const items = (slots.default?.() ?? [])
12+
.filter((vnode) => (vnode.type as Component).name === 'CodeGroupItem')
13+
.map((vnode) => {
14+
vnode.props = vnode.props ?? {}
15+
return vnode
16+
})
17+
18+
items.forEach((item, index) => {
19+
item.props!.active = index === currentIndex.value
20+
})
21+
22+
return (
23+
<div rounded="2" overflow="hidden" my="4" w="full">
24+
<div
25+
w="full"
26+
flex="~"
27+
justify="start"
28+
items="center"
29+
gap="10px"
30+
h="12"
31+
dark:bg="#27272A"
32+
bg="#F5F5F5"
33+
px="3"
34+
py="4"
35+
box="border"
36+
>
37+
{items.map((item, index) => (
38+
<div
39+
key={index}
40+
cursor="pointer"
41+
text="sm black"
42+
px="2"
43+
py="5px"
44+
tracking="tight"
45+
transition="colors"
46+
rounded="6px"
47+
box="border"
48+
class={[
49+
'dark:color-white hover:bg-#E5E5E5 dark:hover:bg-#3A3A3D',
50+
{
51+
active: index === currentIndex.value,
52+
},
53+
]}
54+
onClick={() => (currentIndex.value = index)}
55+
>
56+
{item.props?.title}
57+
</div>
58+
))}
59+
</div>
60+
<div>{items}</div>
61+
</div>
62+
)
63+
}
64+
},
65+
})
66+
</script>
67+
68+
<style scoped>
69+
.active {
70+
--at-apply: dark:bg-#3f3f46 bg-#e6e6e6;
71+
}
72+
</style>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<script setup lang="ts">
2+
defineOptions({
3+
name: 'CodeGroupItem',
4+
inheritAttrs: false,
5+
})
6+
7+
defineProps<{
8+
title: string
9+
active: boolean
10+
}>()
11+
</script>
12+
<template>
13+
<div v-if="active" class="content"><slot /></div>
14+
</template>
15+
<style scoped>
16+
.content :deep(div[class*='language-']) {
17+
border-radius: 0;
18+
border-top: none;
19+
margin: 0;
20+
}
21+
</style>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { useMarkdownContainer } from './plugins/container'
2+
3+
export const useCodeGroup = useMarkdownContainer({
4+
type: 'code-group',
5+
before: () => '<CodeGroup>\n',
6+
after: () => '</CodeGroup>\n',
7+
})
8+
9+
export const useCodeGroupItem = useMarkdownContainer({
10+
type: 'code-group-item',
11+
before: (info: string) => `<CodeGroupItem title="${info}">\n`,
12+
after: () => '</CodeGroupItem>\n',
13+
})
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import container from 'markdown-it-container'
2+
3+
type RenderPlaceFunction = (info: string) => string
4+
5+
interface ContainerPluginOptions {
6+
/**
7+
* The type of the container
8+
*
9+
* It would be used as the `name` of the container
10+
*
11+
* @see https://github.com/markdown-it/markdown-it-container#api
12+
*/
13+
type: string
14+
15+
/**
16+
* A function to render the starting tag of the container.
17+
*
18+
* This option will not take effect if you don't specify the `after` option.
19+
*/
20+
before: RenderPlaceFunction
21+
22+
/**
23+
* A function to render the ending tag of the container.
24+
*
25+
* This option will not take effect if you don't specify the `before` option.
26+
*/
27+
after: RenderPlaceFunction
28+
}
29+
30+
/** Powered by vuepress-next */
31+
export const useMarkdownContainer = ({
32+
type,
33+
after,
34+
before,
35+
}: ContainerPluginOptions) => {
36+
const infoStack: string[] = []
37+
const render = (tokens: any, index: number): string => {
38+
const token = tokens[index]
39+
if (token.nesting === 1) {
40+
// resolve info (title)
41+
const info = token.info.trim().slice(type.length).trim()
42+
infoStack.push(info)
43+
return before(info)
44+
} else {
45+
// `after` tag
46+
47+
// pop the info from stack
48+
const info = infoStack.pop() || ''
49+
50+
// render
51+
return after(info)
52+
}
53+
}
54+
return {
55+
container,
56+
type,
57+
render,
58+
}
59+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
declare module 'markdown-it-container' {
2+
import type { PluginWithParams } from 'markdown-it'
3+
const container: PluginWithParams
4+
export = container
5+
}

0 commit comments

Comments
 (0)