diff --git a/.vuepress/config/index.ts b/.vuepress/config/index.ts index dcbbbe1..2b18854 100644 --- a/.vuepress/config/index.ts +++ b/.vuepress/config/index.ts @@ -1,21 +1,6 @@ /** 站点配置统一导出入口 */ -export { - copyrightHtmlStr, - footerHtmlStr, -} from './footer' -export { sitePathsPlugin } from './plugins/site-paths' -export { seoHead, siteHead } from './seo' -export { - navbarConfig, - sidebarConfig, -} from './sidebar' -export { - homeUrl, - pkg, - siteBase, - siteDescription, - siteLogo, - siteLogoUrl, - siteOrigin, - siteTitle, -} from './site' +export * from './footer' +export * from './seo' +export * from './sidebar' +export * from './sidebar.resolve' +export * from './site' diff --git a/.vuepress/config/plugins/resolve-sidebar-links.ts b/.vuepress/config/plugins/resolve-sidebar-links.ts deleted file mode 100644 index 5e986e7..0000000 --- a/.vuepress/config/plugins/resolve-sidebar-links.ts +++ /dev/null @@ -1,82 +0,0 @@ -import type { AutoLinkOptions, SidebarGroupOptions, SidebarItemOptions } from 'vuepress-theme-hope' -import { VipNodeJS } from '@142vip/utils' - -/** 学科目录,与 `docs/` 下子目录及 sidebar 配置键一致 */ -export type SidebarSubject = 'ds' | 'os' | 'ccp' | 'cn' - -/** 各学科 `*.sidebar.ts` 的链接项(避免在 docs 中直接依赖 hope 类型) */ -export interface SubjectSidebarLink { - text: string - link: string -} - -/** 各学科 `*.sidebar.ts` 的分组项 */ -export interface SubjectSidebarGroup { - text: string - prefix?: string - collapsible?: boolean - expanded?: boolean - children: SubjectSidebarLink[] -} - -export type SubjectSidebarConfig = SubjectSidebarGroup[] - -function readPermalink(mdFilePath: string): string | null { - if (!VipNodeJS.existPath(mdFilePath)) { - return null - } - - for (const line of VipNodeJS.readFileToStrByUTF8(mdFilePath).split('\n')) { - if (line.startsWith('permalink:')) { - return line.slice('permalink:'.length).trim() - } - } - - return null -} - -function resolveSidebarLink( - item: SubjectSidebarLink, - subject: SidebarSubject, - groupPrefix: string, -): AutoLinkOptions { - if (!item.link.endsWith('.md')) { - return item - } - - const mdRelativePath = groupPrefix - ? `${groupPrefix}/${item.link}` - : item.link - const permalink = readPermalink( - VipNodeJS.pathJoin(VipNodeJS.getProcessCwd(), 'docs', subject, mdRelativePath), - ) - - if (permalink == null) { - return item - } - - return { ...item, link: permalink } -} - -function resolveSidebarGroup( - group: SubjectSidebarGroup, - subject: SidebarSubject, -): SidebarGroupOptions { - const groupPrefix = group.prefix ?? '' - - return { - ...group, - children: group.children.map(child => resolveSidebarLink(child, subject, groupPrefix)), - } -} - -/** - * Hope 手动 sidebar 的 `.md` 链接按「prefix + 文件名」推断路由,不会读取 `permalink`。 - * 官方推荐在 `link` 中直接写 permalink;此处仅在配置阶段自动补齐,避免维护成本。 - */ -export function resolveSidebarPermalinks( - sidebar: SubjectSidebarConfig, - subject: SidebarSubject, -): SidebarItemOptions[] { - return sidebar.map(group => resolveSidebarGroup(group, subject)) -} diff --git a/.vuepress/config/plugins/site-paths.ts b/.vuepress/config/plugins/site-paths.ts deleted file mode 100644 index 755e0f7..0000000 --- a/.vuepress/config/plugins/site-paths.ts +++ /dev/null @@ -1,52 +0,0 @@ -import type { App, PageOptions, Plugin } from '@vuepress/core' -import { VipNodeJS } from '@142vip/utils' -import { ensureLeadingSlash, inferRoutePath } from '@vuepress/shared' - -const DOCS_PREFIX = 'docs/' - -/** - * 源目录为项目根时,将 `docs/` 下的 Markdown 映射为与原 `docs` 源目录一致的路由。 - * 例如:`docs/ds/README.md` → `/ds/`,避免 URL 出现 `/docs/` 前缀。 - */ -function getDocsRoutePath(filePath: string, sourceDir: string): string | null { - const normalizedSource = `${VipNodeJS.pathResolve(sourceDir)}/` - const normalizedFile = VipNodeJS.pathResolve(filePath) - if (!normalizedFile.startsWith(normalizedSource)) { - return null - } - - const filePathRelative = normalizedFile - .slice(normalizedSource.length) - .replaceAll('\\', '/') - if (!filePathRelative.startsWith(DOCS_PREFIX)) { - return null - } - - const routePath = filePathRelative.slice(DOCS_PREFIX.length) - return inferRoutePath(ensureLeadingSlash(routePath)) -} - -export function sitePathsPlugin(): Plugin { - return { - name: 'vip-site-paths', - extendsPageOptions(options: PageOptions, app: App) { - if (options.filePath == null) { - return - } - - const inferredPath = getDocsRoutePath(options.filePath, app.dir.source()) - if (inferredPath == null) { - return - } - - if (options.frontmatter?.permalink != null) { - return - } - - options.frontmatter = { - ...options.frontmatter, - permalink: inferredPath, - } - }, - } -} diff --git a/.vuepress/config/sidebar.resolve.ts b/.vuepress/config/sidebar.resolve.ts new file mode 100644 index 0000000..80e7046 --- /dev/null +++ b/.vuepress/config/sidebar.resolve.ts @@ -0,0 +1,72 @@ +import type { + DocsSidebarConfig, + DocsSidebarGroup, + DocsSidebarItem, +} from '@142vip/vuepress' +import type { AutoLinkOptions, SidebarItemOptions } from 'vuepress-theme-hope' +import { VipNodeJS } from '@142vip/utils' + +function readPermalink(mdFilePath: string): string | null { + if (!VipNodeJS.existPath(mdFilePath)) { + return null + } + + for (const line of VipNodeJS.readFileToStrByUTF8(mdFilePath).split('\n')) { + if (line.startsWith('permalink:')) { + return line.slice('permalink:'.length).trim() + } + } + + return null +} + +function resolveMdLink( + link: string, + docsBasePath: string, + groupPrefix: string, +): string { + const mdRelativePath = groupPrefix + ? `${groupPrefix}/${link}` + : link + const permalink = readPermalink( + VipNodeJS.pathJoin(VipNodeJS.getProcessCwd(), 'docs', docsBasePath, mdRelativePath), + ) + + return permalink ?? link +} + +/** + * Hope 手动 sidebar 的 `.md` 链接按「prefix + 文件名」推断路由,**不会**读取 frontmatter `permalink`。 + * 本仓库页面 permalink 与文件名不一致时,在配置阶段按文件解析并替换 `link`。 + * + * 侧栏结构类型见 `@142vip/vuepress` 的 `DocsSidebar*`;本函数为站点侧解析工具。 + * + * @param sidebar 模块侧边栏原始配置(可含 `.md` 链接) + * @param docsBasePath `docs/` 下的相对目录,如 `ds`、`os` + */ +export function resolveSidebarPermalinks( + sidebar: DocsSidebarConfig, + docsBasePath: string, +): SidebarItemOptions[] { + const walk = (items: DocsSidebarItem[], groupPrefix = ''): SidebarItemOptions[] => + items.map((item) => { + if (Array.isArray((item as DocsSidebarGroup).children)) { + const group = item as DocsSidebarGroup + return { + ...group, + children: walk(group.children, group.prefix ?? ''), + } + } + + if (!item.link?.endsWith('.md')) { + return item as AutoLinkOptions + } + + return { + ...item, + link: resolveMdLink(item.link, docsBasePath, groupPrefix), + } + }) + + return walk(sidebar) +} diff --git a/.vuepress/config/sidebar.ts b/.vuepress/config/sidebar.ts index 77fe210..ea8195b 100644 --- a/.vuepress/config/sidebar.ts +++ b/.vuepress/config/sidebar.ts @@ -3,7 +3,7 @@ import { cppSidebar } from '../../docs/ccp/cpp.sidebar' import { cnSidebar } from '../../docs/cn/cn.sidebar' import { dsSidebar } from '../../docs/ds/ds.sidebar' import { osSidebar } from '../../docs/os/os.sidebar' -import { resolveSidebarPermalinks } from './plugins/resolve-sidebar-links' +import { resolveSidebarPermalinks } from './sidebar.resolve' /** 顶部导航栏 */ export const navbarConfig = defineVipNavbarConfig([ @@ -33,7 +33,11 @@ export const navbarConfig = defineVipNavbarConfig([ }, ]) -/** 各学科侧边栏(路径与 `docs/` 下目录对应) */ +/** + * 各学科侧边栏 + * - 路径键与 Hope 路由前缀一致 + * - `.md` 链接通过 `resolveSidebarPermalinks` 自动对齐 frontmatter `permalink` + */ export const sidebarConfig = defineVipSidebarConfig({ '/ds': resolveSidebarPermalinks(dsSidebar, 'ds'), '/os': resolveSidebarPermalinks(osSidebar, 'os'), diff --git a/.vuepress/config/site.ts b/.vuepress/config/site.ts index fc48532..30685b3 100644 --- a/.vuepress/config/site.ts +++ b/.vuepress/config/site.ts @@ -22,6 +22,9 @@ export const siteBase = vipDocSite.getBase(pkg.name) /** 站点首页绝对地址 */ export const homeUrl = new URL(siteBase || '/', `${siteOrigin}/`).href +/** 本地开发端口 */ +export const siteDevPort = 4200 + /** 站点 Logo(深浅色导航栏通用,`.vuepress/public/logo.png`) */ export const siteLogo = '/logo.png' diff --git a/docs/ccp/cpp.sidebar.ts b/docs/ccp/cpp.sidebar.ts index 43b6b38..10e1045 100644 --- a/docs/ccp/cpp.sidebar.ts +++ b/docs/ccp/cpp.sidebar.ts @@ -1,9 +1,9 @@ -import type { SubjectSidebarConfig } from '../../.vuepress/config/plugins/resolve-sidebar-links' +import type { DocsSidebarConfig } from '@142vip/vuepress' /** * 计算机组成原理 */ -export const cppSidebar: SubjectSidebarConfig = [ +export const cppSidebar: DocsSidebarConfig = [ { text: '计算机引论', prefix: '计算机引论', diff --git a/docs/cn/cn.sidebar.ts b/docs/cn/cn.sidebar.ts index 56c3c4a..60d85ce 100644 --- a/docs/cn/cn.sidebar.ts +++ b/docs/cn/cn.sidebar.ts @@ -1,9 +1,9 @@ -import type { SubjectSidebarConfig } from '../../.vuepress/config/plugins/resolve-sidebar-links' +import type { DocsSidebarConfig } from '@142vip/vuepress' /** * 计算机网络 */ -export const cnSidebar: SubjectSidebarConfig = [ +export const cnSidebar: DocsSidebarConfig = [ { text: '体系结构', prefix: '体系结构', diff --git a/docs/ds/ds.sidebar.ts b/docs/ds/ds.sidebar.ts index e81c4cb..be83738 100644 --- a/docs/ds/ds.sidebar.ts +++ b/docs/ds/ds.sidebar.ts @@ -1,9 +1,9 @@ -import type { SubjectSidebarConfig } from '../../.vuepress/config/plugins/resolve-sidebar-links' +import type { DocsSidebarConfig } from '@142vip/vuepress' /** * 数据结构 */ -export const dsSidebar: SubjectSidebarConfig = [ +export const dsSidebar: DocsSidebarConfig = [ { text: '基础入门', prefix: '基础入门', diff --git a/docs/os/os.sidebar.ts b/docs/os/os.sidebar.ts index 06700d2..77e3c99 100644 --- a/docs/os/os.sidebar.ts +++ b/docs/os/os.sidebar.ts @@ -1,9 +1,9 @@ -import type { SubjectSidebarConfig } from '../../.vuepress/config/plugins/resolve-sidebar-links' +import type { DocsSidebarConfig } from '@142vip/vuepress' /** * 操作系统 */ -export const osSidebar: SubjectSidebarConfig = [ +export const osSidebar: DocsSidebarConfig = [ { text: '系统概述', prefix: '系统概述', diff --git a/vuepress.config.ts b/vuepress.config.ts index 48f2c92..e504f73 100644 --- a/vuepress.config.ts +++ b/vuepress.config.ts @@ -1,9 +1,3 @@ -import { OPEN_SOURCE_ADDRESS, OPEN_SOURCE_AUTHOR } from '@142vip/open-source' -import { - defineVipVuepressConfig, - getVipHopeTheme, - handleImportCodePath, -} from '@142vip/vuepress' import { copyrightHtmlStr, footerHtmlStr, @@ -12,25 +6,38 @@ import { sidebarConfig, siteBase, siteDescription, + siteDevPort, siteHead, siteLogo, - sitePathsPlugin, + siteOrigin, siteTitle, -} from './.vuepress/config' +} from '.vuepress/config' +import { + OPEN_SOURCE_ADDRESS, + OPEN_SOURCE_AUTHOR, +} from '@142vip/open-source' +import { + createVipViteBuildPlugin, + defineVipVuepressConfig, + getVipHopeTheme, + handleImportCodePath, +} from '@142vip/vuepress' /** * VuePress 站点配置 * - * 注意: - * - 环境变量 `NEED_PROXY=true` 时,base 为 `/{pkg.name}/`,否则为 `/` - * - `locales` / `lang` / `bundler` 等由 `defineVipVuepressConfig` 默认注入 - * - 导航、SEO、页脚等拆分见 `.vuepress/config/` + * 对齐 `@142vip/vuepress`(见 core-x `packages/vuepress`): + * - `locales` / `lang` / `bundler` 由 `defineVipVuepressConfig` 默认注入 + * - `author` / `docsDir` / `docsBranch` / `contributors` 由 `getVipHopeTheme` 默认注入 + * - 大文档站 chunk 告警用包内 `createVipViteBuildPlugin` + * - 文档路由依赖各 md 的 frontmatter `permalink` + * - 侧边栏 `.md` 链接经 `resolveSidebarPermalinks` 对齐 permalink */ export default defineVipVuepressConfig({ base: siteBase, title: siteTitle, description: siteDescription, - port: 4200, + port: siteDevPort, pagePatterns: [ 'docs/**/*.md', '!node_modules', @@ -39,7 +46,7 @@ export default defineVipVuepressConfig({ ], head: siteHead, plugins: [ - sitePathsPlugin(), + createVipViteBuildPlugin(), ], markdown: { importCode: { @@ -65,23 +72,16 @@ export default defineVipVuepressConfig({ end: ['Language', 'Outlook', 'Repo', 'Search'], }, logo: siteLogo, - hostname: OPEN_SOURCE_ADDRESS.HOME_PAGE_DOMAIN_408, + hostname: siteOrigin, // 页脚 footer: footerHtmlStr, // 版权 copyright: copyrightHtmlStr, - // 仓库 142vip/408CSFamily repo: `${OPEN_SOURCE_ADDRESS.GITHUB_ORGANIZATION_NAME}/${pkg.name}`, - // 作者信息 - author: OPEN_SOURCE_AUTHOR, - // 主题布局选项 docsRepo: OPEN_SOURCE_ADDRESS.GITHUB_REPO_408, - contributors: true, - // 插件 plugins: { - // 水印 watermark: { - enabled: false, + enabled: true, watermarkOptions: { content: OPEN_SOURCE_AUTHOR.name, }, @@ -89,6 +89,5 @@ export default defineVipVuepressConfig({ }, }), }, { - // 浏览器控制台打印版本与构建时间 appBuildLog: { version: pkg.version }, })