From 8ea871963dc76ab5e19ea3d73a1b9489befdbbe7 Mon Sep 17 00:00:00 2001 From: Melvin Chia Date: Tue, 30 Dec 2025 22:07:35 +0800 Subject: [PATCH] feat(docs): changelog page overhaul --- docs/plugins/mdxListCountsPlugin.ts | 68 ++++++++++++++ docs/src/components/Boilerplate/index.tsx | 5 +- docs/src/components/Rightbar.tsx | 6 ++ .../src/contents/04.progress/01.changelog.mdx | 7 -- .../components/ChangelogEntries.tsx | 27 ++++-- .../04.progress/components/Version.tsx | 91 ++++++++++++++----- docs/src/vite-env.d.ts | 5 + docs/tsconfig.node.json | 7 +- docs/vite.config.ts | 5 +- 9 files changed, 178 insertions(+), 43 deletions(-) create mode 100644 docs/plugins/mdxListCountsPlugin.ts diff --git a/docs/plugins/mdxListCountsPlugin.ts b/docs/plugins/mdxListCountsPlugin.ts new file mode 100644 index 000000000..8753d92a3 --- /dev/null +++ b/docs/plugins/mdxListCountsPlugin.ts @@ -0,0 +1,68 @@ +import fs from 'node:fs' +import path from 'node:path' +import type { Plugin } from 'vite' + +const VIRTUAL_MODULE_ID = 'virtual:mdx-list-counts' +const RESOLVED_VIRTUAL_MODULE_ID = '\0' + VIRTUAL_MODULE_ID + +function countListItems(mdxSource: string): number { + const listItemRegex = /^[\t ]*- /gm + const matches = mdxSource.match(listItemRegex) + return matches ? matches.length : 0 +} + +function mdxListCountsPlugin(): Plugin { + return { + name: 'vite-plugin-mdx-list-counts', + resolveId(id) { + if (id === VIRTUAL_MODULE_ID) { + return RESOLVED_VIRTUAL_MODULE_ID + } + }, + load(id) { + if (id === RESOLVED_VIRTUAL_MODULE_ID) { + const versionsDir = path.resolve( + __dirname, + '../src/contents/04.progress/versions' + ) + const counts: Record = {} + + // Read all MDX files recursively + function readMdxFiles(dir: string, basePath = '') { + const entries = fs.readdirSync(dir, { withFileTypes: true }) + + for (const entry of entries) { + const fullPath = path.join(dir, entry.name) + const relativePath = path.join(basePath, entry.name) + + if (entry.isDirectory()) { + readMdxFiles(fullPath, relativePath) + } else if (entry.name.endsWith('.mdx')) { + const content = fs.readFileSync(fullPath, 'utf-8') + const key = `../versions/${relativePath.replace(/\\/g, '/')}` + counts[key] = countListItems(content) + } + } + } + + readMdxFiles(versionsDir) + + return `export default ${JSON.stringify(counts)};` + } + }, + handleHotUpdate({ file, server }) { + // Invalidate the virtual module when any MDX file changes + if (file.endsWith('.mdx') && file.includes('versions')) { + const module = server.moduleGraph.getModuleById( + RESOLVED_VIRTUAL_MODULE_ID + ) + if (module) { + server.moduleGraph.invalidateModule(module) + return [module] + } + } + } + } +} + +export default mdxListCountsPlugin diff --git a/docs/src/components/Boilerplate/index.tsx b/docs/src/components/Boilerplate/index.tsx index d2cd8eb0f..937560800 100644 --- a/docs/src/components/Boilerplate/index.tsx +++ b/docs/src/components/Boilerplate/index.tsx @@ -3,6 +3,7 @@ import { useEffect } from 'react' import Scrollbars from 'react-custom-scrollbars' import { useLocation } from 'shared' +import { BLACKLISTED_PAGES } from '../Rightbar' import NavigationBar from './components/NavigationBar' function Boilerplate({ children }: { children: React.ReactNode }) { @@ -40,7 +41,9 @@ function Boilerplate({ children }: { children: React.ReactNode }) { /> )} > -
+
location.pathname.startsWith(page)) ? '' : 'lg:w-[calc(100%-20rem)]'}`} + > {children}
diff --git a/docs/src/components/Rightbar.tsx b/docs/src/components/Rightbar.tsx index 97375bf6d..23def6553 100644 --- a/docs/src/components/Rightbar.tsx +++ b/docs/src/components/Rightbar.tsx @@ -3,6 +3,8 @@ import _ from 'lodash' import { useEffect, useRef, useState } from 'react' import { useLocation } from 'shared' +export const BLACKLISTED_PAGES = ['/progress/changelog'] + function Rightbar() { const [allSections, setAllSections] = useState([]) @@ -135,6 +137,10 @@ function Rightbar() { }, 1000) // 1 second delay to allow scroll to finish } + if (BLACKLISTED_PAGES.some(page => location.pathname.startsWith(page))) { + return null + } + return (
) } diff --git a/docs/src/vite-env.d.ts b/docs/src/vite-env.d.ts index 11f02fe2a..70ae257a4 100644 --- a/docs/src/vite-env.d.ts +++ b/docs/src/vite-env.d.ts @@ -1 +1,6 @@ /// + +declare module 'virtual:mdx-list-counts' { + const counts: Record + export default counts +} diff --git a/docs/tsconfig.node.json b/docs/tsconfig.node.json index 97ede7ee6..33ac3ddbf 100644 --- a/docs/tsconfig.node.json +++ b/docs/tsconfig.node.json @@ -7,5 +7,8 @@ "allowSyntheticDefaultImports": true, "strict": true }, - "include": ["vite.config.ts"] -} + "include": [ + "vite.config.ts", + "plugins/**/*.ts" + ] +} \ No newline at end of file diff --git a/docs/vite.config.ts b/docs/vite.config.ts index 996abafe7..e8fb16701 100644 --- a/docs/vite.config.ts +++ b/docs/vite.config.ts @@ -2,17 +2,18 @@ import mdx, { Options } from '@mdx-js/rollup' import tailwindcss from '@tailwindcss/vite' import react from '@vitejs/plugin-react' import path from 'node:path' -import rehypeHighlight from 'rehype-highlight' import remarkGfm from 'remark-gfm' import { defineConfig } from 'vite' +import mdxListCountsPlugin from './plugins/mdxListCountsPlugin' + const options: Options = { remarkPlugins: [remarkGfm] } // https://vitejs.dev/config/ export default defineConfig({ - plugins: [react(), mdx(options), tailwindcss()], + plugins: [react(), mdx(options), tailwindcss(), mdxListCountsPlugin()], resolve: { alias: { '@': path.resolve(__dirname, './src')