diff --git a/demo/src/app.html b/demo/src/app.html index 0e5ac56..52f1a61 100644 --- a/demo/src/app.html +++ b/demo/src/app.html @@ -5,6 +5,12 @@ + diff --git a/demo/src/routes/docs/[...7]testing/[...1]katex/+page.md b/demo/src/routes/docs/[...7]testing/[...1]katex/+page.md new file mode 100644 index 0000000..35a81d8 --- /dev/null +++ b/demo/src/routes/docs/[...7]testing/[...1]katex/+page.md @@ -0,0 +1,20 @@ +# KaTeX support + +Mathematical expressions can be written inline, such as $a=1$, or as a block, such as the example below: + +$$ +b=2 +$$ + +You can add all sorts of mathematical operations. + +$$ +\frac {a}{b} = \frac {1}{2} +$$ + +$$ +\sum_{i=1}^{5}b*i = 30 +$$ + + +And also inline them $\sum_{i=1}^{5}b*i = 30$, just like that diff --git a/packages/kit-docs/package.json b/packages/kit-docs/package.json index 7f72562..26abd9a 100644 --- a/packages/kit-docs/package.json +++ b/packages/kit-docs/package.json @@ -105,6 +105,18 @@ "unplugin-icons": "^0.17.0", "vite": "^4.4.9" }, + "peerDependencies": { + "markdown-it-texmath": "^1.0.0", + "katex": "^0.16.9" + }, + "peerDependenciesMeta": { + "markdown-it-texmath": { + "optional": true + }, + "katex": { + "optional": true + } + }, "publishConfig": { "access": "public" }, diff --git a/packages/kit-docs/src/node/markdown-plugin/parser/create-markdown-parser.ts b/packages/kit-docs/src/node/markdown-plugin/parser/create-markdown-parser.ts index ac1fbf6..39e8a65 100644 --- a/packages/kit-docs/src/node/markdown-plugin/parser/create-markdown-parser.ts +++ b/packages/kit-docs/src/node/markdown-plugin/parser/create-markdown-parser.ts @@ -12,7 +12,9 @@ import { extractTitlePlugin, hoistTagsPlugin, importCodePlugin, + katexPlugin, linksPlugin, + removeAnnotationPlugin, tocPlugin, } from './plugins'; import type { @@ -49,6 +51,21 @@ export async function createMarkdownParser( const parser = MarkdownIt({ html: true }); + // Optional math expressions plugins + try { + const markdownItTexmath = await import('markdown-it-texmath'); + const katex = await import('katex'); + + try { + parser.use(katexPlugin, { plugin: markdownItTexmath, katex }); + parser.use(removeAnnotationPlugin); + } catch (error) { + console.error(error); + } + } catch { + console.log('no KaTeX support, failed to import'); + } + parser.use(emojiPlugin); parser.use(anchorPlugin); parser.use(tocPlugin); diff --git a/packages/kit-docs/src/node/markdown-plugin/parser/plugins/index.ts b/packages/kit-docs/src/node/markdown-plugin/parser/plugins/index.ts index 85b9473..de515dd 100644 --- a/packages/kit-docs/src/node/markdown-plugin/parser/plugins/index.ts +++ b/packages/kit-docs/src/node/markdown-plugin/parser/plugins/index.ts @@ -7,6 +7,8 @@ export * from './extract-headers-plugin'; export * from './extract-title-plugin'; export * from './hoist-tags-plugin'; export * from './import-code-plugin'; +export * from './katex-plugin'; export * from './links-plugin'; +export * from './remove-annotation-plugin'; export * from './shiki-plugin'; export * from './toc-plugin'; diff --git a/packages/kit-docs/src/node/markdown-plugin/parser/plugins/katex-plugin.ts b/packages/kit-docs/src/node/markdown-plugin/parser/plugins/katex-plugin.ts new file mode 100644 index 0000000..51a1e82 --- /dev/null +++ b/packages/kit-docs/src/node/markdown-plugin/parser/plugins/katex-plugin.ts @@ -0,0 +1,8 @@ +import type { PluginWithOptions } from 'markdown-it'; + +export const katexPlugin: PluginWithOptions = (parser, { plugin, katex }) => { + return plugin.default(parser, { + engine: katex.default, + delimiters: 'dollars', + }); +}; diff --git a/packages/kit-docs/src/node/markdown-plugin/parser/plugins/remove-annotation-plugin.ts b/packages/kit-docs/src/node/markdown-plugin/parser/plugins/remove-annotation-plugin.ts new file mode 100644 index 0000000..009a279 --- /dev/null +++ b/packages/kit-docs/src/node/markdown-plugin/parser/plugins/remove-annotation-plugin.ts @@ -0,0 +1,31 @@ +import { type PluginSimple } from 'markdown-it'; +const annotationRe = /()([\s\S]+?)(<\/annotation>)/g; + +/** + * Resolves link URLs. + */ +export const removeAnnotationPlugin: PluginSimple = (parser) => { + Object.entries(parser.renderer.rules).forEach(([name, fn]) => { + if (!name.startsWith('math')) return; + if (!fn) return; + + // Fix math rules, to remove annotation blocks afterward + parser.renderer.rules[name] = (tokens, idx, options, env, renderer) => { + let result = fn(tokens, idx, options, env, renderer); + + [...result.matchAll(annotationRe)].forEach((match) => { + /* Given an annotation rule with a backslash or curly braces, modify it using svelte's @html + to avoid svelte attempting to parse variables, or processing backslashes + + Input: \rule {braces} + Output: {@html `\\rule {braces}`} */ + result = result.replace( + match[0], + `${match[1]}{@html \`${match[2].replace('\\', '\\\\')}\`}${match[3]}`, + ); + }); + + return result; + }; + }); +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index de7e5bf..2578260 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -160,9 +160,15 @@ importers: clsx: specifier: ^2.0.0 version: 2.0.0 + katex: + specifier: ^0.16.9 + version: 0.16.9 kleur: specifier: ^4.1.5 version: 4.1.5 + markdown-it-texmath: + specifier: ^1.0.0 + version: 1.0.0 shiki: specifier: ^0.14.4 version: 0.14.4 @@ -1872,6 +1878,11 @@ packages: engines: {node: '>= 6'} dev: true + /commander@8.3.0: + resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} + engines: {node: '>= 12'} + dev: false + /commander@9.5.0: resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} engines: {node: ^12.20.0 || >=14} @@ -3354,6 +3365,13 @@ packages: engines: {'0': node >= 0.2.0} dev: true + /katex@0.16.9: + resolution: {integrity: sha512-fsSYjWS0EEOwvy81j3vRA8TEAhQhKiqO+FQaKWp0m39qwOzHVBgAUBIXWj1pB+O2W3fIpNa6Y9KSKCVbfPhyAQ==} + hasBin: true + dependencies: + commander: 8.3.0 + dev: false + /keyv@4.5.3: resolution: {integrity: sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==} dependencies: @@ -3567,6 +3585,10 @@ packages: resolution: {integrity: sha512-zLftSaNrKuYl0kR5zm4gxXjHaOI3FAOEaloKmRA5hijmJZvSjmxcokOLlzycb/HXlUFWzXqpIEoyEMCE4i9MvQ==} dev: true + /markdown-it-texmath@1.0.0: + resolution: {integrity: sha512-4hhkiX8/gus+6e53PLCUmUrsa6ZWGgJW2XCW6O0ASvZUiezIK900ZicinTDtG3kAO2kon7oUA/ReWmpW2FByxg==} + dev: false + /markdown-it@13.0.1: resolution: {integrity: sha512-lTlxriVoy2criHP0JKRhO2VDG9c2ypWCsT237eDiLqi09rmbKoUetyGHq2uOIRoRS//kfoJckS0eUzzkDR+k2Q==} hasBin: true