@mvarble/mesearch-markdown 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Matthew Varble
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,20 @@
1
- # Temporary Holding Version
1
+ # @mvarble/mesearch-markdown
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The mdsvex pipeline shared by [mesearch](https://github.com/mvarble/mesearch), [mkdoc](https://github.com/mvarble/mkdoc) and [the blog](https://github.com/mvarble/blog).
4
+
5
+ ```js
6
+ import { markdownPreprocessors } from '@mvarble/mesearch-markdown';
7
+
8
+ // svelte.config.js, or vite-plugin-svelte's `preprocess`
9
+ preprocess: markdownPreprocessors({
10
+ katex: { macros: { '\\PP': '\\mathbb{P}' } },
11
+ mathBox: { liftTags: true },
12
+ }),
13
+ ```
14
+
15
+ - **KaTeX**, rendered at build time by KaTeX itself, with the macros layered: the base table (`\bbR`, `\calF`, `\bfx`, `\rmd`, `\defeq`, … in `baseMacros`), then the site's, then the document's own frontmatter `katex_macros`. `macros` may instead be a function of the document, for a content layer that folds macros down a tree of documents. A formula that does not parse renders in KaTeX's error colour and is reported, and does not fail the build.
16
+ - **Display math** wrapped in a scroll container, so a long equation slides sideways rather than overflowing. With `liftTags`, a `\tag` is moved out of the container to stay in view.
17
+ - **Shiki** highlighting in a light and a dark theme at once, with grammars loaded on first use. A fence whose whole body is `{./file}` or `{./file:10..20}` is replaced by those lines of the file.
18
+ - **Relative assets** --- `![](./figure.png)` --- turned into Vite imports, so they are hashed and emitted with the page.
19
+
20
+ `styles/katex.css` styles all of it. It also keeps inline math from breaking across a line.
@@ -0,0 +1,12 @@
1
+ import type { Plugin } from 'unified';
2
+ import type { Root } from 'hast';
3
+ export declare const rehypeAssets: Plugin<[], Root>;
4
+ export declare const hoistAssetImports: {
5
+ name: string;
6
+ markup({ content, filename }: {
7
+ content: string;
8
+ filename?: string;
9
+ }): {
10
+ code: string;
11
+ } | undefined;
12
+ };
package/dist/assets.js ADDED
@@ -0,0 +1,108 @@
1
+ // Vite rewrites relative URLs in `.html` entries, and Svelte resolves them in
2
+ // `import` statements --- but nothing rewrites `src="./picture.png"` written in
3
+ // a Svelte template, which is exactly what `![](./picture.png)` compiles to.
4
+ // Such a document builds cleanly and then ships a broken image.
5
+ //
6
+ // So the rehype pass below turns the attribute into `src={__md_asset_0}` and
7
+ // records the URL; the preprocessor that runs after mdsvex hoists a matching
8
+ // `import __md_asset_0 from './picture.png'` into the document's script.
9
+ // From there it is an ordinary Vite import: hashed, emitted, and correct in
10
+ // both the dev server and the build.
11
+ // Attributes that name a file, by tag. `<a href>` is deliberately absent: a
12
+ // link to a neighbouring document is a link, not an asset to inline.
13
+ const ASSET_ATTRIBUTES = {
14
+ img: ['src'],
15
+ source: ['src'],
16
+ video: ['src', 'poster'],
17
+ audio: ['src'],
18
+ track: ['src'],
19
+ embed: ['src'],
20
+ iframe: ['src'],
21
+ object: ['data'],
22
+ link: ['href'],
23
+ };
24
+ // URLs written relative to the document. Everything else --- absolute paths,
25
+ // external URLs, fragments, data URIs --- is left exactly as the author wrote it.
26
+ const isRelative = (url) => url.startsWith('./') || url.startsWith('../');
27
+ // Keyed by absolute filename, because the rehype tree and the Svelte source it
28
+ // becomes are handled by two different plugins with no channel between them.
29
+ //
30
+ // A build compiles the document twice --- once for the client, once for SSR ---
31
+ // and both passes write here. That is safe only because the two passes are
32
+ // sequential and see the same tree; running them concurrently would let one
33
+ // pass's markup be hoisted against the other's URLs.
34
+ const collected = new Map();
35
+ export const rehypeAssets = () => {
36
+ return (tree, vfile) => {
37
+ // mdsvex puts the document's absolute path on the vfile as `filename`,
38
+ // which is not one of vfile's own fields.
39
+ const filename = vfile.filename;
40
+ if (!filename)
41
+ throw new Error('the document vfile has no filename.');
42
+ const urls = [];
43
+ const stack = [tree];
44
+ while (stack.length > 0) {
45
+ const node = stack.pop();
46
+ if (node.type == 'element') {
47
+ const properties = node.properties ?? {};
48
+ for (const attribute of ASSET_ATTRIBUTES[node.tagName] ?? []) {
49
+ const value = properties[attribute];
50
+ if (typeof value != 'string' || !isRelative(value))
51
+ continue;
52
+ properties[attribute] = `{__md_asset_${urls.length}}`;
53
+ urls.push(value);
54
+ }
55
+ }
56
+ for (const child of node.children) {
57
+ if (child.type == 'element')
58
+ stack.push(child);
59
+ }
60
+ }
61
+ collected.set(filename, urls);
62
+ };
63
+ };
64
+ // A document may already declare an instance `<script>`; a second one is a
65
+ // compile error, so the imports go inside the existing block when there is one.
66
+ //
67
+ // Only the prologue is searched. mdsvex emits the module block it makes from the
68
+ // frontmatter, then the document's own script, then the markup --- so a `<script`
69
+ // that appears after any real content is something the author wrote *in* the
70
+ // document, and injecting imports into it would corrupt the page.
71
+ const SCRIPT_OPEN = /^<script\b([^>]*)>/;
72
+ function instanceScriptEnd(content) {
73
+ let at = 0;
74
+ while (at < content.length) {
75
+ const rest = content.slice(at);
76
+ const leading = rest.length - rest.trimStart().length;
77
+ at += leading;
78
+ const open = SCRIPT_OPEN.exec(content.slice(at));
79
+ if (!open)
80
+ return null;
81
+ const attributes = open[1] ?? '';
82
+ const openEnd = at + open[0].length;
83
+ const close = content.indexOf('</script>', openEnd);
84
+ if (close < 0)
85
+ return null;
86
+ // The block mdsvex generates for the frontmatter is not the document's.
87
+ if (!/\b(?:context|module)\b/.test(attributes))
88
+ return openEnd;
89
+ at = close + '</script>'.length;
90
+ }
91
+ return null;
92
+ }
93
+ export const hoistAssetImports = {
94
+ name: 'markdown-assets',
95
+ markup({ content, filename }) {
96
+ const urls = filename ? collected.get(filename) : undefined;
97
+ if (!urls?.length)
98
+ return;
99
+ const imports = urls
100
+ .map((url, i) => `import __md_asset_${i} from ${JSON.stringify(url)};`)
101
+ .join('\n');
102
+ const at = instanceScriptEnd(content);
103
+ if (at == null)
104
+ return { code: `<script>\n${imports}\n</script>\n${content}` };
105
+ return { code: `${content.slice(0, at)}\n${imports}${content.slice(at)}` };
106
+ },
107
+ };
108
+ //# sourceMappingURL=assets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assets.js","sourceRoot":"","sources":["../src/assets.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export declare function highlight(code: string, lang: string | null | undefined, _meta: string | null | undefined, filename: string | undefined): Promise<string>;
@@ -0,0 +1,85 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { createHighlighter, bundledLanguages } from 'shiki';
4
+ import { escapeSvelte } from 'mdsvex';
5
+ const THEMES = { light: 'vitesse-light', dark: 'vitesse-dark' };
6
+ // Loading all of Shiki's grammars eagerly costs seconds, and a site cannot know
7
+ // up front which ones its documents use, so a language is loaded the first time
8
+ // a fence asks for one.
9
+ //
10
+ // Both caches hold the *promise* rather than what it settles to. mdsvex
11
+ // highlights a document's fences concurrently, through `Promise.all`, so a
12
+ // cache filled in after an `await` is empty for every call already in flight:
13
+ // two fences would each build a highlighter, and the language one of them loaded
14
+ // would be loaded into a highlighter the other never sees. That fails as
15
+ // "Language `json` not found", on the second language a document uses.
16
+ let highlighter;
17
+ const loaded = new Map();
18
+ const highlighterFor = () => (highlighter ??= createHighlighter({ themes: Object.values(THEMES), langs: [] }));
19
+ async function languageFor(shiki, lang) {
20
+ if (!lang || !(lang in bundledLanguages))
21
+ return 'text';
22
+ const load = loaded.get(lang) ?? shiki.loadLanguage(lang);
23
+ loaded.set(lang, load);
24
+ await load;
25
+ return lang;
26
+ }
27
+ // A fence whose entire body is `{./path/to/file}` --- optionally with a line
28
+ // range, as `{./path/to/file:10..20}` --- is replaced by that file's contents,
29
+ // highlighted as its own extension suggests. Keeping a snippet in the file it
30
+ // came from means it cannot drift from the code it is quoting.
31
+ export async function highlight(code, lang, _meta, filename) {
32
+ if (!code.includes('\n') && code.startsWith('{') && code.endsWith('}')) {
33
+ if (!filename)
34
+ throw new Error('cannot resolve a fenced import without a filename.');
35
+ const [imported, start, end] = parseImportString(code.slice(1, -1), filename);
36
+ code = await fs.readFile(imported, 'utf8');
37
+ code = sliceLines(code, start, end);
38
+ lang = path.extname(imported).slice(1);
39
+ }
40
+ const shiki = await highlighterFor();
41
+ const html = escapeSvelte(shiki.codeToHtml(code, { lang: await languageFor(shiki, lang), themes: THEMES }));
42
+ return `{@html \`${html}\` }`;
43
+ }
44
+ function sliceLines(code, start, end) {
45
+ if (start == null && end == null)
46
+ return code;
47
+ const lines = code.split('\n');
48
+ return lines.slice(start ?? 0, end == null ? undefined : end + 1).join('\n');
49
+ }
50
+ // Parses the body of a fenced import: a path, optionally followed by `:` and a
51
+ // range written as `start..`, `..end` or `start..end`. The path is resolved
52
+ // relative to the document that wrote it.
53
+ function parseImportString(importString, filename) {
54
+ let imported = importString;
55
+ let start = null;
56
+ let end = null;
57
+ if (importString.includes(':')) {
58
+ const parts = importString.split(':');
59
+ if (parts.length > 2) {
60
+ throw new Error('a fenced import cannot contain multiple `:` characters.');
61
+ }
62
+ imported = parts[0];
63
+ const range = parts[1];
64
+ if (range.startsWith('..')) {
65
+ end = Number(range.slice(2));
66
+ if (!Number.isFinite(end))
67
+ throw new Error(`\`${range}\` is not a range ..end.`);
68
+ }
69
+ else if (range.endsWith('..')) {
70
+ start = Number(range.slice(0, -2));
71
+ if (!Number.isFinite(start)) {
72
+ throw new Error(`\`${range}\` is not a range start..`);
73
+ }
74
+ }
75
+ else {
76
+ const match = /^([0-9]+)\.\.([0-9]+)$/.exec(range);
77
+ if (!match)
78
+ throw new Error(`\`${range}\` is not a range start..end.`);
79
+ start = Number(match[1]);
80
+ end = Number(match[2]);
81
+ }
82
+ }
83
+ return [path.resolve(path.dirname(filename), imported), start, end];
84
+ }
85
+ //# sourceMappingURL=highlight.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"highlight.js","sourceRoot":"","sources":["../src/highlight.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import type { PreprocessorGroup } from 'svelte/compiler';
2
+ import { type RehypeKatexOptions } from './katex.ts';
3
+ import { type MathBoxOptions } from './mathbox.ts';
4
+ export { baseMacros, documentMacros, frontmatterMacros, rehypeKatex, renderMath, type KatexMacros, type RehypeKatexOptions, type RenderOptions, } from './katex.ts';
5
+ export { rehypeMathBox, type MathBoxOptions } from './mathbox.ts';
6
+ export { rehypeAssets, hoistAssetImports } from './assets.ts';
7
+ export { highlight } from './highlight.ts';
8
+ export declare const DOCUMENT_EXTENSIONS: string[];
9
+ export type PluginEntry = any;
10
+ export interface PreprocessorOptions {
11
+ extensions?: string[];
12
+ /** Run after frontmatter and math are parsed. */
13
+ remarkPlugins?: PluginEntry[];
14
+ /** Run before the math is rendered, so they see the TeX source. */
15
+ rehypePlugins?: PluginEntry[];
16
+ katex?: RehypeKatexOptions;
17
+ mathBox?: MathBoxOptions;
18
+ /** Hoist relative `src`s into Vite imports. On by default. */
19
+ assets?: boolean;
20
+ /** Set while a dev server is running: diagnostics repeat on every rebuild. */
21
+ watch?: boolean;
22
+ }
23
+ export declare function markdownPreprocessors(options?: PreprocessorOptions): PreprocessorGroup[];
package/dist/index.js ADDED
@@ -0,0 +1,34 @@
1
+ import { mdsvex } from 'mdsvex';
2
+ import remarkMath from 'remark-math';
3
+ import remarkFrontmatter from 'remark-frontmatter';
4
+ import { rehypeKatex } from "./katex.js";
5
+ import { rehypeAssets, hoistAssetImports } from "./assets.js";
6
+ import { rehypeMathBox } from "./mathbox.js";
7
+ import { highlight } from "./highlight.js";
8
+ export { baseMacros, documentMacros, frontmatterMacros, rehypeKatex, renderMath, } from "./katex.js";
9
+ export { rehypeMathBox } from "./mathbox.js";
10
+ export { rehypeAssets, hoistAssetImports } from "./assets.js";
11
+ export { highlight } from "./highlight.js";
12
+ export const DOCUMENT_EXTENSIONS = ['.svx', '.md'];
13
+ // One parser configuration, shared by everything that renders these documents.
14
+ // Order matters: `rehypeAssets` has to see the tree after KaTeX has run, so that
15
+ // nothing it rewrote is mistaken for an asset.
16
+ export function markdownPreprocessors(options = {}) {
17
+ const extensions = options.extensions ?? DOCUMENT_EXTENSIONS;
18
+ const assets = options.assets ?? true;
19
+ return [
20
+ mdsvex({
21
+ extensions,
22
+ remarkPlugins: [remarkFrontmatter, remarkMath, ...(options.remarkPlugins ?? [])],
23
+ rehypePlugins: [
24
+ ...(options.rehypePlugins ?? []),
25
+ [rehypeKatex, { watch: options.watch, ...options.katex }],
26
+ [rehypeMathBox, options.mathBox ?? {}],
27
+ ...(assets ? [rehypeAssets] : []),
28
+ ],
29
+ highlight: { highlighter: highlight },
30
+ }),
31
+ ...(assets ? [hoistAssetImports] : []),
32
+ ];
33
+ }
34
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAsB,MAAM,QAAQ,CAAC;AAEpD,OAAO,UAAU,MAAM,aAAa,CAAC;AACrC,OAAO,iBAAiB,MAAM,oBAAoB,CAAC;AAEnD,OAAO,EAAE,WAAW,EAA2B,MAAM,YAAY,CAAC;AAClE,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAuB,MAAM,cAAc,CAAC;AAClE,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE3C,OAAO,EACH,UAAU,EACV,cAAc,EACd,iBAAiB,EACjB,WAAW,EACX,UAAU,GAIb,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,aAAa,EAAuB,MAAM,cAAc,CAAC;AAClE,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE3C,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;AAsBnD,+EAA+E;AAC/E,iFAAiF;AACjF,+CAA+C;AAC/C,MAAM,UAAU,qBAAqB,CAAC,UAA+B,EAAE;IACnE,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,mBAAmB,CAAC;IAC7D,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,IAAI,CAAC;IACtC,OAAO;QACH,MAAM,CAAC;YACH,UAAU;YACV,aAAa,EAAE,CAAC,iBAAiB,EAAE,UAAU,EAAE,GAAG,CAAC,OAAO,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC;YAChF,aAAa,EAAE;gBACX,GAAG,CAAC,OAAO,CAAC,aAAa,IAAI,EAAE,CAAC;gBAChC,CAAC,WAAW,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;gBACzD,CAAC,aAAa,EAAE,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC;gBACtC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;aACpC;YACD,SAAS,EAAE,EAAE,WAAW,EAAE,SAAS,EAAE;SACvB,CAAsB;QACxC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;KACzC,CAAC;AACN,CAAC"}
@@ -0,0 +1,28 @@
1
+ import { type KatexOptions } from 'katex';
2
+ import type { Plugin } from 'unified';
3
+ import type { Root } from 'hast';
4
+ export type KatexMacros = NonNullable<KatexOptions['macros']>;
5
+ export declare const baseMacros: KatexMacros;
6
+ export declare function frontmatterMacros(frontmatter: unknown): KatexMacros;
7
+ export declare const documentMacros: (vfile: unknown) => KatexMacros;
8
+ export interface RenderOptions {
9
+ katex?: KatexOptions;
10
+ /** Prefixes every report, so the reader knows which tool is talking. */
11
+ label?: string;
12
+ /** Re-report on every recompile, rather than only when something changes. */
13
+ watch?: boolean;
14
+ }
15
+ export declare function renderMath(tree: Root, filename: string, options?: RenderOptions): void;
16
+ export interface RehypeKatexOptions extends Omit<RenderOptions, 'katex'> {
17
+ /** Start from `baseMacros`. On by default. */
18
+ base?: boolean;
19
+ /**
20
+ * The macros layered over the base table. A plain table is folded under the
21
+ * document's own frontmatter macros. A function is asked per document and
22
+ * its answer is used as given --- which is how a content layer that already
23
+ * knows each document's ancestry supplies the whole fold itself.
24
+ */
25
+ macros?: KatexMacros | ((vfile: unknown) => KatexMacros);
26
+ katex?: Omit<KatexOptions, 'macros'>;
27
+ }
28
+ export declare const rehypeKatex: Plugin<[RehypeKatexOptions?], Root>;
package/dist/katex.js ADDED
@@ -0,0 +1,186 @@
1
+ import katex, {} from 'katex';
2
+ const alphabet = 'abcdefghijklmnopqrstuvwxyz';
3
+ const greeks = [
4
+ ['α', 'alpha'],
5
+ ['β', 'beta'],
6
+ ['γ', 'gamma'],
7
+ ['δ', 'delta'],
8
+ ['ϵ', 'epsilon'],
9
+ ['ζ', 'zeta'],
10
+ ['η', 'eta'],
11
+ ['θ', 'theta'],
12
+ ['ι', 'iota'],
13
+ ['κ', 'kappa'],
14
+ ['λ', 'lambda'],
15
+ ['μ', 'mu'],
16
+ ['ν', 'nu'],
17
+ ['ξ', 'xi'],
18
+ ['π', 'pi'],
19
+ ['ρ', 'rho'],
20
+ ['σ', 'sigma'],
21
+ ['τ', 'tau'],
22
+ ['ϕ', 'phi'],
23
+ ['ψ', 'psi'],
24
+ ['χ', 'chi'],
25
+ ['ω', 'omega'],
26
+ ];
27
+ const titleCase = (str) => (str.length ? str.at(0).toUpperCase() + str.slice(1) : '');
28
+ // Macros every document gets for free: `\bfx` for a bold `x`, `\calF` for a
29
+ // script `F`, `\bbR` for the reals, and the same for the Greek letters. A site
30
+ // adds to these from its configuration and a document from its frontmatter,
31
+ // and the nearer definition of a name wins.
32
+ export const baseMacros = {
33
+ ...Object.fromEntries([
34
+ ...alphabet.split('').flatMap((char) => [
35
+ [`\\rm${char}`, `{\\rm ${char}}`],
36
+ [`\\bf${char}`, `\\boldsymbol{${char}}`],
37
+ [`\\rm${char.toUpperCase()}`, `\\mathrm{${char.toUpperCase()}}`],
38
+ [`\\cal${char.toUpperCase()}`, `\\mathcal{${char.toUpperCase()}}`],
39
+ [`\\scr${char.toUpperCase()}`, `\\mathscr{${char.toUpperCase()}}`],
40
+ [`\\bb${char.toUpperCase()}`, `\\mathbb{${char.toUpperCase()}}`],
41
+ [`\\bf${char.toUpperCase()}`, `\\mathbf{${char.toUpperCase()}}`],
42
+ ]),
43
+ ...greeks.flatMap(([char, ident]) => [
44
+ [`\\rm${ident}`, `{\\rm ${char}}`],
45
+ [`\\bf${ident}`, `\\boldsymbol{${char}}`],
46
+ [`\\rm${titleCase(ident)}`, `\\mathrm{${char.toUpperCase()}}`],
47
+ [`\\cal${titleCase(ident)}`, `\\mathcal{${char.toUpperCase()}}`],
48
+ [`\\bf${titleCase(ident)}`, `\\mathbf{${char.toUpperCase()}}`],
49
+ ]),
50
+ ]),
51
+ '\\im': '\\rmi',
52
+ '\\defeq': '\\coloneqq',
53
+ '\\eqdef': '\\eqqcolon',
54
+ };
55
+ // Both spellings are accepted. `katex_macros` is the documented one; documents
56
+ // written for other tools reach for the camelCase form often enough that
57
+ // rejecting it only produces a confusing wall of undefined-control-sequence
58
+ // errors.
59
+ const MACRO_FIELDS = ['katex_macros', 'katexMacros'];
60
+ // The macros a frontmatter mapping declares, in either spelling.
61
+ export function frontmatterMacros(frontmatter) {
62
+ if (!frontmatter || typeof frontmatter != 'object')
63
+ return {};
64
+ const macros = {};
65
+ for (const field of MACRO_FIELDS) {
66
+ const value = frontmatter[field];
67
+ if (value && typeof value == 'object')
68
+ Object.assign(macros, value);
69
+ }
70
+ return macros;
71
+ }
72
+ // mdsvex parks the parsed frontmatter on the vfile as `data.fm`.
73
+ export const documentMacros = (vfile) => frontmatterMacros(vfile.data?.fm);
74
+ const isMath = (node) => {
75
+ const classes = node.properties?.className;
76
+ if (!Array.isArray(classes))
77
+ return null;
78
+ if (classes.includes('math-display'))
79
+ return 'display';
80
+ if (classes.includes('math-inline'))
81
+ return 'inline';
82
+ return null;
83
+ };
84
+ // The TeX source, which `remark-math` leaves as the element's text content.
85
+ function textOf(node) {
86
+ let text = '';
87
+ const stack = [...node.children].reverse();
88
+ while (stack.length > 0) {
89
+ const child = stack.pop();
90
+ if (child.type == 'text')
91
+ text += child.value;
92
+ else if (child.type == 'element')
93
+ stack.push(...[...child.children].reverse());
94
+ }
95
+ return text;
96
+ }
97
+ // What was last reported for each document. A build compiles each document
98
+ // twice, once per Vite pass, so without this the same broken equation is
99
+ // reported twice for one command.
100
+ const lastReported = new Map();
101
+ // Renders every `.math-inline` / `.math-display` element in place, replacing its
102
+ // children with the one text node mdsvex needs.
103
+ //
104
+ // KaTeX is called directly rather than through `rehype-katex`: the escaping a
105
+ // Svelte template needs is a one-liner, and going through a wrapper meant the
106
+ // math was rendered by whichever KaTeX that wrapper depended on rather than the
107
+ // one whose stylesheet and fonts ship with the page. Skewed versions render
108
+ // markup the stylesheet does not fully cover.
109
+ export function renderMath(tree, filename, options = {}) {
110
+ // One copy per document: KaTeX writes `\gdef`s back into this object, so a
111
+ // definition made in one equation is visible to later ones, while the table
112
+ // it was copied from stays intact for the next document.
113
+ const settings = { ...options.katex, macros: { ...(options.katex?.macros ?? {}) } };
114
+ const failures = [];
115
+ const stack = [tree];
116
+ while (stack.length > 0) {
117
+ const node = stack.pop();
118
+ for (const child of node.children) {
119
+ if (child.type != 'element')
120
+ continue;
121
+ const mode = isMath(child);
122
+ if (!mode) {
123
+ stack.push(child);
124
+ continue;
125
+ }
126
+ const tex = textOf(child);
127
+ const displayMode = mode == 'display';
128
+ let html;
129
+ try {
130
+ html = katex.renderToString(tex, { ...settings, displayMode, throwOnError: true });
131
+ }
132
+ catch (error) {
133
+ // Rendered anyway, in KaTeX's error colour: a document being
134
+ // drafted should still produce a page, with the broken formula
135
+ // visible in place rather than a failed build.
136
+ html = katex.renderToString(tex, { ...settings, displayMode, throwOnError: false });
137
+ failures.push(`${summarise(error)}\n in: ${tex.trim().split('\n')[0]}`);
138
+ }
139
+ // `JSON.stringify` is what makes this Svelte rather than HTML, and
140
+ // `rehypeMathBox` reads the escaping it produces back out again.
141
+ child.children = [{ type: 'text', value: `{@html ${JSON.stringify(html)}}` }];
142
+ }
143
+ }
144
+ report(options.label ?? 'katex', filename, failures, options.watch ?? false);
145
+ }
146
+ // Every failure the document currently has, every time it is recompiled ---
147
+ // not just the ones that are new. While you are editing macros to fix an
148
+ // expression, an error that is still there is the thing you most need to be
149
+ // told about, and silence reads as success.
150
+ //
151
+ // Outside watch mode the same report would be printed once per build pass, so
152
+ // there it is emitted only when the set of failures actually changes.
153
+ function report(label, filename, failures, watch) {
154
+ const signature = failures.join('\n');
155
+ const previous = lastReported.get(filename);
156
+ lastReported.set(filename, signature);
157
+ if (!watch && signature == previous)
158
+ return;
159
+ if (failures.length == 0) {
160
+ // Only worth saying when it is news: the document had errors a moment
161
+ // ago and now does not.
162
+ if (watch && previous)
163
+ console.log(`${label}: ${filename}\n math renders cleanly`);
164
+ return;
165
+ }
166
+ for (const failure of failures)
167
+ console.warn(`${label}: ${filename}\n ${failure}`);
168
+ }
169
+ const summarise = (error) => (error instanceof Error ? error.message : String(error)).replace(/ at position \d+.*$/s, '');
170
+ export const rehypeKatex = (options = {}) => {
171
+ const base = options.base === false ? {} : baseMacros;
172
+ return (tree, vfile) => {
173
+ const macros = typeof options.macros == 'function'
174
+ ? { ...base, ...options.macros(vfile) }
175
+ : { ...base, ...(options.macros ?? {}), ...documentMacros(vfile) };
176
+ // mdsvex puts the document's absolute path on the vfile as `filename`,
177
+ // which is not one of vfile's own fields.
178
+ const filename = vfile.filename ?? 'document';
179
+ renderMath(tree, filename, {
180
+ katex: { ...options.katex, macros },
181
+ label: options.label,
182
+ watch: options.watch,
183
+ });
184
+ };
185
+ };
186
+ //# sourceMappingURL=katex.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"katex.js","sourceRoot":"","sources":["../src/katex.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { Plugin } from 'unified';
2
+ import type { Root } from 'hast';
3
+ export interface MathBoxOptions {
4
+ /** Move a `\tag` out of the scroll box, so the number stays in view. */
5
+ liftTags?: boolean;
6
+ }
7
+ export declare const rehypeMathBox: Plugin<[MathBoxOptions?], Root>;
@@ -0,0 +1,88 @@
1
+ const isDisplayMath = (node) => node.type == 'element' &&
2
+ node.tagName == 'div' &&
3
+ Array.isArray(node.properties?.className) &&
4
+ node.properties.className.includes('math-display');
5
+ const div = (className, children) => ({
6
+ type: 'element',
7
+ tagName: 'div',
8
+ properties: { className: [className] },
9
+ children,
10
+ });
11
+ // A display equation is one unbreakable box: on a narrow screen it either
12
+ // overflows the page or gets clipped. Wrapping it in a scroll container lets the
13
+ // equation slide sideways on its own while the prose around it stays put.
14
+ const scrollBox = (math) => div('math-scroll-container', [div('math-container', [math])]);
15
+ // The KaTeX markup of a `\tag`, cut out of the rendered equation so that it can
16
+ // sit beside the scroll box rather than scroll away with the equation.
17
+ //
18
+ // The equation reaches here as the `{@html "..."}` text node `rehypeKatex`
19
+ // leaves, so this works on the escaped string: it finds the tag's opening span,
20
+ // walks to the span that closes it, and drops the tag's first child --- a
21
+ // `katex-strut` that sizes the line for the equation it no longer sits in.
22
+ function liftTag(math) {
23
+ const text = math.children[0];
24
+ if (math.children.length != 1 || text?.type != 'text' || !text.value.startsWith('{@html')) {
25
+ return undefined;
26
+ }
27
+ const start = text.value.indexOf('<span class=\\"katex-tag\\"');
28
+ if (start <= 0)
29
+ return undefined;
30
+ const rest = text.value.slice(start);
31
+ let length = 0;
32
+ let depth = 0;
33
+ let strutStart = 0;
34
+ let strutStop = 0;
35
+ while (length < rest.length) {
36
+ if (rest.slice(length, length + 5) === '<span') {
37
+ if (depth == 1 && strutStart == 0)
38
+ strutStart = length;
39
+ depth++;
40
+ length += 5;
41
+ while (length < rest.length && rest[length] !== '>')
42
+ length++;
43
+ length++;
44
+ }
45
+ else if (rest.slice(length, length + 7) === '</span>') {
46
+ depth--;
47
+ length += 7;
48
+ if (strutStart != 0 && strutStop == 0)
49
+ strutStop = length;
50
+ if (depth === 0)
51
+ break;
52
+ }
53
+ else {
54
+ length++;
55
+ }
56
+ }
57
+ text.value = text.value.slice(0, start) + text.value.slice(start + length);
58
+ return rest.slice(0, strutStart) + rest.slice(strutStop, length);
59
+ }
60
+ export const rehypeMathBox = (options = {}) => {
61
+ return (tree) => {
62
+ const stack = [tree];
63
+ while (stack.length > 0) {
64
+ const children = stack.pop().children;
65
+ for (let i = 0; i < children.length; i++) {
66
+ const child = children[i];
67
+ if (child.type != 'element')
68
+ continue;
69
+ // The wrapper is not descended into: it still contains the
70
+ // display-math node, and revisiting it would wrap it again,
71
+ // forever.
72
+ if (!isDisplayMath(child)) {
73
+ stack.push(child);
74
+ continue;
75
+ }
76
+ const tag = options.liftTags ? liftTag(child) : undefined;
77
+ const box = scrollBox(child);
78
+ children[i] = tag
79
+ ? div('math-tag-container', [
80
+ box,
81
+ div('math-tag', [{ type: 'text', value: `{@html "${tag}"}` }]),
82
+ ])
83
+ : box;
84
+ }
85
+ }
86
+ };
87
+ };
88
+ //# sourceMappingURL=mathbox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mathbox.js","sourceRoot":"","sources":["../src/mathbox.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,59 @@
1
1
  {
2
2
  "name": "@mvarble/mesearch-markdown",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "The mdsvex pipeline shared by mesearch, mkdoc and the blog: KaTeX with layered macros, scrollable display math, Shiki highlighting and relative assets.",
5
+ "license": "MIT",
6
+ "author": "Matthew Varble <matthew@rodent.club>",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/mvarble/mesearch.git",
10
+ "directory": "packages/markdown"
11
+ },
12
+ "publishConfig": {
13
+ "access": "public"
14
+ },
15
+ "type": "module",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "import": "./dist/index.js"
20
+ },
21
+ "./katex": {
22
+ "types": "./dist/katex.d.ts",
23
+ "import": "./dist/katex.js"
24
+ },
25
+ "./styles/katex.css": "./styles/katex.css",
26
+ "./package.json": "./package.json"
27
+ },
28
+ "files": [
29
+ "dist",
30
+ "styles"
31
+ ],
32
+ "engines": {
33
+ "node": ">=22.18"
34
+ },
35
+ "dependencies": {
36
+ "katex": "^0.18.4",
37
+ "mdsvex": "^0.12.8",
38
+ "remark-frontmatter": "^2.0.0",
39
+ "remark-math": "3.0.0",
40
+ "shiki": "^4.4.3"
41
+ },
42
+ "devDependencies": {
43
+ "@types/hast": "^3.0.5",
44
+ "@types/katex": "^0.16.7",
45
+ "@types/node": "^22.14.0",
46
+ "svelte": "^5.57.1",
47
+ "typescript": "^6.0.3",
48
+ "unified": "^11.0.5",
49
+ "vfile": "^6.0.3"
50
+ },
51
+ "peerDependencies": {
52
+ "svelte": "^5.0.0"
53
+ },
54
+ "scripts": {
55
+ "build": "tsc -p tsconfig.json",
56
+ "check": "tsc -p tsconfig.json --noEmit",
57
+ "test": "node --test 'test/**/*.test.mjs'"
58
+ }
6
59
  }
