@aparte/plugin-mermaid 0.0.0-stage → 0.28.1

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,72 @@
1
- # Temporary Holding Version
1
+ # @aparte/plugin-mermaid
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
+ Diagrams for [aparté](https://github.com/apartejs/aparte) via [Mermaid](https://mermaid.js.org).
4
+ Every ```` ```mermaid ```` fence in a reply is drawn as a diagram — as it streams, whichever
5
+ Markdown provider is installed, or none.
6
+
7
+ ```bash
8
+ npm install @aparte/plugin-mermaid @aparte/core mermaid
9
+ ```
10
+
11
+ ```ts
12
+ import { setupMermaid } from '@aparte/plugin-mermaid';
13
+
14
+ setupMermaid();
15
+ ```
16
+
17
+ `@aparte/core` and `mermaid` (11.16.1 or later) are **peer dependencies**. Mermaid is loaded on
18
+ the first diagram, not before, and a diagram is drawn only once it comes near the screen, so
19
+ reopening a long conversation stays fast.
20
+
21
+ ## An image, so nothing in it reaches the page
22
+
23
+ Each diagram is an `<img>` of its SVG. A browser draws an image's SVG with no scripts and no
24
+ access to the page, so neither a label a model wrote nor a style inside the diagram can act on
25
+ the chat around it. Mermaid also runs with `securityLevel: 'strict'` and HTML labels off, and a
26
+ diagram's own `%%{init}%%` cannot turn either back on.
27
+
28
+ What that costs: the text in a diagram cannot be selected, and it is set in the system fonts,
29
+ since an image cannot load the page's. The copy button on the block copies the diagram's source.
30
+
31
+ ## While a reply streams
32
+
33
+ The diagram is redrawn as its fence arrives — at most every 300 ms, or as long as the last
34
+ drawing took — and keeps the last version that parsed. Once the fence closes, a diagram that
35
+ does not parse shows a note and its source.
36
+
37
+ ## The chat's colours
38
+
39
+ The diagram takes its colours from the chat's theme (`--aparte-bg`, `--aparte-text`,
40
+ `--aparte-primary`…) and is drawn again when the theme changes. Its alt text is the diagram's own
41
+ `accTitle` or `accDescr`, else "Diagram"; set `diagramAlt` and `diagramError` with
42
+ `extendLocale()` to translate the fallback and the error note.
43
+
44
+ ## Options
45
+
46
+ `setupMermaid(options?, config?)` takes `MermaidOptions`:
47
+
48
+ | option | default | |
49
+ | --- | --- | --- |
50
+ | `progressive` | `true` | `false` draws a diagram once its fence has closed |
51
+ | `mermaid` | `{}` | Mermaid's own configuration: a `flowchart` curve, `themeVariables`… |
52
+
53
+ ## Two entry points
54
+
55
+ `@aparte/plugin-mermaid` imports Mermaid on the first diagram. To hand it over yourself, use the
56
+ entry that imports nothing from `mermaid`:
57
+
58
+ ```ts
59
+ import mermaid from 'mermaid';
60
+ import { setupMermaidWith } from '@aparte/plugin-mermaid/core';
61
+
62
+ setupMermaidWith(mermaid);
63
+ ```
64
+
65
+ `setupMermaidWith` takes the instance, or a function returning a promise of it, and any object
66
+ shaped like `MermaidLike` (`initialize`, `parse`, `render`). Both functions return a disposer.
67
+
68
+ Mermaid keeps one configuration for the whole page, and the plugin calls `initialize` with its
69
+ own before every diagram it draws. If your page also draws diagrams of its own with the same
70
+ Mermaid, call `mermaid.initialize(yourConfig)` again before your own `render()` or `run()`.
71
+
72
+ ESM only.
package/dist/core.d.ts ADDED
@@ -0,0 +1,42 @@
1
+ import { type AparteConfig } from '@aparte/core';
2
+ /**
3
+ * The part of Mermaid this plugin calls. Structural, so this module imports nothing
4
+ * from `mermaid` — a bundle that hands over its own instance carries one copy.
5
+ */
6
+ export interface MermaidLike {
7
+ initialize(config: Record<string, unknown>): void;
8
+ parse(text: string, options?: {
9
+ suppressErrors?: boolean;
10
+ }): Promise<unknown>;
11
+ render(id: string, text: string): Promise<{
12
+ svg: string;
13
+ diagramType?: string;
14
+ }>;
15
+ }
16
+ export interface MermaidOptions {
17
+ /**
18
+ * Redraw a diagram while its fence arrives, keeping the last version that parsed
19
+ * (default `true`). `false` draws it once the fence has closed.
20
+ */
21
+ progressive?: boolean;
22
+ /**
23
+ * Mermaid configuration — a `flowchart` curve, `sequence` spacing, `themeVariables`
24
+ * over the ones read from the chat's theme. The plugin keeps the keys that make
25
+ * model-written diagrams safe to draw: `securityLevel` is `'strict'`, HTML labels
26
+ * are off, and a diagram's own `%%{init}%%` cannot change either.
27
+ */
28
+ mermaid?: Record<string, unknown>;
29
+ }
30
+ /**
31
+ * Draw diagrams with a Mermaid you provide — the instance, or a function that loads it
32
+ * on the first diagram. The entry point for a bundle that controls what it ships:
33
+ * `@aparte/plugin-mermaid` itself is this, with `() => import('mermaid')`.
34
+ *
35
+ * @returns a disposer that unregisters the renderer and stops watching the theme.
36
+ * @example
37
+ * import mermaid from 'mermaid';
38
+ * import { setupMermaidWith } from '@aparte/plugin-mermaid/core';
39
+ * setupMermaidWith(mermaid);
40
+ */
41
+ export declare function setupMermaidWith(mermaid: MermaidLike | (() => Promise<MermaidLike>), options?: MermaidOptions, 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,EAAmE,KAAK,YAAY,EAAkC,MAAM,cAAc,CAAC;AAElJ;;;GAGG;AACH,MAAM,WAAW,WAAW;IACxB,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAClD,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,cAAc,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9E,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACpF;AAED,MAAM,WAAW,cAAc;IAC3B;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACrC;AAiCD;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAC5B,OAAO,EAAE,WAAW,GAAG,CAAC,MAAM,OAAO,CAAC,WAAW,CAAC,CAAC,EACnD,OAAO,GAAE,cAAmB,EAC5B,MAAM,GAAE,YAAiC,GAC1C,MAAM,IAAI,CA2BZ"}
package/dist/core.js ADDED
@@ -0,0 +1,302 @@
1
+ import { aparteGlobalConfig, renderCodeLanguages, escapeHtml, escapeAttr } from "@aparte/core";
2
+ const SVG_NS = "http://www.w3.org/2000/svg";
3
+ const SYSTEM_FONTS = 'system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif';
4
+ const PROGRESSIVE_MS = 300;
5
+ const PENDING = '<span class="aparte-skeleton aparte-skeleton--rect" aria-hidden="true"></span>';
6
+ let queue = Promise.resolve();
7
+ let ids = 0;
8
+ const ID_PREFIX = `aparte-mermaid-${Math.random().toString(36).slice(2, 8)}`;
9
+ const serial = (job) => {
10
+ const run = queue.then(job, job);
11
+ queue = run.catch(() => void 0);
12
+ return run;
13
+ };
14
+ function setupMermaidWith(mermaid, options = {}, config = aparteGlobalConfig) {
15
+ const load = typeof mermaid === "function" ? mermaid : () => Promise.resolve(mermaid);
16
+ const shadows = /* @__PURE__ */ new Set();
17
+ const redraw = () => {
18
+ renderer.epoch++;
19
+ renderCodeLanguages(document.body, { refresh: true, language: "mermaid" });
20
+ for (const root of shadows) {
21
+ if (!root.host.isConnected) {
22
+ shadows.delete(root);
23
+ continue;
24
+ }
25
+ for (const child of Array.from(root.children)) renderCodeLanguages(child, { refresh: true, language: "mermaid" });
26
+ }
27
+ };
28
+ const watch = watchTheme(redraw);
29
+ const renderer = createMermaidRenderer(load, options, (host) => {
30
+ const root = host.getRootNode();
31
+ if (typeof ShadowRoot === "undefined" || !(root instanceof ShadowRoot) || shadows.has(root)) return;
32
+ for (const old of shadows) if (!old.host.isConnected) shadows.delete(old);
33
+ shadows.add(root);
34
+ watch.add(root);
35
+ });
36
+ const unregister = config.registerCodeLanguageRenderer(renderer);
37
+ return () => {
38
+ watch.stop();
39
+ shadows.clear();
40
+ unregister();
41
+ };
42
+ }
43
+ function createMermaidRenderer(load, options, seen) {
44
+ const progressive = options.progressive !== false;
45
+ const user = options.mermaid ?? {};
46
+ let instance;
47
+ const arriving = /* @__PURE__ */ new WeakMap();
48
+ const watched = /* @__PURE__ */ new WeakSet();
49
+ const stateOf = (host) => {
50
+ const part = host.closest("[data-segment-id]") ?? host.closest("pre")?.parentElement ?? host.parentElement ?? host;
51
+ let state = arriving.get(part);
52
+ if (!state) {
53
+ state = { waiting: null, running: false, timer: void 0, at: 0, took: 0, started: null };
54
+ arriving.set(part, state);
55
+ }
56
+ return state;
57
+ };
58
+ const schedule = (state) => {
59
+ if (state.running || state.timer !== void 0 || !state.waiting) return;
60
+ const wait = Math.max(0, state.at + Math.max(PROGRESSIVE_MS, state.took) - performance.now());
61
+ state.timer = setTimeout(() => {
62
+ state.timer = void 0;
63
+ const job = state.waiting;
64
+ state.waiting = null;
65
+ if (!job) return;
66
+ state.running = true;
67
+ const started = performance.now();
68
+ void draw(job.ctx).then(job.answer, () => job.answer(null)).finally(() => {
69
+ state.running = false;
70
+ state.at = performance.now();
71
+ state.took = state.at - started;
72
+ schedule(state);
73
+ });
74
+ }, wait);
75
+ };
76
+ const mermaidOf = () => {
77
+ instance ??= load().catch((error) => {
78
+ instance = void 0;
79
+ throw error;
80
+ });
81
+ return instance;
82
+ };
83
+ const draw = (ctx) => serial(async () => {
84
+ const source = ctx.open ? ctx.source.slice(0, ctx.source.lastIndexOf("\n") + 1) : ctx.source;
85
+ if (!source.trim()) return null;
86
+ const m = await mermaidOf();
87
+ m.initialize(mermaidConfig(user, ctx.host));
88
+ if (!await m.parse(source, { suppressErrors: true })) return null;
89
+ const id = `${ID_PREFIX}-${++ids}`;
90
+ try {
91
+ const { svg, diagramType } = await m.render(id, source);
92
+ return image(svg, diagramType, ctx.open);
93
+ } finally {
94
+ document.getElementById(id)?.remove();
95
+ document.getElementById(`d${id}`)?.remove();
96
+ }
97
+ });
98
+ const renderer = {
99
+ languages: ["mermaid"],
100
+ lazy: true,
101
+ epoch: 0,
102
+ /** The colours themselves, not the theme's name: two skins may share one name. */
103
+ cacheKey(ctx) {
104
+ seen(ctx.host);
105
+ return `${renderer.epoch}|${colourKey(ctx.host)}`;
106
+ },
107
+ render(ctx) {
108
+ if (!ctx.display) return null;
109
+ seen(ctx.host);
110
+ const state = stateOf(ctx.host);
111
+ const waiting = state.waiting;
112
+ if (!ctx.open) {
113
+ if (waiting && ctx.source.startsWith(waiting.ctx.source)) {
114
+ waiting.answer(null);
115
+ state.waiting = null;
116
+ state.started = null;
117
+ }
118
+ return draw(ctx).then(
119
+ (html) => html ?? failed(ctx),
120
+ () => failed(ctx)
121
+ );
122
+ }
123
+ waiting?.answer(null);
124
+ state.waiting = null;
125
+ if (!progressive) return null;
126
+ if (state.started === null || !ctx.source.startsWith(state.started)) {
127
+ state.started = ctx.source;
128
+ return PENDING;
129
+ }
130
+ return new Promise((answer) => {
131
+ state.waiting = { ctx, answer };
132
+ schedule(state);
133
+ });
134
+ },
135
+ relabel(drawing, ctx) {
136
+ const locale = ctx.config.getLocale();
137
+ const img = drawing.querySelector("img.aparte-diagram");
138
+ if (img) {
139
+ img.alt = describe(ctx.source) ?? String(locale["diagramAlt"] ?? "Diagram");
140
+ if (!watched.has(img)) {
141
+ watched.add(img);
142
+ const source = ctx.source;
143
+ img.addEventListener("error", () => img.replaceWith(errorNode(source)), { once: true });
144
+ }
145
+ }
146
+ const note = drawing.querySelector("[data-aparte-diagram-error]");
147
+ if (note) note.textContent = String(locale["diagramError"] ?? "This diagram could not be drawn.");
148
+ }
149
+ };
150
+ return renderer;
151
+ }
152
+ function mermaidConfig(user, host) {
153
+ const palette = paletteOf(host);
154
+ return {
155
+ ...user,
156
+ startOnLoad: false,
157
+ securityLevel: "strict",
158
+ htmlLabels: false,
159
+ suppressErrorRendering: true,
160
+ theme: "base",
161
+ fontFamily: SYSTEM_FONTS,
162
+ flowchart: { ...user["flowchart"], htmlLabels: false },
163
+ themeVariables: { ...palette, ...user["themeVariables"], fontFamily: SYSTEM_FONTS },
164
+ maxTextSize: 5e4,
165
+ maxEdges: 500,
166
+ // What a diagram's own `%%{init}%%` may not change.
167
+ secure: ["secure", "securityLevel", "startOnLoad", "maxTextSize", "maxEdges", "suppressErrorRendering", "htmlLabels", "flowchart", "themeCSS", "fontFamily"]
168
+ };
169
+ }
170
+ function image(svg, diagramType, arriving = false) {
171
+ const template = document.createElement("template");
172
+ template.innerHTML = svg.trim();
173
+ const root = template.content.firstElementChild;
174
+ if (!root || template.content.children.length !== 1 || root.localName !== "svg" || root.namespaceURI !== SVG_NS || root.getElementsByTagName("foreignObject").length > 0) return null;
175
+ const box = (root.getAttribute("viewBox") ?? "").split(/[\s,]+/).map(Number);
176
+ const width = Math.ceil(box[2] ?? 0);
177
+ const height = Math.ceil(box[3] ?? 0);
178
+ if (!(width > 0 && height > 0)) return null;
179
+ if (arriving && !drawsSomething(root)) return null;
180
+ root.setAttribute("width", String(width));
181
+ root.setAttribute("height", String(height));
182
+ root.removeAttribute("style");
183
+ const src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(new XMLSerializer().serializeToString(root))}`;
184
+ const type = diagramType ? ` data-diagram-type="${escapeHtml(diagramType)}"` : "";
185
+ return `<img class="aparte-diagram" src="${escapeAttr(src)}" width="${escapeAttr(String(width))}" height="${escapeAttr(String(height))}" alt=""${type} decoding="async" draggable="false">`;
186
+ }
187
+ function drawsSomething(root) {
188
+ for (const el of Array.from(root.querySelectorAll("path, rect, circle, ellipse, polygon, polyline, line, text, image, use"))) {
189
+ if (!el.closest("defs, marker, symbol, clipPath, mask, pattern")) return true;
190
+ }
191
+ return false;
192
+ }
193
+ function failed(ctx) {
194
+ return `<span class="aparte-code-error" role="note" data-aparte-diagram-error></span><pre class="aparte-code-error">${escapeHtml(ctx.source)}</pre>`;
195
+ }
196
+ function errorNode(source) {
197
+ const pre = document.createElement("pre");
198
+ pre.className = "aparte-code-error";
199
+ pre.textContent = source;
200
+ return pre;
201
+ }
202
+ function describe(source) {
203
+ return /^\s*accTitle\s*:\s*(.+)$/m.exec(source)?.[1]?.trim() ?? /^\s*accDescr\s*:\s*(.+)$/m.exec(source)?.[1]?.trim();
204
+ }
205
+ function themeOf(host) {
206
+ const attr = host.closest("[data-aparte-theme]")?.getAttribute("data-aparte-theme");
207
+ if (attr) return attr;
208
+ return typeof matchMedia === "function" && matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
209
+ }
210
+ const TOKENS = {
211
+ bg: ["--aparte-bg", "#ffffff"],
212
+ text: ["--aparte-text", "#1a1a1a"],
213
+ muted: ["--aparte-text-muted", "#666666"],
214
+ border: ["--aparte-border", "#d0d0d0"],
215
+ surface: ["--aparte-surface-2", "#f4f4f4"],
216
+ surfaceLow: ["--aparte-surface-1", "#fafafa"],
217
+ primary: ["--aparte-primary", "#3b5bdb"]
218
+ };
219
+ function colourKey(host) {
220
+ const theme = themeOf(host);
221
+ if (typeof getComputedStyle !== "function") return theme;
222
+ const style = getComputedStyle(host);
223
+ return [theme, style.colorScheme, ...Object.values(TOKENS).map(([token]) => style.getPropertyValue(token).trim())].join("|");
224
+ }
225
+ function paletteOf(host) {
226
+ if (typeof getComputedStyle !== "function") return {};
227
+ const probe = document.createElement("span");
228
+ probe.style.display = "none";
229
+ (host.parentElement ?? document.body).appendChild(probe);
230
+ const canvas = document.createElement("canvas").getContext?.("2d") ?? null;
231
+ const read = (token, fallback) => {
232
+ probe.style.color = `var(${token}, ${fallback})`;
233
+ const computed = getComputedStyle(probe).color || fallback;
234
+ if (!canvas) return computed;
235
+ canvas.fillStyle = "#000";
236
+ canvas.fillStyle = computed;
237
+ return String(canvas.fillStyle);
238
+ };
239
+ try {
240
+ const bg = read(...TOKENS.bg);
241
+ const text = read(...TOKENS.text);
242
+ const muted = read(...TOKENS.muted);
243
+ const border = read(...TOKENS.border);
244
+ const surface = read(...TOKENS.surface);
245
+ const surfaceLow = read(...TOKENS.surfaceLow);
246
+ const primary = read(...TOKENS.primary);
247
+ return {
248
+ darkMode: luminance(bg) < 0.5,
249
+ background: bg,
250
+ primaryColor: surface,
251
+ primaryTextColor: text,
252
+ primaryBorderColor: primary,
253
+ secondaryColor: surfaceLow,
254
+ tertiaryColor: surfaceLow,
255
+ mainBkg: surface,
256
+ nodeBorder: border,
257
+ clusterBkg: surfaceLow,
258
+ clusterBorder: border,
259
+ lineColor: muted,
260
+ textColor: text,
261
+ titleColor: text,
262
+ edgeLabelBackground: bg,
263
+ noteBkgColor: surfaceLow,
264
+ noteTextColor: text,
265
+ noteBorderColor: border,
266
+ actorBkg: surface,
267
+ actorBorder: primary,
268
+ actorTextColor: text,
269
+ signalColor: muted,
270
+ signalTextColor: text
271
+ };
272
+ } finally {
273
+ probe.remove();
274
+ }
275
+ }
276
+ function luminance(color) {
277
+ const hex = /^#([0-9a-f]{6})$/i.exec(color)?.[1];
278
+ if (!hex) return 1;
279
+ const [r, g, b] = [0, 2, 4].map((i) => parseInt(hex.slice(i, i + 2), 16) / 255);
280
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
281
+ }
282
+ function watchTheme(changed) {
283
+ if (typeof MutationObserver === "undefined" || typeof document === "undefined") return { add: () => {
284
+ }, stop: () => {
285
+ } };
286
+ const observer = new MutationObserver(changed);
287
+ const options = { attributes: true, subtree: true, attributeFilter: ["data-aparte-theme"] };
288
+ observer.observe(document.documentElement, options);
289
+ const media = typeof matchMedia === "function" ? matchMedia("(prefers-color-scheme: dark)") : null;
290
+ media?.addEventListener("change", changed);
291
+ return {
292
+ add: (root) => observer.observe(root, options),
293
+ stop: () => {
294
+ observer.disconnect();
295
+ media?.removeEventListener("change", changed);
296
+ }
297
+ };
298
+ }
299
+ export {
300
+ setupMermaidWith
301
+ };
302
+ //# sourceMappingURL=core.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.js","sources":["../src/core.ts"],"sourcesContent":["import { aparteGlobalConfig, escapeAttr, escapeHtml, renderCodeLanguages, type AparteConfig, type AparteCodeLanguageContext } from '@aparte/core';\n\n/**\n * The part of Mermaid this plugin calls. Structural, so this module imports nothing\n * from `mermaid` — a bundle that hands over its own instance carries one copy.\n */\nexport interface MermaidLike {\n initialize(config: Record<string, unknown>): void;\n parse(text: string, options?: { suppressErrors?: boolean }): Promise<unknown>;\n render(id: string, text: string): Promise<{ svg: string; diagramType?: string }>;\n}\n\nexport interface MermaidOptions {\n /**\n * Redraw a diagram while its fence arrives, keeping the last version that parsed\n * (default `true`). `false` draws it once the fence has closed.\n */\n progressive?: boolean;\n /**\n * Mermaid configuration — a `flowchart` curve, `sequence` spacing, `themeVariables`\n * over the ones read from the chat's theme. The plugin keeps the keys that make\n * model-written diagrams safe to draw: `securityLevel` is `'strict'`, HTML labels\n * are off, and a diagram's own `%%{init}%%` cannot change either.\n */\n mermaid?: Record<string, unknown>;\n}\n\n/**\n * The fonts a diagram is measured and drawn in. An `<img>` cannot load the page's web\n * fonts, so Mermaid must measure its labels in the fonts the image will actually use,\n * or every box comes out the wrong size.\n */\nconst SVG_NS = 'http://www.w3.org/2000/svg';\nconst SYSTEM_FONTS = 'system-ui, -apple-system, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif';\n/** The least time between two drawings of a diagram still arriving. */\nconst PROGRESSIVE_MS = 300;\n/**\n * What a diagram still arriving shows before it has a node to draw: a quiet box, not its\n * source scrolling by, and not the empty image Mermaid draws for a header alone.\n */\nconst PENDING = '<span class=\"aparte-skeleton aparte-skeleton--rect\" aria-hidden=\"true\"></span>';\n\n/*\n * One render at a time on the whole page, whichever chat asks: Mermaid is one object,\n * `initialize` replaces its whole configuration, and a render reads it. A queue per chat\n * let one chat's diagram be drawn with another chat's options and colours.\n */\nlet queue: Promise<unknown> = Promise.resolve();\nlet ids = 0;\n/** Unique to this copy of the plugin, so two bundles on one page never remove each other's nodes. */\nconst ID_PREFIX = `aparte-mermaid-${Math.random().toString(36).slice(2, 8)}`;\n\nconst serial = <T>(job: () => Promise<T>): Promise<T> => {\n const run = queue.then(job, job);\n queue = run.catch(() => undefined);\n return run;\n};\n\n/**\n * Draw diagrams with a Mermaid you provide — the instance, or a function that loads it\n * on the first diagram. The entry point for a bundle that controls what it ships:\n * `@aparte/plugin-mermaid` itself is this, with `() => import('mermaid')`.\n *\n * @returns a disposer that unregisters the renderer and stops watching the theme.\n * @example\n * import mermaid from 'mermaid';\n * import { setupMermaidWith } from '@aparte/plugin-mermaid/core';\n * setupMermaidWith(mermaid);\n */\nexport function setupMermaidWith(\n mermaid: MermaidLike | (() => Promise<MermaidLike>),\n options: MermaidOptions = {},\n config: AparteConfig = aparteGlobalConfig,\n): () => void {\n const load = typeof mermaid === 'function' ? mermaid : () => Promise.resolve(mermaid);\n /*\n * The shadow roots diagrams were drawn in. Neither the page's observer nor a redraw\n * from <body> reaches inside one, so each is watched, and redrawn, on its own.\n */\n const shadows = new Set<ShadowRoot>();\n const redraw = (): void => {\n renderer.epoch++;\n renderCodeLanguages(document.body, { refresh: true, language: 'mermaid' });\n for (const root of shadows) {\n // A widget taken off the page is let go, not drawn again: if it comes back,\n // its next drawing hands its root over once more.\n if (!root.host.isConnected) { shadows.delete(root); continue; }\n for (const child of Array.from(root.children)) renderCodeLanguages(child, { refresh: true, language: 'mermaid' });\n }\n };\n const watch = watchTheme(redraw);\n const renderer = createMermaidRenderer(load, options, (host) => {\n const root = host.getRootNode();\n if (typeof ShadowRoot === 'undefined' || !(root instanceof ShadowRoot) || shadows.has(root)) return;\n for (const old of shadows) if (!old.host.isConnected) shadows.delete(old);\n shadows.add(root);\n watch.add(root);\n });\n const unregister = config.registerCodeLanguageRenderer(renderer);\n return () => { watch.stop(); shadows.clear(); unregister(); };\n}\n\nfunction createMermaidRenderer(load: () => Promise<MermaidLike>, options: MermaidOptions, seen: (host: HTMLElement) => void) {\n const progressive = options.progressive !== false;\n const user = options.mermaid ?? {};\n let instance: Promise<MermaidLike> | undefined;\n /**\n * A diagram still arriving, by the part of the reply it is in — a rebuild replaces its\n * placeholder, never its part: the latest source waiting to be drawn, whether a\n * drawing is running, and the pace of the last one — the next waits as long as it\n * took, and at least `PROGRESSIVE_MS`.\n */\n interface Arriving {\n waiting: { ctx: AparteCodeLanguageContext; answer: (html: string | null) => void } | null;\n running: boolean;\n timer: ReturnType<typeof setTimeout> | undefined;\n at: number;\n took: number;\n /** The source of the diagram this part has started showing, or `null` before its first frame. */\n started: string | null;\n }\n const arriving = new WeakMap<Element, Arriving>();\n const watched = new WeakSet<HTMLImageElement>();\n\n const stateOf = (host: HTMLElement): Arriving => {\n const part = host.closest('[data-segment-id]') ?? host.closest('pre')?.parentElement ?? host.parentElement ?? host;\n let state = arriving.get(part);\n if (!state) {\n state = { waiting: null, running: false, timer: undefined, at: 0, took: 0, started: null };\n arriving.set(part, state);\n }\n return state;\n };\n\n const schedule = (state: Arriving): void => {\n if (state.running || state.timer !== undefined || !state.waiting) return;\n const wait = Math.max(0, state.at + Math.max(PROGRESSIVE_MS, state.took) - performance.now());\n state.timer = setTimeout(() => {\n state.timer = undefined;\n const job = state.waiting;\n state.waiting = null;\n if (!job) return;\n state.running = true;\n const started = performance.now();\n void draw(job.ctx).then(job.answer, () => job.answer(null)).finally(() => {\n state.running = false;\n state.at = performance.now();\n state.took = state.at - started;\n schedule(state);\n });\n }, wait);\n };\n\n const mermaidOf = (): Promise<MermaidLike> => {\n // Not poisoned: a failed load is tried again on the next diagram.\n instance ??= load().catch((error: unknown) => { instance = undefined; throw error; });\n return instance;\n };\n\n /**\n * Draw `ctx.source` as an image; `null` when it does not parse. A diagram still arriving\n * is drawn up to its last complete line: a line half written (`A[Mod`) does not parse, and\n * drawing only whole statements is what makes each node appear as its line ends.\n */\n const draw = (ctx: AparteCodeLanguageContext): Promise<string | null> => serial(async () => {\n const source = ctx.open ? ctx.source.slice(0, ctx.source.lastIndexOf('\\n') + 1) : ctx.source;\n if (!source.trim()) return null;\n const m = await mermaidOf();\n m.initialize(mermaidConfig(user, ctx.host));\n if (!(await m.parse(source, { suppressErrors: true }))) return null;\n const id = `${ID_PREFIX}-${++ids}`;\n try {\n const { svg, diagramType } = await m.render(id, source);\n return image(svg, diagramType, ctx.open);\n } finally {\n // Mermaid leaves its scratch and error nodes in <body> when a render fails.\n document.getElementById(id)?.remove();\n document.getElementById(`d${id}`)?.remove();\n }\n });\n\n const renderer = {\n languages: ['mermaid'] as const,\n lazy: true,\n epoch: 0,\n /** The colours themselves, not the theme's name: two skins may share one name. */\n cacheKey(ctx: AparteCodeLanguageContext): string {\n seen(ctx.host);\n return `${renderer.epoch}|${colourKey(ctx.host)}`;\n },\n render(ctx: AparteCodeLanguageContext): string | null | Promise<string | null> {\n if (!ctx.display) return null;\n seen(ctx.host);\n const state = stateOf(ctx.host);\n const waiting = state.waiting;\n if (!ctx.open) {\n // The end of the diagram that was arriving: its waiting frame is not drawn.\n // Another diagram of the same part, drawn again, leaves it waiting.\n if (waiting && ctx.source.startsWith(waiting.ctx.source)) {\n waiting.answer(null);\n state.waiting = null;\n state.started = null;\n }\n return draw(ctx).then(\n (html) => html ?? failed(ctx),\n () => failed(ctx),\n );\n }\n // A newer source supersedes the one still waiting.\n waiting?.answer(null);\n state.waiting = null;\n if (!progressive) return null;\n // A diagram's first frame: the box it will be drawn in, at once, in place of\n // its source. The frames after it draw what has arrived, and a frame that\n // draws nothing keeps what is shown.\n if (state.started === null || !ctx.source.startsWith(state.started)) {\n state.started = ctx.source;\n return PENDING;\n }\n return new Promise<string | null>((answer) => {\n state.waiting = { ctx, answer };\n schedule(state);\n });\n },\n relabel(drawing: HTMLElement, ctx: AparteCodeLanguageContext): void {\n const locale = ctx.config.getLocale() as Record<string, unknown>;\n const img = drawing.querySelector<HTMLImageElement>('img.aparte-diagram');\n if (img) {\n img.alt = describe(ctx.source) ?? String(locale['diagramAlt'] ?? 'Diagram');\n // A page whose CSP refuses `data:` images shows the source instead. Once per\n // image, and holding the source only: a relabel runs on every locale change.\n if (!watched.has(img)) {\n watched.add(img);\n const source = ctx.source;\n img.addEventListener('error', () => img.replaceWith(errorNode(source)), { once: true });\n }\n }\n const note = drawing.querySelector<HTMLElement>('[data-aparte-diagram-error]');\n if (note) note.textContent = String(locale['diagramError'] ?? 'This diagram could not be drawn.');\n },\n };\n return renderer;\n}\n\n/** The configuration every render runs with: the caller's, under the keys the plugin keeps. */\nfunction mermaidConfig(user: Record<string, unknown>, host: HTMLElement): Record<string, unknown> {\n const palette = paletteOf(host);\n return {\n ...user,\n startOnLoad: false,\n securityLevel: 'strict',\n htmlLabels: false,\n suppressErrorRendering: true,\n theme: 'base',\n fontFamily: SYSTEM_FONTS,\n flowchart: { ...(user['flowchart'] as Record<string, unknown> | undefined), htmlLabels: false },\n themeVariables: { ...palette, ...(user['themeVariables'] as Record<string, unknown> | undefined), fontFamily: SYSTEM_FONTS },\n maxTextSize: 50_000,\n maxEdges: 500,\n // What a diagram's own `%%{init}%%` may not change.\n secure: ['secure', 'securityLevel', 'startOnLoad', 'maxTextSize', 'maxEdges', 'suppressErrorRendering', 'htmlLabels', 'flowchart', 'themeCSS', 'fontFamily'],\n };\n}\n\n/**\n * The SVG as an image: no script and no style of it can reach the page.\n *\n * Read by the HTML parser, as Mermaid wrote it: it serialises its SVG as HTML (a no-break\n * space comes out as `&nbsp;`, and its `#hearts;` codes as `&hearts;`), which an XML\n * parser refuses — and a French label, `Étape 1 : début`, then never drew. Anything\n * the parser had to put beside the `<svg>` means it was not one clean SVG.\n */\nfunction image(svg: string, diagramType?: string, arriving = false): string | null {\n const template = document.createElement('template');\n template.innerHTML = svg.trim();\n const root = template.content.firstElementChild;\n if (!root || template.content.children.length !== 1 || root.localName !== 'svg' || root.namespaceURI !== SVG_NS\n || root.getElementsByTagName('foreignObject').length > 0) return null;\n const box = (root.getAttribute('viewBox') ?? '').split(/[\\s,]+/).map(Number);\n const width = Math.ceil(box[2] ?? 0);\n const height = Math.ceil(box[3] ?? 0);\n if (!(width > 0 && height > 0)) return null;\n // A diagram still arriving with nothing to draw yet — a header, a title — comes out as\n // an empty box: shown, it would replace what is on screen with nothing.\n if (arriving && !drawsSomething(root)) return null;\n root.setAttribute('width', String(width));\n root.setAttribute('height', String(height));\n root.removeAttribute('style');\n const src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(new XMLSerializer().serializeToString(root))}`;\n const type = diagramType ? ` data-diagram-type=\"${escapeHtml(diagramType)}\"` : '';\n return `<img class=\"aparte-diagram\" src=\"${escapeAttr(src)}\" width=\"${escapeAttr(String(width))}\" height=\"${escapeAttr(String(height))}\" alt=\"\"${type} decoding=\"async\" draggable=\"false\">`;\n}\n\n/** Whether an SVG paints anything beyond its definitions: a header alone draws no shape. */\nfunction drawsSomething(root: Element): boolean {\n for (const el of Array.from(root.querySelectorAll('path, rect, circle, ellipse, polygon, polyline, line, text, image, use'))) {\n if (!el.closest('defs, marker, symbol, clipPath, mask, pattern')) return true;\n }\n return false;\n}\n\n/** The note and the source, for a diagram that does not parse. Its text is set by `relabel`. */\nfunction failed(ctx: AparteCodeLanguageContext): string {\n return `<span class=\"aparte-code-error\" role=\"note\" data-aparte-diagram-error></span><pre class=\"aparte-code-error\">${escapeHtml(ctx.source)}</pre>`;\n}\n\nfunction errorNode(source: string): Node {\n const pre = document.createElement('pre');\n pre.className = 'aparte-code-error';\n pre.textContent = source;\n return pre;\n}\n\n/** The title or description a diagram gives itself for assistive technology, if any. */\nfunction describe(source: string): string | undefined {\n return /^\\s*accTitle\\s*:\\s*(.+)$/m.exec(source)?.[1]?.trim()\n ?? /^\\s*accDescr\\s*:\\s*(.+)$/m.exec(source)?.[1]?.trim();\n}\n\n/** Which theme the chat around `host` is in: its own attribute, else the system's. */\nfunction themeOf(host: HTMLElement): string {\n const attr = host.closest('[data-aparte-theme]')?.getAttribute('data-aparte-theme');\n if (attr) return attr;\n return typeof matchMedia === 'function' && matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';\n}\n\n/** The tokens a diagram is coloured from, with the colour each falls back to. */\nconst TOKENS = {\n bg: ['--aparte-bg', '#ffffff'],\n text: ['--aparte-text', '#1a1a1a'],\n muted: ['--aparte-text-muted', '#666666'],\n border: ['--aparte-border', '#d0d0d0'],\n surface: ['--aparte-surface-2', '#f4f4f4'],\n surfaceLow: ['--aparte-surface-1', '#fafafa'],\n primary: ['--aparte-primary', '#3b5bdb'],\n} as const;\n\n/**\n * What a diagram's colours depend on at `host`: the theme, the colour scheme, and the\n * tokens as declared there. Cheap — one style read, no probe — since the engine asks it\n * on every pass; two skins with the same theme name still differ in their tokens.\n */\nfunction colourKey(host: HTMLElement): string {\n const theme = themeOf(host);\n if (typeof getComputedStyle !== 'function') return theme;\n const style = getComputedStyle(host);\n return [theme, style.colorScheme, ...Object.values(TOKENS).map(([token]) => style.getPropertyValue(token).trim())].join('|');\n}\n\n/**\n * The chat's colours as Mermaid can read them. A token may be `color-mix()` or `oklch()`,\n * which Mermaid's colour library cannot parse, so each is resolved by the browser — a\n * probe's computed colour — and normalised to hex by a canvas.\n */\nfunction paletteOf(host: HTMLElement): Record<string, unknown> {\n if (typeof getComputedStyle !== 'function') return {};\n const probe = document.createElement('span');\n probe.style.display = 'none';\n // Beside the source, never in it: an incremental provider may still be writing there.\n (host.parentElement ?? document.body).appendChild(probe);\n const canvas = document.createElement('canvas').getContext?.('2d') ?? null;\n const read = (token: string, fallback: string): string => {\n probe.style.color = `var(${token}, ${fallback})`;\n const computed = getComputedStyle(probe).color || fallback;\n if (!canvas) return computed;\n canvas.fillStyle = '#000';\n canvas.fillStyle = computed;\n return String(canvas.fillStyle);\n };\n try {\n const bg = read(...TOKENS.bg);\n const text = read(...TOKENS.text);\n const muted = read(...TOKENS.muted);\n const border = read(...TOKENS.border);\n const surface = read(...TOKENS.surface);\n const surfaceLow = read(...TOKENS.surfaceLow);\n const primary = read(...TOKENS.primary);\n return {\n darkMode: luminance(bg) < 0.5,\n background: bg,\n primaryColor: surface,\n primaryTextColor: text,\n primaryBorderColor: primary,\n secondaryColor: surfaceLow,\n tertiaryColor: surfaceLow,\n mainBkg: surface,\n nodeBorder: border,\n clusterBkg: surfaceLow,\n clusterBorder: border,\n lineColor: muted,\n textColor: text,\n titleColor: text,\n edgeLabelBackground: bg,\n noteBkgColor: surfaceLow,\n noteTextColor: text,\n noteBorderColor: border,\n actorBkg: surface,\n actorBorder: primary,\n actorTextColor: text,\n signalColor: muted,\n signalTextColor: text,\n };\n } finally {\n probe.remove();\n }\n}\n\nfunction luminance(color: string): number {\n const hex = /^#([0-9a-f]{6})$/i.exec(color)?.[1];\n if (!hex) return 1;\n const [r, g, b] = [0, 2, 4].map((i) => parseInt(hex.slice(i, i + 2), 16) / 255);\n return 0.2126 * r! + 0.7152 * g! + 0.0722 * b!;\n}\n\n/**\n * Call `changed` when the chat's theme or the system's colour scheme changes — on the\n * page, and in each shadow root handed to `add`.\n */\nfunction watchTheme(changed: () => void): { add: (root: ShadowRoot) => void; stop: () => void } {\n if (typeof MutationObserver === 'undefined' || typeof document === 'undefined') return { add: () => {}, stop: () => {} };\n const observer = new MutationObserver(changed);\n const options: MutationObserverInit = { attributes: true, subtree: true, attributeFilter: ['data-aparte-theme'] };\n observer.observe(document.documentElement, options);\n const media = typeof matchMedia === 'function' ? matchMedia('(prefers-color-scheme: dark)') : null;\n media?.addEventListener('change', changed);\n return {\n add: (root) => observer.observe(root, options),\n stop: () => { observer.disconnect(); media?.removeEventListener('change', changed); },\n };\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import { type AparteConfig } from '@aparte/core';
2
+ import { type MermaidOptions } from './core.js';
3
+ export { setupMermaidWith } from './core.js';
4
+ export type { MermaidLike, MermaidOptions } from './core.js';
5
+ /**
6
+ * Draw diagrams with [Mermaid](https://mermaid.js.org): every ```` ```mermaid ```` fence
7
+ * in a reply, whichever Markdown provider is installed, or none. The diagram is redrawn
8
+ * as its fence arrives and keeps the last version that parsed; once the fence closes,
9
+ * one that does not parse shows a note and its source.
10
+ *
11
+ * Each diagram is an `<img>` of its SVG, so nothing in it — no script, no style a
12
+ * model wrote — can reach the page; its colours come from the chat's theme and follow
13
+ * it. Mermaid is loaded on the first diagram, and a diagram is drawn only once it
14
+ * comes near the screen, so reopening a long conversation stays fast.
15
+ *
16
+ * @returns a disposer that unregisters the renderer.
17
+ * @example
18
+ * import { setupMermaid } from '@aparte/plugin-mermaid';
19
+ * setupMermaid();
20
+ */
21
+ export declare function setupMermaid(options?: MermaidOptions, config?: AparteConfig): () => void;
22
+ //# 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,EAAsC,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AAEpF,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,YAAY,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAE7D;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,YAAY,CAAC,OAAO,GAAE,cAAmB,EAAE,MAAM,GAAE,YAAiC,GAAG,MAAM,IAAI,CAMhH"}
package/dist/index.js ADDED
@@ -0,0 +1,14 @@
1
+ import { aparteGlobalConfig } from "@aparte/core";
2
+ import { setupMermaidWith } from "./core.js";
3
+ function setupMermaid(options = {}, config = aparteGlobalConfig) {
4
+ return setupMermaidWith(
5
+ () => import("mermaid").then((m) => m.default ?? m),
6
+ options,
7
+ config
8
+ );
9
+ }
10
+ export {
11
+ setupMermaid,
12
+ setupMermaidWith
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 { setupMermaidWith, type MermaidLike, type MermaidOptions } from './core.js';\n\nexport { setupMermaidWith } from './core.js';\nexport type { MermaidLike, MermaidOptions } from './core.js';\n\n/**\n * Draw diagrams with [Mermaid](https://mermaid.js.org): every ```` ```mermaid ```` fence\n * in a reply, whichever Markdown provider is installed, or none. The diagram is redrawn\n * as its fence arrives and keeps the last version that parsed; once the fence closes,\n * one that does not parse shows a note and its source.\n *\n * Each diagram is an `<img>` of its SVG, so nothing in it — no script, no style a\n * model wrote — can reach the page; its colours come from the chat's theme and follow\n * it. Mermaid is loaded on the first diagram, and a diagram is drawn only once it\n * comes near the screen, so reopening a long conversation stays fast.\n *\n * @returns a disposer that unregisters the renderer.\n * @example\n * import { setupMermaid } from '@aparte/plugin-mermaid';\n * setupMermaid();\n */\nexport function setupMermaid(options: MermaidOptions = {}, config: AparteConfig = aparteGlobalConfig): () => void {\n return setupMermaidWith(\n () => import('mermaid').then((m) => (m.default ?? m) as unknown as MermaidLike),\n options,\n config,\n );\n}\n"],"names":[],"mappings":";;AAsBO,SAAS,aAAa,UAA0B,IAAI,SAAuB,oBAAgC;AAC9G,SAAO;AAAA,IACH,MAAM,OAAO,SAAS,EAAE,KAAK,CAAC,MAAO,EAAE,WAAW,CAA4B;AAAA,IAC9E;AAAA,IACA;AAAA,EAAA;AAER;"}
package/package.json CHANGED
@@ -1,6 +1,67 @@
1
1
  {
2
2
  "name": "@aparte/plugin-mermaid",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.28.1",
4
+ "homepage": "https://apartejs.dev/guides/diagrams/",
5
+ "description": "Diagrams for aparté via Mermaid — drawn as the fence streams, in a sandboxed image.",
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.1 <1.0.0",
35
+ "mermaid": "^11.16.1"
36
+ },
37
+ "devDependencies": {
38
+ "mermaid": "^11.17.2",
39
+ "typescript": "~5.7.0",
40
+ "vite": "^6.0.0",
41
+ "@aparte/core": "0.28.1"
42
+ },
43
+ "keywords": [
44
+ "aparte",
45
+ "plugin",
46
+ "diagram",
47
+ "mermaid",
48
+ "flowchart"
49
+ ],
50
+ "license": "MIT",
51
+ "repository": {
52
+ "type": "git",
53
+ "url": "git+https://github.com/apartejs/aparte.git",
54
+ "directory": "packages/plugins/mermaid"
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
  }