feat(@vben/docs): preview components are supported within documents (#4250)

This commit is contained in:
Vben
2024-08-27 23:22:34 +08:00
committed by GitHub
parent d47d051b19
commit cbf601581d
23 changed files with 558 additions and 38 deletions

View File

@@ -203,7 +203,7 @@ function nav(): DefaultTheme.NavItem[] {
},
{
link: '/commercial/technical-support',
text: '🦄 Technical Support',
text: '🦄 Tech Support',
},
{
link: '/sponsor/personal',

View File

@@ -2,12 +2,12 @@ import { withPwa } from '@vite-pwa/vitepress';
import { defineConfigWithTheme } from 'vitepress';
import { en } from './en.mts';
import { shard } from './shard.mts';
import { shared } from './shared.mts';
import { zh } from './zh.mts';
export default withPwa(
defineConfigWithTheme({
...shard,
...shared,
locales: {
en: {
label: 'English',

View File

@@ -0,0 +1,135 @@
import type { MarkdownEnv, MarkdownRenderer } from 'vitepress';
import crypto from 'node:crypto';
import { readdirSync } from 'node:fs';
import { join } from 'node:path';
export const rawPathRegexp =
// eslint-disable-next-line regexp/no-super-linear-backtracking, regexp/strict
/^(.+?(?:\.([\da-z]+))?)(#[\w-]+)?(?: ?{(\d+(?:[,-]\d+)*)? ?(\S+)?})? ?(?:\[(.+)])?$/;
function rawPathToToken(rawPath: string) {
const [
filepath = '',
extension = '',
region = '',
lines = '',
lang = '',
rawTitle = '',
] = (rawPathRegexp.exec(rawPath) || []).slice(1);
const title = rawTitle || filepath.split('/').pop() || '';
return { extension, filepath, lang, lines, region, title };
}
export const demoPreviewPlugin = (md: MarkdownRenderer) => {
md.core.ruler.after('inline', 'demo-preview', (state) => {
const insertComponentImport = (importString: string) => {
const index = state.tokens.findIndex(
(i) => i.type === 'html_block' && i.content.match(/<script setup>/g),
);
if (index === -1) {
const importComponent = new state.Token('html_block', '', 0);
importComponent.content = `<script setup>\n${importString}\n</script>\n`;
state.tokens.splice(0, 0, importComponent);
} else {
if (state.tokens[index]) {
const content = state.tokens[index].content;
state.tokens[index].content = content.replace(
'</script>',
`${importString}\n</script>`,
);
}
}
};
// Define the regular expression to match the desired pattern
const regex = /<DemoPreview[^>]*\sdir="([^"]*)"/g;
// Iterate through the Markdown content and replace the pattern
state.src = state.src.replaceAll(regex, (_match, dir) => {
const componentDir = join(process.cwd(), 'src', dir);
let childFiles: string[] = [];
let dirExists = true;
try {
childFiles =
readdirSync(componentDir, {
encoding: 'utf8',
recursive: false,
withFileTypes: false,
}) || [];
} catch {
dirExists = false;
}
if (!dirExists) {
return '';
}
const uniqueWord = generateContentHash(componentDir);
const ComponentName = `DemoComponent_${uniqueWord}`;
insertComponentImport(
`import ${ComponentName} from '${componentDir}/index.vue'`,
);
const { path: _path } = state.env as MarkdownEnv;
const index = state.tokens.findIndex((i) => i.content.match(regex));
if (!state.tokens[index]) {
return '';
}
state.tokens[index].content =
`<DemoPreview files="${encodeURIComponent(JSON.stringify(childFiles))}" ><${ComponentName}/>
`;
const _dummyToken = new state.Token('', '', 0);
const tokenArray: Array<typeof _dummyToken> = [];
childFiles.forEach((filename) => {
// const slotName = filename.replace(extname(filename), '');
const templateStart = new state.Token('html_inline', '', 0);
templateStart.content = `<template #${filename}>`;
tokenArray.push(templateStart);
const resolvedPath = join(componentDir, filename);
const { extension, filepath, lang, lines, title } =
rawPathToToken(resolvedPath);
// Add code tokens for each line
const token = new state.Token('fence', 'code', 0);
token.info = `${lang || extension}${lines ? `{${lines}}` : ''}${
title ? `[${title}]` : ''
}`;
token.content = `<<< ${filepath}`;
(token as any).src = [resolvedPath];
tokenArray.push(token);
const templateEnd = new state.Token('html_inline', '', 0);
templateEnd.content = '</template>';
tokenArray.push(templateEnd);
});
const endTag = new state.Token('html_inline', '', 0);
endTag.content = '</DemoPreview>';
tokenArray.push(endTag);
state.tokens.splice(index + 1, 0, ...tokenArray);
// console.log(
// state.md.renderer.render(state.tokens, state?.options ?? [], state.env),
// );
return '';
});
});
};
function generateContentHash(input: string, length: number = 10): string {
// 使用 SHA-256 生成哈希值
const hash = crypto.createHash('sha256').update(input).digest('hex');
// 将哈希值转换为 Base36 编码,并取指定长度的字符作为结果
return Number.parseInt(hash, 16).toString(36).slice(0, length);
}

View File

@@ -1,4 +1,5 @@
import type { PwaOptions } from '@vite-pwa/vitepress';
import type { HeadConfig } from 'vitepress';
import { resolve } from 'node:path';
@@ -6,12 +7,20 @@ import {
GitChangelog,
GitChangelogMarkdownSection,
} from '@nolebase/vitepress-plugin-git-changelog/vite';
import { defineConfig, type HeadConfig } from 'vitepress';
import tailwind from 'tailwindcss';
import { defineConfig, postcssIsolateStyles } from 'vitepress';
import { demoPreviewPlugin } from './plugins/demo-preview';
import { search as zhSearch } from './zh.mts';
export const shard = defineConfig({
export const shared = defineConfig({
appearance: 'dark',
head: head(),
markdown: {
preConfig(md) {
md.use(demoPreviewPlugin);
},
},
pwa: pwa(),
srcDir: 'src',
themeConfig: {
@@ -36,11 +45,34 @@ export const shard = defineConfig({
chunkSizeWarningLimit: Infinity,
minify: 'terser',
},
css: {
postcss: {
plugins: [
tailwind(),
postcssIsolateStyles({ includeFiles: [/vp-doc\.css/] }),
],
},
},
json: {
stringify: true,
},
plugins: [
GitChangelog({
mapAuthors: [
{
mapByNameAliases: ['Vben'],
name: 'vben',
username: 'anncwb',
},
{
name: 'vince',
username: 'vince292007',
},
{
name: 'Li Kui',
username: 'likui628',
},
],
repoURL: () => 'https://github.com/vbenjs/vue-vben-admin',
}),
GitChangelogMarkdownSection(),

View File

@@ -38,6 +38,7 @@ export const zh = defineConfig({
sidebar: {
'/commercial/': { base: '/commercial/', items: sidebarCommercial() },
'/components/': { base: '/components/', items: sidebarComponents() },
'/guide/': { base: '/guide/', items: sidebarGuide() },
},
sidebarMenuLabel: '菜单',
@@ -60,6 +61,11 @@ function sidebarGuide(): DefaultTheme.SidebarItem[] {
},
{ link: 'introduction/quick-start', text: '快速开始' },
{ link: 'introduction/thin', text: '精简版本' },
{
base: '/',
link: 'components/introduction',
text: '组件文档',
},
],
},
{
@@ -117,7 +123,7 @@ function sidebarCommercial(): DefaultTheme.SidebarItem[] {
return [
{
link: 'community',
text: '社区交流',
text: '社区',
},
{
link: 'technical-support',
@@ -130,6 +136,30 @@ function sidebarCommercial(): DefaultTheme.SidebarItem[] {
];
}
function sidebarComponents(): DefaultTheme.SidebarItem[] {
return [
{
text: '组件',
items: [
{
link: 'introduction',
text: '介绍',
},
],
},
{
collapsed: false,
text: '通用组件',
items: [
{
link: 'common-ui/vben-modal',
text: 'Modal 弹窗',
},
],
},
];
}
function nav(): DefaultTheme.NavItem[] {
return [
{
@@ -138,28 +168,10 @@ function nav(): DefaultTheme.NavItem[] {
{
link: '/guide/introduction/vben',
text: '指南',
// items: [
// {
// link: '/guide/introduction/vben',
// text: '简介',
// },
// {
// link: '/guide/essentials/concept',
// text: '基础',
// },
// {
// link: '/guide/in-depth/layout',
// text: '深入',
// },
// {
// link: '/guide/project/standard',
// text: '工程',
// },
// {
// link: '/guide/other/project-update',
// text: '其他',
// },
// ],
},
{
link: '/components/introduction',
text: '组件',
},
{
text: '历史版本',
@@ -234,7 +246,7 @@ function nav(): DefaultTheme.NavItem[] {
},
{
link: '/commercial/community',
text: '👨‍👦‍👦 社区交流',
text: '👨‍👦‍👦 社区',
// items: [
// {
// link: 'https://qun.qq.com/qqweb/qunpro/share?_wv=3&_wwv=128&appChannel=share&inviteCode=22ySzj7pKiw&businessType=9&from=246610&biz=ka&mainSourceId=share&subSourceId=others&jumpsource=shorturl#/pc',