@aparte/plugin-katex 0.0.0-stage → 0.28.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 aparté
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,68 @@
1
- # Temporary Holding Version
1
+ # @aparte/plugin-katex
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
+ Maths for [aparté](https://github.com/apartejs/aparte) via [KaTeX](https://katex.org). Every
4
+ `$…$`, `$$…$$`, `\(…\)` and `\[…\]` in a reply is drawn as a formula — while the reply streams,
5
+ under whichever Markdown provider recognises maths.
6
+
7
+ ```bash
8
+ npm install @aparte/plugin-katex @aparte/core katex
9
+ ```
10
+
11
+ ```ts
12
+ import 'katex/dist/katex.min.css';
13
+ import { setupKatex } from '@aparte/plugin-katex';
14
+
15
+ setupKatex();
16
+ ```
17
+
18
+ `@aparte/core` and `katex` are **peer dependencies**. KaTeX's stylesheet is yours to import: its
19
+ fonts are relative URLs your bundler resolves. Without it each formula shows twice, and the plugin
20
+ says so once in the console.
21
+
22
+ ## Which Markdown provider
23
+
24
+ Maths has to be recognised before Markdown reads it — `\,` and `a*b*c` mean something else to
25
+ Markdown. `@aparte/plugin-marked` (one-shot and `/streaming`) and
26
+ `@aparte/plugin-streaming-markdown` recognise it on their own once this plugin is registered.
27
+ A provider of your own hands maths over by emitting
28
+ `<code class="language-math math-inline">TeX</code>`, or `<pre><code class="language-math
29
+ math-display">` for a block — the markup remark-math already produces.
30
+
31
+ A single `$` follows Pandoc's rule, so a price stays a price: `$5 and $10` is text.
32
+
33
+ ## While a reply streams
34
+
35
+ A formula is drawn as it arrives. One KaTeX cannot parse yet — `\frac{1}{` — keeps the last
36
+ drawing that it could, so nothing flashes red mid-stream. Once the reply is finished, a formula
37
+ that still does not parse shows its TeX, quietly, with KaTeX's message as a tooltip.
38
+
39
+ ## Options
40
+
41
+ `setupKatex(options?, config?)` takes `KatexOptions`:
42
+
43
+ | option | default | |
44
+ | --- | --- | --- |
45
+ | `live` | `true` | `false` draws a formula once it has closed |
46
+ | `katex` | `{}` | KaTeX's own options: `macros`, `leqno`, `fleqn`, `output`… |
47
+ | `maxLength` | `4000` | a longer formula shows as TeX |
48
+
49
+ What the plugin keeps, whatever `katex` says: `trust` is `false` (no `\href`, no `\htmlClass`),
50
+ `maxSize` and `maxExpand` are capped, and `macros` is copied for each formula, so a `\gdef` in one
51
+ reply cannot reach the next.
52
+
53
+ ## Two entry points
54
+
55
+ `@aparte/plugin-katex` loads KaTeX on the first formula. If you would rather hand it over —
56
+ your own build, a CDN copy — use the entry that imports nothing from `katex`:
57
+
58
+ ```ts
59
+ import katex from 'katex';
60
+ import { setupKatexWith } from '@aparte/plugin-katex/core';
61
+
62
+ setupKatexWith(katex);
63
+ ```
64
+
65
+ `setupKatexWith` takes the instance, or a function returning a promise of it, and any object
66
+ shaped like `KatexLike` (a `renderToString`). Both functions return a disposer.
67
+
68
+ ESM only.
package/dist/core.d.ts ADDED
@@ -0,0 +1,42 @@
1
+ import { type AparteConfig } from '@aparte/core';
2
+ /**
3
+ * The part of KaTeX this plugin calls. Structural, so this module imports nothing from
4
+ * `katex` — not even a type — and a bundle that hands over its own instance carries no
5
+ * second copy.
6
+ */
7
+ export interface KatexLike {
8
+ renderToString(tex: string, options?: Record<string, unknown>): string;
9
+ }
10
+ export interface KatexOptions {
11
+ /**
12
+ * Draw a formula while it arrives, keeping the last drawing that parsed until the
13
+ * next one does (default `true`). `false` draws it once it has closed.
14
+ */
15
+ live?: boolean;
16
+ /**
17
+ * Options for KaTeX itself — `macros`, `leqno`, `fleqn`, `output`, `minRuleThickness`…
18
+ * The plugin keeps the ones that make model-written TeX safe to draw: `trust` is
19
+ * always `false`, `maxSize` and `maxExpand` are capped, and `macros` is copied for
20
+ * each formula so a `\gdef` in one reply cannot reach the next.
21
+ */
22
+ katex?: Record<string, unknown>;
23
+ /** The longest formula drawn, in characters (default 4000); a longer one shows as TeX. */
24
+ maxLength?: number;
25
+ }
26
+ /**
27
+ * Draw maths with a KaTeX you provide — the instance, or a function that loads it on the
28
+ * first formula. The entry point for a bundle that controls what it ships:
29
+ * `@aparte/plugin-katex` itself is this, with `() => import('katex')`.
30
+ *
31
+ * KaTeX's stylesheet is yours to import (`katex/dist/katex.min.css`): its fonts are
32
+ * relative URLs your bundler resolves. Without it each formula shows twice.
33
+ *
34
+ * @returns a disposer that unregisters the renderer.
35
+ * @example
36
+ * import katex from 'katex';
37
+ * import 'katex/dist/katex.min.css';
38
+ * import { setupKatexWith } from '@aparte/plugin-katex/core';
39
+ * setupKatexWith(katex);
40
+ */
41
+ export declare function setupKatexWith(katex: KatexLike | (() => Promise<KatexLike>), options?: KatexOptions, config?: AparteConfig): () => void;
42
+ //# sourceMappingURL=core.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkC,KAAK,YAAY,EAAkC,MAAM,cAAc,CAAC;AAGjH;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACtB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC;CAC1E;AAED,MAAM,WAAW,YAAY;IACzB;;;OAGG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IACf;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,0FAA0F;IAC1F,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAWD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAC1B,KAAK,EAAE,SAAS,GAAG,CAAC,MAAM,OAAO,CAAC,SAAS,CAAC,CAAC,EAC7C,OAAO,GAAE,YAAiB,EAC1B,MAAM,GAAE,YAAiC,GAC1C,MAAM,IAAI,CAGZ"}
package/dist/core.js ADDED
@@ -0,0 +1,119 @@
1
+ import { aparteGlobalConfig, escapeHtml } from "@aparte/core";
2
+ function checkStyles() {
3
+ if (typeof document === "undefined" || typeof getComputedStyle !== "function") return;
4
+ queueMicrotask(() => {
5
+ const probe = document.createElement("span");
6
+ probe.className = "katex";
7
+ probe.innerHTML = '<span class="katex-mathml"></span>';
8
+ probe.style.position = "absolute";
9
+ probe.style.visibility = "hidden";
10
+ document.body.appendChild(probe);
11
+ const hidden = getComputedStyle(probe.firstElementChild).position === "absolute";
12
+ probe.remove();
13
+ if (!hidden) {
14
+ console.warn("[aparte] @aparte/plugin-katex: KaTeX's stylesheet is not loaded, so each formula shows twice. Import 'katex/dist/katex.min.css'.");
15
+ }
16
+ });
17
+ }
18
+ const FRAME_MS = 16;
19
+ const TALLEST_EM = 100;
20
+ function setupKatexWith(katex, options = {}, config = aparteGlobalConfig) {
21
+ const load = typeof katex === "function" ? katex : () => katex;
22
+ return config.registerCodeLanguageRenderer(createKatexRenderer(load, options));
23
+ }
24
+ function createKatexRenderer(load, options) {
25
+ const live = options.live !== false;
26
+ const maxLength = options.maxLength ?? 4e3;
27
+ const user = options.katex ?? {};
28
+ let katex;
29
+ let loading;
30
+ let checkedStyles = false;
31
+ const paces = /* @__PURE__ */ new WeakMap();
32
+ const partOf = (host) => host.closest("[data-segment-id], .aparte-content") ?? host.parentElement ?? host;
33
+ const ensure = () => {
34
+ if (katex) return katex;
35
+ if (loading) return loading;
36
+ const got = load();
37
+ if (!(got instanceof Promise)) return katex = got;
38
+ loading ??= got.then((k) => katex = k, (error) => {
39
+ loading = void 0;
40
+ throw error;
41
+ });
42
+ return loading;
43
+ };
44
+ const drawPaced = (pace, k, ctx) => {
45
+ const started = performance.now();
46
+ const html = draw(k, ctx);
47
+ pace.at = started;
48
+ pace.took = performance.now() - started;
49
+ return html;
50
+ };
51
+ const draw = (k, ctx) => {
52
+ const tex = ctx.source.trim();
53
+ if (tex.length > maxLength) return ctx.open ? null : failed(tex, "This formula is too long to draw.");
54
+ try {
55
+ const html = k.renderToString(tex, {
56
+ output: "htmlAndMathml",
57
+ strict: "ignore",
58
+ ...user,
59
+ displayMode: ctx.display,
60
+ throwOnError: true,
61
+ trust: false,
62
+ maxSize: Math.min(Number(user["maxSize"] ?? 10), 10),
63
+ maxExpand: Math.min(Number(user["maxExpand"] ?? 500), 500),
64
+ macros: { ...user["macros"] }
65
+ });
66
+ if (!checkedStyles) {
67
+ checkedStyles = true;
68
+ checkStyles();
69
+ }
70
+ if (tallestLine(html) > TALLEST_EM) return ctx.open ? null : failed(tex, "This formula is too tall to draw.");
71
+ return html;
72
+ } catch (error) {
73
+ return ctx.open ? null : failed(tex, error instanceof Error ? error.message : String(error));
74
+ }
75
+ };
76
+ return {
77
+ languages: ["math"],
78
+ render(ctx) {
79
+ if (ctx.open) {
80
+ if (!live) return null;
81
+ const loaded = ensure();
82
+ if (loaded instanceof Promise) return loaded.then((k2) => draw(k2, ctx), () => null);
83
+ const part = partOf(ctx.host);
84
+ let pace = paces.get(part);
85
+ if (!pace) {
86
+ pace = { at: -Infinity, took: 0, waiting: null, timer: void 0 };
87
+ paces.set(part, pace);
88
+ }
89
+ const wait = pace.at + Math.max(FRAME_MS, pace.took) - performance.now();
90
+ if (wait <= 0 && !pace.waiting) return drawPaced(pace, loaded, ctx);
91
+ const current = pace;
92
+ pace.waiting?.answer(null);
93
+ return new Promise((answer) => {
94
+ current.waiting = { ctx, answer };
95
+ current.timer ??= setTimeout(() => {
96
+ current.timer = void 0;
97
+ const job = current.waiting;
98
+ current.waiting = null;
99
+ if (job) job.answer(drawPaced(current, loaded, job.ctx));
100
+ }, Math.max(0, wait));
101
+ });
102
+ }
103
+ const k = ensure();
104
+ return k instanceof Promise ? k.then((loaded) => draw(loaded, ctx)) : draw(k, ctx);
105
+ }
106
+ };
107
+ }
108
+ function tallestLine(html) {
109
+ let tallest = 0;
110
+ for (const match of html.matchAll(/strut" style="height:([\d.]+)em/g)) tallest = Math.max(tallest, Number(match[1]));
111
+ return tallest;
112
+ }
113
+ function failed(tex, message) {
114
+ return `<code class="aparte-code-error" title="${escapeHtml(message)}">${escapeHtml(tex)}</code>`;
115
+ }
116
+ export {
117
+ setupKatexWith
118
+ };
119
+ //# sourceMappingURL=core.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.js","sources":["../src/stylesheet-check.ts","../src/core.ts"],"sourcesContent":["/**\n * KaTeX draws every formula twice — once as MathML for screen readers, once as HTML to\n * see — and its stylesheet is what hides the first. Missing, the formula shows twice,\n * which is easy to mistake for a bug here; say so once, where the developer looks.\n */\nexport function checkStyles(): void {\n if (typeof document === 'undefined' || typeof getComputedStyle !== 'function') return;\n queueMicrotask(() => {\n const probe = document.createElement('span');\n probe.className = 'katex';\n probe.innerHTML = '<span class=\"katex-mathml\"></span>';\n probe.style.position = 'absolute';\n probe.style.visibility = 'hidden';\n document.body.appendChild(probe);\n const hidden = getComputedStyle(probe.firstElementChild!).position === 'absolute';\n probe.remove();\n if (!hidden) {\n console.warn(\"[aparte] @aparte/plugin-katex: KaTeX's stylesheet is not loaded, so each formula shows twice. Import 'katex/dist/katex.min.css'.\");\n }\n });\n}\n","import { aparteGlobalConfig, escapeHtml, type AparteConfig, type AparteCodeLanguageContext } from '@aparte/core';\nimport { checkStyles } from './stylesheet-check.js';\n\n/**\n * The part of KaTeX this plugin calls. Structural, so this module imports nothing from\n * `katex` — not even a type — and a bundle that hands over its own instance carries no\n * second copy.\n */\nexport interface KatexLike {\n renderToString(tex: string, options?: Record<string, unknown>): string;\n}\n\nexport interface KatexOptions {\n /**\n * Draw a formula while it arrives, keeping the last drawing that parsed until the\n * next one does (default `true`). `false` draws it once it has closed.\n */\n live?: boolean;\n /**\n * Options for KaTeX itself — `macros`, `leqno`, `fleqn`, `output`, `minRuleThickness`…\n * The plugin keeps the ones that make model-written TeX safe to draw: `trust` is\n * always `false`, `maxSize` and `maxExpand` are capped, and `macros` is copied for\n * each formula so a `\\gdef` in one reply cannot reach the next.\n */\n katex?: Record<string, unknown>;\n /** The longest formula drawn, in characters (default 4000); a longer one shows as TeX. */\n maxLength?: number;\n}\n\n/** How long a frame is, for the live redraw's pace. */\nconst FRAME_MS = 16;\n/**\n * The tallest line a formula may lay out, in em. KaTeX caps a size it is asked for\n * (`maxSize`) but not a negative shift: `\\raisebox{-10000em}{x}` makes a line 10000em\n * tall, a bubble a few hundred thousand pixels high. A 30-row matrix is under 40em.\n */\nconst TALLEST_EM = 100;\n\n/**\n * Draw maths with a KaTeX you provide — the instance, or a function that loads it on the\n * first formula. The entry point for a bundle that controls what it ships:\n * `@aparte/plugin-katex` itself is this, with `() => import('katex')`.\n *\n * KaTeX's stylesheet is yours to import (`katex/dist/katex.min.css`): its fonts are\n * relative URLs your bundler resolves. Without it each formula shows twice.\n *\n * @returns a disposer that unregisters the renderer.\n * @example\n * import katex from 'katex';\n * import 'katex/dist/katex.min.css';\n * import { setupKatexWith } from '@aparte/plugin-katex/core';\n * setupKatexWith(katex);\n */\nexport function setupKatexWith(\n katex: KatexLike | (() => Promise<KatexLike>),\n options: KatexOptions = {},\n config: AparteConfig = aparteGlobalConfig,\n): () => void {\n const load = typeof katex === 'function' ? katex : () => katex;\n return config.registerCodeLanguageRenderer(createKatexRenderer(load, options));\n}\n\n/** The renderer, over a KaTeX that may not be loaded yet. */\nfunction createKatexRenderer(\n load: () => KatexLike | Promise<KatexLike>,\n options: KatexOptions,\n) {\n const live = options.live !== false;\n const maxLength = options.maxLength ?? 4000;\n const user = options.katex ?? {};\n let katex: KatexLike | undefined;\n let loading: Promise<KatexLike> | undefined;\n let checkedStyles = false;\n /**\n * The pace of a formula still arriving, per reply part: when it was last drawn, how\n * long that took, and the latest source waiting for the next turn. The next drawing\n * waits a frame, or as long as the last one took if that was longer (a matrix is\n * slow) — and then it IS drawn: a source asked for inside the wait is answered once\n * the wait is over, so the last token of a formula is never left undrawn.\n */\n interface Pace {\n at: number;\n took: number;\n waiting: { ctx: AparteCodeLanguageContext; answer: (html: string | null) => void } | null;\n timer: ReturnType<typeof setTimeout> | undefined;\n }\n const paces = new WeakMap<Element, Pace>();\n /**\n * The reply part a formula belongs to, which outlives a rebuilt block: its segment, or\n * the bubble's content when the reply has no segments — not the paragraph, which the\n * Markdown provider writes anew on every token.\n */\n const partOf = (host: HTMLElement): Element => host.closest('[data-segment-id], .aparte-content') ?? host.parentElement ?? host;\n\n const ensure = (): KatexLike | Promise<KatexLike> => {\n if (katex) return katex;\n if (loading) return loading;\n const got = load();\n if (!(got instanceof Promise)) return (katex = got);\n // Not poisoned: a failed load is tried again on the next formula.\n loading ??= got.then((k) => (katex = k), (error: unknown) => { loading = undefined; throw error; });\n return loading;\n };\n\n const drawPaced = (pace: Pace, k: KatexLike, ctx: AparteCodeLanguageContext): string | null => {\n const started = performance.now();\n const html = draw(k, ctx);\n pace.at = started;\n pace.took = performance.now() - started;\n return html;\n };\n\n const draw = (k: KatexLike, ctx: AparteCodeLanguageContext): string | null => {\n const tex = ctx.source.trim();\n if (tex.length > maxLength) return ctx.open ? null : failed(tex, 'This formula is too long to draw.');\n try {\n const html = k.renderToString(tex, {\n output: 'htmlAndMathml',\n strict: 'ignore',\n ...user,\n displayMode: ctx.display,\n throwOnError: true,\n trust: false,\n maxSize: Math.min(Number(user['maxSize'] ?? 10), 10),\n maxExpand: Math.min(Number(user['maxExpand'] ?? 500), 500),\n macros: { ...(user['macros'] as Record<string, string> | undefined) },\n });\n if (!checkedStyles) { checkedStyles = true; checkStyles(); }\n if (tallestLine(html) > TALLEST_EM) return ctx.open ? null : failed(tex, 'This formula is too tall to draw.');\n return html;\n } catch (error) {\n // Half a formula does not parse; the last drawing stays until the next one does.\n return ctx.open ? null : failed(tex, error instanceof Error ? error.message : String(error));\n }\n };\n\n return {\n languages: ['math'] as const,\n render(ctx: AparteCodeLanguageContext): string | null | Promise<string | null> {\n if (ctx.open) {\n if (!live) return null;\n const loaded = ensure();\n // Still loading: answered once KaTeX is there, not left as TeX until a later token.\n if (loaded instanceof Promise) return loaded.then((k) => draw(k, ctx), () => null);\n const part = partOf(ctx.host);\n let pace = paces.get(part);\n if (!pace) { pace = { at: -Infinity, took: 0, waiting: null, timer: undefined }; paces.set(part, pace); }\n const wait = pace.at + Math.max(FRAME_MS, pace.took) - performance.now();\n if (wait <= 0 && !pace.waiting) return drawPaced(pace, loaded, ctx);\n // Inside the wait: the latest source takes the next turn, the one it replaces\n // is answered null (the engine asks again for anything still current).\n const current = pace;\n pace.waiting?.answer(null);\n return new Promise<string | null>((answer) => {\n current.waiting = { ctx, answer };\n current.timer ??= setTimeout(() => {\n current.timer = undefined;\n const job = current.waiting;\n current.waiting = null;\n if (job) job.answer(drawPaced(current, loaded, job.ctx));\n }, Math.max(0, wait));\n });\n }\n const k = ensure();\n return k instanceof Promise ? k.then((loaded) => draw(loaded, ctx)) : draw(k, ctx);\n },\n };\n}\n\n/** The height of the tallest line KaTeX laid a formula out on: each line's strut, in em. */\nfunction tallestLine(html: string): number {\n let tallest = 0;\n for (const match of html.matchAll(/strut\" style=\"height:([\\d.]+)em/g)) tallest = Math.max(tallest, Number(match[1]));\n return tallest;\n}\n\nfunction failed(tex: string, message: string): string {\n return `<code class=\"aparte-code-error\" title=\"${escapeHtml(message)}\">${escapeHtml(tex)}</code>`;\n}\n"],"names":["k"],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import { type AparteConfig } from '@aparte/core';
2
+ import { type KatexOptions } from './core.js';
3
+ export { setupKatexWith } from './core.js';
4
+ export type { KatexLike, KatexOptions } from './core.js';
5
+ /**
6
+ * Draw maths with [KaTeX](https://katex.org): `$…$`, `$$…$$`, `\(…\)` and `\[…\]` in a
7
+ * reply, as a Markdown provider that recognises maths hands them over
8
+ * (`@aparte/plugin-marked`, `@aparte/plugin-streaming-markdown`). A formula is drawn
9
+ * while it arrives; one KaTeX cannot parse yet keeps the last drawing that it could.
10
+ *
11
+ * KaTeX is loaded on the first formula, not before. Its stylesheet is yours to import,
12
+ * because its fonts are relative URLs your bundler resolves:
13
+ *
14
+ * ```ts
15
+ * import 'katex/dist/katex.min.css';
16
+ * ```
17
+ *
18
+ * @returns a disposer that unregisters the renderer.
19
+ * @example
20
+ * import 'katex/dist/katex.min.css';
21
+ * import { setupKatex } from '@aparte/plugin-katex';
22
+ * setupKatex();
23
+ */
24
+ export declare function setupKatex(options?: KatexOptions, config?: AparteConfig): () => void;
25
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AACrE,OAAO,EAAkC,KAAK,YAAY,EAAE,MAAM,WAAW,CAAC;AAE9E,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAEzD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,UAAU,CAAC,OAAO,GAAE,YAAiB,EAAE,MAAM,GAAE,YAAiC,GAAG,MAAM,IAAI,CAM5G"}
package/dist/index.js ADDED
@@ -0,0 +1,14 @@
1
+ import { aparteGlobalConfig } from "@aparte/core";
2
+ import { setupKatexWith } from "./core.js";
3
+ function setupKatex(options = {}, config = aparteGlobalConfig) {
4
+ return setupKatexWith(
5
+ () => import("katex").then((m) => m.default ?? m),
6
+ options,
7
+ config
8
+ );
9
+ }
10
+ export {
11
+ setupKatex,
12
+ setupKatexWith
13
+ };
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["import { aparteGlobalConfig, type AparteConfig } from '@aparte/core';\nimport { setupKatexWith, type KatexLike, type KatexOptions } from './core.js';\n\nexport { setupKatexWith } from './core.js';\nexport type { KatexLike, KatexOptions } from './core.js';\n\n/**\n * Draw maths with [KaTeX](https://katex.org): `$…$`, `$$…$$`, `\\(…\\)` and `\\[…\\]` in a\n * reply, as a Markdown provider that recognises maths hands them over\n * (`@aparte/plugin-marked`, `@aparte/plugin-streaming-markdown`). A formula is drawn\n * while it arrives; one KaTeX cannot parse yet keeps the last drawing that it could.\n *\n * KaTeX is loaded on the first formula, not before. Its stylesheet is yours to import,\n * because its fonts are relative URLs your bundler resolves:\n *\n * ```ts\n * import 'katex/dist/katex.min.css';\n * ```\n *\n * @returns a disposer that unregisters the renderer.\n * @example\n * import 'katex/dist/katex.min.css';\n * import { setupKatex } from '@aparte/plugin-katex';\n * setupKatex();\n */\nexport function setupKatex(options: KatexOptions = {}, config: AparteConfig = aparteGlobalConfig): () => void {\n return setupKatexWith(\n () => import('katex').then((m) => (m.default ?? m) as unknown as KatexLike),\n options,\n config,\n );\n}\n"],"names":[],"mappings":";;AAyBO,SAAS,WAAW,UAAwB,IAAI,SAAuB,oBAAgC;AAC1G,SAAO;AAAA,IACH,MAAM,OAAO,OAAO,EAAE,KAAK,CAAC,MAAO,EAAE,WAAW,CAA0B;AAAA,IAC1E;AAAA,IACA;AAAA,EAAA;AAER;"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * KaTeX draws every formula twice — once as MathML for screen readers, once as HTML to
3
+ * see — and its stylesheet is what hides the first. Missing, the formula shows twice,
4
+ * which is easy to mistake for a bug here; say so once, where the developer looks.
5
+ */
6
+ export declare function checkStyles(): void;
7
+ //# sourceMappingURL=stylesheet-check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stylesheet-check.d.ts","sourceRoot":"","sources":["../src/stylesheet-check.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,WAAW,IAAI,IAAI,CAelC"}
package/package.json CHANGED
@@ -1,6 +1,67 @@
1
1
  {
2
2
  "name": "@aparte/plugin-katex",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.28.0",
4
+ "homepage": "https://apartejs.dev/guides/math/",
5
+ "description": "Maths for aparté via KaTeX — formulas drawn as they stream, under any Markdown provider.",
6
+ "type": "module",
7
+ "sideEffects": false,
8
+ "main": "./dist/index.js",
9
+ "module": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "@aparte-workspace/source": "./src/index.ts",
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js",
16
+ "default": "./dist/index.js"
17
+ },
18
+ "./core": {
19
+ "@aparte-workspace/source": "./src/core.ts",
20
+ "types": "./dist/core.d.ts",
21
+ "import": "./dist/core.js",
22
+ "default": "./dist/core.js"
23
+ }
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "README.md",
28
+ "LICENSE"
29
+ ],
30
+ "engines": {
31
+ "node": ">=18"
32
+ },
33
+ "peerDependencies": {
34
+ "@aparte/core": ">=0.28.0 <1.0.0",
35
+ "katex": ">=0.18.2 <1.0.0"
36
+ },
37
+ "devDependencies": {
38
+ "katex": "^0.19.1",
39
+ "typescript": "~5.7.0",
40
+ "vite": "^6.0.0",
41
+ "@aparte/core": "0.28.0"
42
+ },
43
+ "keywords": [
44
+ "aparte",
45
+ "plugin",
46
+ "math",
47
+ "latex",
48
+ "katex"
49
+ ],
50
+ "license": "MIT",
51
+ "repository": {
52
+ "type": "git",
53
+ "url": "git+https://github.com/apartejs/aparte.git",
54
+ "directory": "packages/plugins/katex"
55
+ },
56
+ "bugs": {
57
+ "url": "https://github.com/apartejs/aparte/issues"
58
+ },
59
+ "scripts": {
60
+ "dev": "vite",
61
+ "build": "vite build && tsc -b --emitDeclarationOnly --force && node ../../../scripts/stamp-dist.mjs",
62
+ "preview": "vite preview",
63
+ "test": "vitest",
64
+ "test:run": "vitest run",
65
+ "test:coverage": "vitest run --coverage"
66
+ }
6
67
  }