From 009208f60db82be15469637e82e62bb1310281dc Mon Sep 17 00:00:00 2001 From: flyingcys Date: Thu, 2 Oct 2025 16:37:34 +0800 Subject: [PATCH] fix markdown UI renders --- package-lock.json | 183 ++++++++++++++++++++++++++++++++++-- package.json | 13 ++- src/markdown/index.ts | 3 + src/markdown/renderer.ts | 149 +++++++++++++++++++++++++++++ src/markdown/slugify.ts | 97 +++++++++++++++++++ src/markdown/slugifyMode.ts | 11 +++ src/types/markdown-it.d.ts | 8 ++ src/vue/about/App.vue | 7 +- src/vue/assets/markdown.css | 141 +++++++++++++++++++++++++++ src/webviews/about.ts | 61 +++++++++++- tsconfig.json | 2 +- 11 files changed, 658 insertions(+), 17 deletions(-) create mode 100644 src/markdown/index.ts create mode 100644 src/markdown/renderer.ts create mode 100644 src/markdown/slugify.ts create mode 100644 src/markdown/slugifyMode.ts create mode 100644 src/types/markdown-it.d.ts create mode 100644 src/vue/assets/markdown.css diff --git a/package-lock.json b/package-lock.json index 8598c51..6127887 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,13 +11,24 @@ "src/vue" ], "dependencies": { + "@neilsustc/markdown-it-katex": "^1.0.0", "@vue/shared": "^3.x.x", "element-plus": "^2.4.2", - "marked": "^14.1.3", + "highlight.js": "^11.10.0", + "katex": "^0.16.11", + "markdown-it": "^13.0.2", + "markdown-it-deflist": "^2.1.0", + "markdown-it-emoji": "^3.0.0", + "markdown-it-footnote": "^4.0.0", + "markdown-it-github-alerts": "^0.1.2", + "markdown-it-sub": "^1.0.0", + "markdown-it-sup": "^1.0.0", + "markdown-it-task-lists": "^2.1.1", "vue": "^3.3.8", "vue-router": "^4.5.0" }, "devDependencies": { + "@types/markdown-it": "^14.0.1", "@types/mocha": "^10.0.7", "@types/node": "20.x", "@types/vscode": "^1.96.0", @@ -907,6 +918,19 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@neilsustc/markdown-it-katex": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/@neilsustc/markdown-it-katex/-/markdown-it-katex-1.0.0.tgz", + "integrity": "sha512-2u21S69739IGrNA/Q2xfcurox5LFXq2sp3CPeqx7yDNjy3/SC8taL6QAIYxupRuEUPNS5+OXq9DmwcdKjs90pg==", + "license": "MIT", + "dependencies": { + "katex": "*" + }, + "peerDependencies": { + "katex": "0", + "markdown-it": ">=6" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1268,6 +1292,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/lodash": { "version": "4.17.20", "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.20.tgz", @@ -1283,6 +1314,24 @@ "@types/lodash": "*" } }, + "node_modules/@types/markdown-it": { + "version": "14.1.2", + "resolved": "https://registry.npmmirror.com/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/mocha": { "version": "10.0.10", "resolved": "https://registry.npmmirror.com/@types/mocha/-/mocha-10.0.10.tgz", @@ -1991,7 +2040,6 @@ "version": "2.0.1", "resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, "license": "Python-2.0" }, "node_modules/async-validator": { @@ -3221,6 +3269,15 @@ "he": "bin/he" } }, + "node_modules/highlight.js": { + "version": "11.11.1", + "resolved": "https://registry.npmmirror.com/highlight.js/-/highlight.js-11.11.1.tgz", + "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmmirror.com/html-escaper/-/html-escaper-2.0.2.tgz", @@ -3582,6 +3639,31 @@ "setimmediate": "^1.0.5" } }, + "node_modules/katex": { + "version": "0.16.22", + "resolved": "https://registry.npmmirror.com/katex/-/katex-0.16.22.tgz", + "integrity": "sha512-XCHRdUw4lf3SKBaJe4EvgqIuWwkPSo9XoeO8GjQW94Bp7TWv9hNhzZjZ+OH9yf1UmLygb7DIT5GSFQiyt16zYg==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/katex/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmmirror.com/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmmirror.com/keyv/-/keyv-4.5.4.tgz", @@ -3669,6 +3751,15 @@ "immediate": "~3.0.5" } }, + "node_modules/linkify-it": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/linkify-it/-/linkify-it-4.0.1.tgz", + "integrity": "sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==", + "license": "MIT", + "dependencies": { + "uc.micro": "^1.0.1" + } + }, "node_modules/local-pkg": { "version": "1.1.1", "resolved": "https://registry.npmmirror.com/local-pkg/-/local-pkg-1.1.1.tgz", @@ -3782,16 +3873,80 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/marked": { - "version": "14.1.4", - "resolved": "https://registry.npmmirror.com/marked/-/marked-14.1.4.tgz", - "integrity": "sha512-vkVZ8ONmUdPnjCKc5uTRvmkRbx4EAi2OkTOXmfTDhZz3OFqMNBM1oTTWwTr4HY4uAEojhzPf+Fy8F1DWa3Sndg==", + "node_modules/markdown-it": { + "version": "13.0.2", + "resolved": "https://registry.npmmirror.com/markdown-it/-/markdown-it-13.0.2.tgz", + "integrity": "sha512-FtwnEuuK+2yVU7goGn/MJ0WBZMM9ZPgU9spqlFs7/A/pDIUNSOQZhUgOqYCficIuR2QaFnrt8LHqBWsbTAoI5w==", "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "~3.0.1", + "linkify-it": "^4.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, "bin": { - "marked": "bin/marked.js" + "markdown-it": "bin/markdown-it.js" + } + }, + "node_modules/markdown-it-deflist": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/markdown-it-deflist/-/markdown-it-deflist-2.1.0.tgz", + "integrity": "sha512-3OuqoRUlSxJiuQYu0cWTLHNhhq2xtoSFqsZK8plANg91+RJQU1ziQ6lA2LzmFAEes18uPBsHZpcX6We5l76Nzg==", + "license": "MIT" + }, + "node_modules/markdown-it-emoji": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/markdown-it-emoji/-/markdown-it-emoji-3.0.0.tgz", + "integrity": "sha512-+rUD93bXHubA4arpEZO3q80so0qgoFJEKRkRbjKX8RTdca89v2kfyF+xR3i2sQTwql9tpPZPOQN5B+PunspXRg==", + "license": "MIT" + }, + "node_modules/markdown-it-footnote": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/markdown-it-footnote/-/markdown-it-footnote-4.0.0.tgz", + "integrity": "sha512-WYJ7urf+khJYl3DqofQpYfEYkZKbmXmwxQV8c8mO/hGIhgZ1wOe7R4HLFNwqx7TjILbnC98fuyeSsin19JdFcQ==", + "license": "MIT" + }, + "node_modules/markdown-it-github-alerts": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/markdown-it-github-alerts/-/markdown-it-github-alerts-0.1.2.tgz", + "integrity": "sha512-jtCrfFxXR6c/bNRBvWkoqUquEDdIF9q7/gNZjP47W96kaIiBMiYHbpf468IPeZJB5BVRls6+IXGchhQSTxy0DQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" }, + "peerDependencies": { + "markdown-it": "^13.0.0" + } + }, + "node_modules/markdown-it-sub": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/markdown-it-sub/-/markdown-it-sub-1.0.0.tgz", + "integrity": "sha512-z2Rm/LzEE1wzwTSDrI+FlPEveAAbgdAdPhdWarq/ZGJrGW/uCQbKAnhoCsE4hAbc3SEym26+W2z/VQB0cQiA9Q==", + "license": "MIT" + }, + "node_modules/markdown-it-sup": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/markdown-it-sup/-/markdown-it-sup-1.0.0.tgz", + "integrity": "sha512-E32m0nV9iyhRR7CrhnzL5msqic7rL1juWre6TQNxsnApg7Uf+F97JOKxUijg5YwXz86lZ0mqfOnutoryyNdntQ==", + "license": "MIT" + }, + "node_modules/markdown-it-task-lists": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/markdown-it-task-lists/-/markdown-it-task-lists-2.1.1.tgz", + "integrity": "sha512-TxFAc76Jnhb2OUu+n3yz9RMu4CwGfaT788br6HhEDlvWfdeJcLUsxk1Hgw2yJio0OXsxv7pyIPmvECY7bMbluA==", + "license": "ISC" + }, + "node_modules/markdown-it/node_modules/entities": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/entities/-/entities-3.0.1.tgz", + "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", + "license": "BSD-2-Clause", "engines": { - "node": ">= 18" + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" } }, "node_modules/mdn-data": { @@ -3801,6 +3956,12 @@ "dev": true, "license": "CC0-1.0" }, + "node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", + "license": "MIT" + }, "node_modules/memoize-one": { "version": "6.0.0", "resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz", @@ -5291,6 +5452,12 @@ "node": ">=14.17" } }, + "node_modules/uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "license": "MIT" + }, "node_modules/ufo": { "version": "1.6.1", "resolved": "https://registry.npmmirror.com/ufo/-/ufo-1.6.1.tgz", diff --git a/package.json b/package.json index 6677309..8b3fa5d 100644 --- a/package.json +++ b/package.json @@ -187,8 +187,18 @@ }, "dependencies": { "@vue/shared": "^3.x.x", + "@neilsustc/markdown-it-katex": "^1.0.0", "element-plus": "^2.4.2", - "marked": "^14.1.3", + "highlight.js": "^11.10.0", + "katex": "^0.16.11", + "markdown-it": "^13.0.2", + "markdown-it-deflist": "^2.1.0", + "markdown-it-emoji": "^3.0.0", + "markdown-it-footnote": "^4.0.0", + "markdown-it-github-alerts": "^0.1.2", + "markdown-it-sub": "^1.0.0", + "markdown-it-sup": "^1.0.0", + "markdown-it-task-lists": "^2.1.1", "vue": "^3.3.8", "vue-router": "^4.5.0" }, @@ -196,6 +206,7 @@ "ms-python.python" ], "devDependencies": { + "@types/markdown-it": "^14.0.1", "@types/mocha": "^10.0.7", "@types/node": "20.x", "@types/vscode": "^1.96.0", diff --git a/src/markdown/index.ts b/src/markdown/index.ts new file mode 100644 index 0000000..2373171 --- /dev/null +++ b/src/markdown/index.ts @@ -0,0 +1,3 @@ +export { MarkdownRenderer, markdownRenderer } from './renderer'; +export type { MarkdownRenderOptions } from './renderer'; +export { default as SlugifyMode } from './slugifyMode'; diff --git a/src/markdown/renderer.ts b/src/markdown/renderer.ts new file mode 100644 index 0000000..e520d75 --- /dev/null +++ b/src/markdown/renderer.ts @@ -0,0 +1,149 @@ +import MarkdownIt from 'markdown-it'; +import markdownItTaskLists from 'markdown-it-task-lists'; +import markdownItGithubAlerts from 'markdown-it-github-alerts'; +import markdownItKatex from '@neilsustc/markdown-it-katex'; +import markdownItFootnote from 'markdown-it-footnote'; +import markdownItDeflist from 'markdown-it-deflist'; +import * as markdownItEmoji from 'markdown-it-emoji'; +import markdownItSub from 'markdown-it-sub'; +import markdownItSup from 'markdown-it-sup'; +import hljs from 'highlight.js'; +import { slugifyHeading } from './slugify'; +import SlugifyMode from './slugifyMode'; + +export interface MarkdownRenderOptions { + breaks?: boolean; + linkify?: boolean; + enableMath?: boolean; + slugifyMode?: SlugifyMode; + mathMacros?: Record; +} + +interface MathOptions { + throwOnError?: boolean; + macros?: Record; +} + +interface EngineRecord { + md: MarkdownIt; + slugCount: Map; +} + +export class MarkdownRenderer { + private engines = new Map(); + + public render(text: string, options: MarkdownRenderOptions = {}): string { + const enableMath = options.enableMath !== false; + const slugifyMode = options.slugifyMode ?? SlugifyMode.GitHub; + const key = this.composeEngineKey(enableMath, slugifyMode, options.mathMacros); + const engineRecord = this.ensureEngine(key, enableMath, slugifyMode, options.mathMacros); + + engineRecord.slugCount.clear(); + + engineRecord.md.set({ + breaks: options.breaks === true, + linkify: options.linkify !== false, + }); + + const env = Object.create(null); + return engineRecord.md.render(text, env); + } + + private composeEngineKey(enableMath: boolean, slugifyMode: SlugifyMode, macros?: Record): string { + const macroEntries = macros ? Object.keys(macros).sort().map((k) => `${k}:${macros[k]}`) : []; + return `${enableMath ? 'math' : 'nomath'}|${slugifyMode}|${macroEntries.join(',')}`; + } + + private ensureEngine(key: string, enableMath: boolean, slugifyMode: SlugifyMode, macros?: Record): EngineRecord { + let record = this.engines.get(key); + if (!record) { + const slugCount = new Map(); + const md = this.createEngine(enableMath, slugCount, slugifyMode, macros); + record = { md, slugCount }; + this.engines.set(key, record); + } + return record; + } + + private createEngine(enableMath: boolean, slugCount: Map, slugifyMode: SlugifyMode, macros?: Record): MarkdownIt { + const md = new MarkdownIt({ + html: true, + highlight: (str: string, lang?: string) => { + if (lang) { + const normalized = this.normalizeHighlightLang(lang); + if (normalized && hljs.getLanguage(normalized)) { + try { + return hljs.highlight(str, { language: normalized, ignoreIllegals: true }).value; + } catch { + // ignore highlighting errors and fallback to default rendering + } + } + } + return ''; + } + }); + + md.use(markdownItTaskLists, { enabled: true, label: true, labelAfter: false }); + md.use(markdownItGithubAlerts, { matchCaseSensitive: false }); + md.use(markdownItFootnote); + md.use(markdownItDeflist); + md.use(markdownItEmoji.full || markdownItEmoji); + md.use(markdownItSub); + md.use(markdownItSup); + + if (enableMath) { + require('katex/contrib/mhchem'); + const katexOptions: MathOptions = { throwOnError: false }; + if (macros && Object.keys(macros).length > 0) { + katexOptions.macros = JSON.parse(JSON.stringify(macros)); + } + md.use(markdownItKatex, katexOptions); + } + + this.addNamedHeaders(md, slugCount, slugifyMode); + return md; + } + + private addNamedHeaders(md: MarkdownIt, slugCount: Map, slugifyMode: SlugifyMode): void { + const originalHeadingOpen = md.renderer.rules.heading_open ?? ((tokens, idx, options, _env, self) => self.renderToken(tokens, idx, options)); + + md.renderer.rules.heading_open = (tokens, idx, options, env, self) => { + const raw = tokens[idx + 1]?.content ?? ''; + const baseSlug = slugifyHeading(raw, env ?? Object.create(null), slugifyMode) || 'section'; + const finalSlug = this.resolveUniqueSlug(slugCount, baseSlug); + tokens[idx].attrs = [...(tokens[idx].attrs || []), ['id', finalSlug]]; + + return originalHeadingOpen(tokens, idx, options, env, self); + }; + } + + private resolveUniqueSlug(slugCount: Map, base: string): string { + const previous = slugCount.get(base); + if (previous === undefined) { + slugCount.set(base, 0); + return base; + } + + const next = previous + 1; + slugCount.set(base, next); + return `${base}-${next}`; + } + + private normalizeHighlightLang(lang: string): string { + switch (lang.toLowerCase()) { + case 'tsx': + case 'typescriptreact': + return 'jsx'; + case 'json5': + case 'jsonc': + return 'json'; + case 'c#': + case 'csharp': + return 'cs'; + default: + return lang.toLowerCase(); + } + } +} + +export const markdownRenderer = new MarkdownRenderer(); diff --git a/src/markdown/slugify.ts b/src/markdown/slugify.ts new file mode 100644 index 0000000..70130de --- /dev/null +++ b/src/markdown/slugify.ts @@ -0,0 +1,97 @@ +import MarkdownIt from 'markdown-it'; +import SlugifyMode from './slugifyMode'; + +const inlineEngine = new MarkdownIt('commonmark'); +const utf8Encoder = new TextEncoder(); + +const Regexp_Github_Punctuation = /[^\p{L}\p{M}\p{Nd}\p{Nl}\p{Pc}\- ]/gu; +const Regexp_Gitlab_Product_Suffix = /[ \t\r\n\f\v]*\**\((?:core|starter|premium|ultimate)(?:[ \t\r\n\f\v]+only)?\)\**/g; + +function mdInlineToPlainText(text: string, env: object): string { + const inlineTokens = inlineEngine.parseInline(text, env)[0]?.children ?? []; + return inlineTokens.reduce((result, token) => { + switch (token.type) { + case 'image': + case 'html_inline': + return result; + default: + return result + token.content; + } + }, ''); +} + +const slugifyHandlers: Record string> = { + [SlugifyMode.AzureDevOps]: (slug: string): string => { + slug = slug.trim().toLowerCase().replace(/\p{Zs}/gu, '-'); + if (/^\d/.test(slug)) { + slug = Array.from(utf8Encoder.encode(slug), (b) => `%${b.toString(16)}`).join('').toUpperCase(); + } else { + slug = encodeURIComponent(slug); + } + return slug; + }, + + [SlugifyMode.BitbucketCloud]: (slug: string, env: object): string => { + return 'markdown-header-' + slugifyHandlers[SlugifyMode.GitHub](slug, env).replace(/-+/g, '-'); + }, + + [SlugifyMode.Gitea]: (slug: string): string => { + return slug + .replace(/^[^\p{L}\p{N}]+/u, '') + .replace(/[^\p{L}\p{N}]+$/u, '') + .replace(/[^\p{L}\p{N}]+/gu, '-') + .toLowerCase(); + }, + + [SlugifyMode.GitHub]: (slug: string, env: object): string => { + slug = mdInlineToPlainText(slug, env) + .replace(Regexp_Github_Punctuation, '') + .toLowerCase() + .replace(/ /g, '-'); + return slug; + }, + + [SlugifyMode.GitLab]: (slug: string, env: object): string => { + slug = mdInlineToPlainText(slug, env) + .replace(/^[ \t\r\n\f\v]+/, '') + .replace(/[ \t\r\n\f\v]+$/, '') + .toLowerCase() + .replace(Regexp_Gitlab_Product_Suffix, '') + .replace(Regexp_Github_Punctuation, '') + .replace(/ /g, '-') + .replace(/-+/g, '-'); + if (/^(\d+)$/.test(slug)) { + slug = `anchor-${slug}`; + } + return slug; + }, + + [SlugifyMode.VisualStudioCode]: (rawContent: string, env: object): string => { + const plain = inlineEngine.parseInline(rawContent, env)[0]?.children?.reduce((result, token) => result + token.content, '') ?? ''; + return encodeURI( + plain + .trim() + .toLowerCase() + .replace(/\s+/g, '-') + .replace(/[\]\[\!\'\#\$\%\&\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\_\{\|\}\~\`。,、;:?!…—·ˉ¨‘’“”々~‖∶"'`|〃〔〕〈〉《》「」『』.〖〗【】()[]{}]/g, '') + .replace(/^\-+/, '') + .replace(/\-+$/, '') + ); + }, + + [SlugifyMode.Zola]: (rawContent: string, env: object): string => { + // 退化为 GitHub 行为;如需完整兼容,可后续引入 zola-slug wasm 实现。 + return slugifyHandlers[SlugifyMode.GitHub](rawContent, env); + }, +}; + +export function slugifyHeading(raw: string, env: object, mode: SlugifyMode): string { + const handler = slugifyHandlers[mode] ?? slugifyHandlers[SlugifyMode.GitHub]; + let slug = handler(raw, env); + slug = slug + .replace(/\s+/g, '-') + .replace(/\u0000/g, '') + .replace(/-+/g, '-') + .replace(/^-+|-+$/g, ''); + return slug; +} diff --git a/src/markdown/slugifyMode.ts b/src/markdown/slugifyMode.ts new file mode 100644 index 0000000..6828f84 --- /dev/null +++ b/src/markdown/slugifyMode.ts @@ -0,0 +1,11 @@ +export const enum SlugifyMode { + AzureDevOps = 'azureDevops', + BitbucketCloud = 'bitbucket-cloud', + Gitea = 'gitea', + GitHub = 'github', + GitLab = 'gitlab', + VisualStudioCode = 'vscode', + Zola = 'zola', +} + +export default SlugifyMode; diff --git a/src/types/markdown-it.d.ts b/src/types/markdown-it.d.ts new file mode 100644 index 0000000..95e7641 --- /dev/null +++ b/src/types/markdown-it.d.ts @@ -0,0 +1,8 @@ +declare module 'markdown-it-task-lists'; +declare module 'markdown-it-github-alerts'; +declare module '@neilsustc/markdown-it-katex'; +declare module 'markdown-it-footnote'; +declare module 'markdown-it-deflist'; +declare module 'markdown-it-emoji'; +declare module 'markdown-it-sub'; +declare module 'markdown-it-sup'; diff --git a/src/vue/about/App.vue b/src/vue/about/App.vue index 61141d4..99a48b9 100644 --- a/src/vue/about/App.vue +++ b/src/vue/about/App.vue @@ -3,7 +3,7 @@
-
+
Open RT-Thread/Github
@@ -12,10 +12,11 @@