@@ -0,0 +1,71 @@
1
+ /* How rendered math sits in prose. `katex/dist/katex.min.css` itself is
2
+ * imported by each site's entry, which is what makes the bundler emit the KaTeX
3
+ * fonts alongside the page.
4
+ *
5
+ * Sizes are variables so that a site can tune them to its body font:
6
+ * --katex-font-size (default 1rem)
7
+ * --katex-font-size-hidpi (default 1.32rem, on screens of 1.75x and up) */
8
+
9
+ /* Display math is one unbreakable box. `rehypeMathBox` wraps it in these two
10
+ * containers so a long equation slides sideways on its own, fading out at the
11
+ * edges rather than being clipped. */
12
+ .math-scroll-container {
13
+ overflow-x: auto;
14
+ flex-grow: 1;
15
+ --fading-size: 3em;
16
+ mask-image: linear-gradient(
17
+ to right,
18
+ transparent 0%,
19
+ black var(--fading-size),
20
+ black calc(100% - var(--fading-size)),
21
+ transparent
22
+ );
23
+ -webkit-mask-image: linear-gradient(
24
+ to right,
25
+ transparent 0%,
26
+ black var(--fading-size),
27
+ black calc(100% - var(--fading-size)),
28
+ transparent
29
+ );
30
+ }
31
+
32
+ /* A `\tag` lifted out of the scroll box by `rehypeMathBox({ liftTags: true })`
33
+ * stays in view beside it. */
34
+ .math-tag-container {
35
+ display: flex;
36
+ flex-direction: row;
37
+ align-items: center;
38
+ }
39
+
40
+ .math-tag {
41
+ margin-left: 2em !important;
42
+ }
43
+
44
+ /* An inline expression never breaks across lines: justified prose stretches
45
+ * the spaces around it instead. */
46
+ .katex-html {
47
+ display: inline-block;
48
+ }
49
+
50
+ .katex {
51
+ line-height: 1;
52
+ font-size: var(--katex-font-size, 1rem);
53
+ }
54
+
55
+ .katex-display {
56
+ margin: 0.5em 0 !important;
57
+
58
+ & .katex-base:first-child {
59
+ margin-left: var(--fading-size) !important;
60
+ }
61
+
62
+ & .katex-base:last-child {
63
+ margin-right: var(--fading-size) !important;
64
+ }
65
+ }
66
+
67
+ @media (min-resolution: 1.75x) {
68
+ .katex {
69
+ font-size: var(--katex-font-size-hidpi, 1.32rem);
70
+ }
71
+ }