remark-dgmo 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 Diagrammo
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 ADDED
@@ -0,0 +1,205 @@
1
+ # remark-dgmo
2
+
3
+ Framework-agnostic [remark](https://github.com/remarkjs/remark) plugin that renders [DGMO](https://diagrammo.app) diagrams from `` ```dgmo `` fenced code blocks at build time. Powered by [`@diagrammo/dgmo`](https://www.npmjs.com/package/@diagrammo/dgmo). Zero client JavaScript by default.
4
+
5
+ ```dgmo
6
+ chart: sequence
7
+ Client -POST /login-> API
8
+ API -validate-> Auth
9
+ Auth -JWT-> API
10
+ API -200 OK-> Client
11
+ ```
12
+
13
+ Drop a fenced block with the language `dgmo` into any markdown or MDX file processed by a unified-style pipeline — Astro, Docusaurus, Starlight, Vitepress, eleventy-with-remark, or your own custom toolchain — and it becomes an inline `<svg>` at build time.
14
+
15
+ By default, every diagram is rendered **twice** (once with the palette's light mode, once with its dark mode) and wrapped in `<div class="dgmo-light">` / `<div class="dgmo-dark">`. A tiny shipped stylesheet hides the wrong one based on `[data-theme="dark"]` (the convention used by Docusaurus, Starlight, and most other docs frameworks). The result: your diagrams follow the host page's color-mode toggle without any client-side rendering.
16
+
17
+ ## Install
18
+
19
+ ```bash
20
+ pnpm add remark-dgmo @diagrammo/dgmo
21
+ # or
22
+ npm install remark-dgmo @diagrammo/dgmo
23
+ ```
24
+
25
+ `@diagrammo/dgmo` is a peer dependency.
26
+
27
+ ESM-only. Your config file must be `.mjs`, `.ts`, or `.mts` — or your `package.json` must have `"type": "module"`.
28
+
29
+ ## Use — three integration patterns
30
+
31
+ ### Pattern 1: Astro
32
+
33
+ Use [`astro-dgmo`](https://www.npmjs.com/package/astro-dgmo) — it wraps this plugin and handles the integration plumbing.
34
+
35
+ ```bash
36
+ pnpm add astro-dgmo @diagrammo/dgmo
37
+ ```
38
+
39
+ ```js
40
+ // astro.config.mjs
41
+ import { defineConfig } from 'astro/config';
42
+ import dgmo from 'astro-dgmo';
43
+
44
+ export default defineConfig({
45
+ integrations: [dgmo()],
46
+ });
47
+ ```
48
+
49
+ You'll also need to import the color-mode stylesheet in your global layout:
50
+
51
+ ```astro
52
+ ---
53
+ // src/layouts/Base.astro
54
+ import 'remark-dgmo/client.css';
55
+ ---
56
+ ```
57
+
58
+ ### Pattern 2: Docusaurus
59
+
60
+ Use [`docusaurus-plugin-dgmo`](https://www.npmjs.com/package/docusaurus-plugin-dgmo) — it handles `getClientModules()` registration for the CSS + client script.
61
+
62
+ ```bash
63
+ pnpm add docusaurus-plugin-dgmo @diagrammo/dgmo
64
+ ```
65
+
66
+ ```ts
67
+ // docusaurus.config.ts
68
+ import type { Config } from '@docusaurus/types';
69
+
70
+ const config: Config = {
71
+ // …
72
+ plugins: ['docusaurus-plugin-dgmo'],
73
+ presets: [
74
+ [
75
+ 'classic',
76
+ {
77
+ docs: {
78
+ remarkPlugins: [(await import('docusaurus-plugin-dgmo/remark')).default],
79
+ },
80
+ blog: {
81
+ remarkPlugins: [(await import('docusaurus-plugin-dgmo/remark')).default],
82
+ },
83
+ pages: {
84
+ remarkPlugins: [(await import('docusaurus-plugin-dgmo/remark')).default],
85
+ },
86
+ },
87
+ ],
88
+ ],
89
+ };
90
+
91
+ export default config;
92
+ ```
93
+
94
+ The plugin registers `client.css` + `client.js` via `getClientModules()`. You still wire `remarkPlugins` into each preset slot manually — Docusaurus's plugin API has no hook to auto-inject into a sibling preset.
95
+
96
+ ### Pattern 3: Vanilla unified pipeline
97
+
98
+ ```ts
99
+ import { unified } from 'unified';
100
+ import remarkParse from 'remark-parse';
101
+ import remarkRehype from 'remark-rehype';
102
+ import rehypeStringify from 'rehype-stringify';
103
+ import remarkDgmo from 'remark-dgmo';
104
+
105
+ const out = await unified()
106
+ .use(remarkParse)
107
+ .use(remarkDgmo, { mode: 'showcase', palette: 'dracula' })
108
+ .use(remarkRehype, { allowDangerousHtml: true })
109
+ .use(rehypeStringify, { allowDangerousHtml: true })
110
+ .process(source);
111
+ ```
112
+
113
+ In your output HTML's `<head>`, add the shipped stylesheet (or inline its three rules):
114
+
115
+ ```html
116
+ <link rel="stylesheet" href="/path/to/node_modules/remark-dgmo/dist/client.css" />
117
+ <script type="module" src="/path/to/node_modules/remark-dgmo/dist/client.js"></script>
118
+ ```
119
+
120
+ The client script is optional — it tightens each diagram's `viewBox` to its content bounds and wires up showcase-mode copy buttons. Without it, diagrams still render but may have extra whitespace and copy buttons won't function.
121
+
122
+ ## Options
123
+
124
+ ```js
125
+ remarkDgmo({
126
+ // Output mode for `dgmo` blocks. 'diagram' (default) = SVG only.
127
+ // 'showcase' = syntax-highlighted source + diagram + copy + open-in-editor.
128
+ mode: 'diagram',
129
+
130
+ // Default palette name (any registered @diagrammo/dgmo palette).
131
+ palette: 'nord',
132
+
133
+ // Color-mode strategy. 'auto' (default) renders both light and dark and
134
+ // toggles via CSS. 'light' or 'dark' single-renders with the matching theme.
135
+ colorMode: 'auto',
136
+
137
+ // Default theme when colorMode is 'light' or 'dark' (single-render). Ignored under 'auto'.
138
+ theme: 'dark',
139
+
140
+ // Showcase chrome — enabled automatically in showcase mode.
141
+ showSource: undefined, // boolean; default = (mode === 'showcase')
142
+ showCopy: undefined, // boolean; default = (mode === 'showcase')
143
+ showOpenInEditor: undefined, // boolean; default = (mode === 'showcase')
144
+
145
+ // Where the "Open in editor" link points.
146
+ editorBaseUrl: 'https://online.diagrammo.app',
147
+
148
+ // Outer wrapper element + class hook.
149
+ wrapper: 'figure',
150
+ className: 'dgmo',
151
+
152
+ // Append additional class names to every emitted wrapper. Used by
153
+ // astro-dgmo v0.3.0 to keep the legacy `astro-dgmo*` class names for one
154
+ // minor cycle of backward compat.
155
+ legacyClassNames: [],
156
+ });
157
+ ```
158
+
159
+ ## Per-block overrides
160
+
161
+ Append options to the fence info string. Tokens are space-separated; values may be quoted.
162
+
163
+ ````markdown
164
+ ```dgmo showcase title="Login flow" palette=catppuccin theme=light
165
+ chart: sequence
166
+ A -> B
167
+ ```
168
+ ````
169
+
170
+ | Token | Effect |
171
+ |---|---|
172
+ | `diagram` / `showcase` | Set `mode` for this block |
173
+ | `palette=<name>` | Override palette |
174
+ | `theme=light` / `theme=dark` / `theme=transparent` | Override theme (single-render only) |
175
+ | `colorMode=auto` / `colorMode=light` / `colorMode=dark` | Override color-mode strategy |
176
+ | `title="…"` | Add a caption (`<figcaption>`) |
177
+ | `source` / `noSource` | Force source listing on/off |
178
+ | `copy` / `noCopy` | Force copy button on/off |
179
+ | `openInEditor` / `noOpenInEditor` | Force editor link on/off |
180
+
181
+ ## Custom color-mode selector
182
+
183
+ The shipped `client.css` keys on `[data-theme="dark"]` — the convention used by Docusaurus and Starlight. For Tailwind-style sites that signal dark mode via a `.dark` class on `<html>` (or any other selector), don't import `client.css`. Inline these three rules in your own CSS instead, swapping the selector:
184
+
185
+ ```css
186
+ .dgmo-dark { display: none; }
187
+ html.dark .dgmo-light { display: none; }
188
+ html.dark .dgmo-dark { display: block; }
189
+ ```
190
+
191
+ For `data-color-scheme="dark"`, `:root[data-mode="dark"]`, etc. — same three rules, swap the selector to match what your toggle sets.
192
+
193
+ ## How it works
194
+
195
+ 1. The remark transformer walks the mdast, finding `code` nodes with `lang === 'dgmo'`.
196
+ 2. For each block, `renderDgmoBlock()` calls `render()` from `@diagrammo/dgmo` — twice if `colorMode: 'auto'` (one light, one dark), once otherwise.
197
+ 3. Each SVG is normalized: width/height stripped, `viewBox` added, inline background removed.
198
+ 4. The original `code` node is replaced with an `html` node carrying the rendered wrapper(s).
199
+ 5. The optional client script (`dist/client.js`) tightens viewBoxes and binds showcase-mode copy buttons.
200
+
201
+ Rendering happens at build time. The browser sees only the inline SVG and the small color-mode CSS.
202
+
203
+ ## License
204
+
205
+ MIT
@@ -0,0 +1,382 @@
1
+ // src/remark-plugin.ts
2
+ import { visit } from "unist-util-visit";
3
+
4
+ // src/render-block.ts
5
+ import {
6
+ render,
7
+ encodeDiagramUrl,
8
+ palettes
9
+ } from "@diagrammo/dgmo";
10
+ import { highlightDgmo, NORD_ROLE_STYLES } from "@diagrammo/dgmo/highlight";
11
+
12
+ // src/options.ts
13
+ function resolveOptions(opts = {}) {
14
+ const mode = opts.mode ?? "diagram";
15
+ const showcase = mode === "showcase";
16
+ return {
17
+ mode,
18
+ palette: opts.palette ?? "nord",
19
+ theme: opts.theme ?? "dark",
20
+ colorMode: opts.colorMode ?? "auto",
21
+ showSource: opts.showSource ?? showcase,
22
+ showCopy: opts.showCopy ?? showcase,
23
+ showOpenInEditor: opts.showOpenInEditor ?? showcase,
24
+ editorBaseUrl: opts.editorBaseUrl ?? "https://online.diagrammo.app",
25
+ wrapper: opts.wrapper ?? "figure",
26
+ className: opts.className ?? "dgmo",
27
+ legacyClassNames: opts.legacyClassNames ?? []
28
+ };
29
+ }
30
+
31
+ // src/fence-meta.ts
32
+ var BARE_FLAGS = {
33
+ diagram: { mode: "diagram" },
34
+ showcase: { mode: "showcase" },
35
+ noSource: { showSource: false },
36
+ source: { showSource: true },
37
+ noCopy: { showCopy: false },
38
+ copy: { showCopy: true },
39
+ noOpenInEditor: { showOpenInEditor: false },
40
+ openInEditor: { showOpenInEditor: true }
41
+ };
42
+ var VALID_THEMES = /* @__PURE__ */ new Set(["light", "dark", "transparent"]);
43
+ var VALID_COLOR_MODES = /* @__PURE__ */ new Set([
44
+ "auto",
45
+ "light",
46
+ "dark"
47
+ ]);
48
+ function parseFenceMeta(meta) {
49
+ if (!meta) return {};
50
+ const out = {};
51
+ const tokens = tokenize(meta);
52
+ for (const tok of tokens) {
53
+ if (BARE_FLAGS[tok]) {
54
+ Object.assign(out, BARE_FLAGS[tok]);
55
+ continue;
56
+ }
57
+ const eq = tok.indexOf("=");
58
+ if (eq < 0) continue;
59
+ const key = tok.slice(0, eq).trim();
60
+ const rawVal = tok.slice(eq + 1).trim();
61
+ const val = unquote(rawVal);
62
+ switch (key) {
63
+ case "palette":
64
+ if (val) out.palette = val;
65
+ break;
66
+ case "theme":
67
+ if (VALID_THEMES.has(val)) out.theme = val;
68
+ break;
69
+ case "colorMode":
70
+ if (VALID_COLOR_MODES.has(val)) {
71
+ out.colorMode = val;
72
+ }
73
+ break;
74
+ case "mode":
75
+ if (val === "diagram" || val === "showcase") out.mode = val;
76
+ break;
77
+ case "showSource":
78
+ out.showSource = parseBool(val);
79
+ break;
80
+ case "showCopy":
81
+ out.showCopy = parseBool(val);
82
+ break;
83
+ case "showOpenInEditor":
84
+ out.showOpenInEditor = parseBool(val);
85
+ break;
86
+ case "title":
87
+ if (val) out.title = val;
88
+ break;
89
+ }
90
+ }
91
+ return out;
92
+ }
93
+ function parseBool(s) {
94
+ return s === "true" || s === "1" || s === "yes";
95
+ }
96
+ function unquote(s) {
97
+ if (s.length >= 2 && s.startsWith('"') && s.endsWith('"')) {
98
+ return s.slice(1, -1);
99
+ }
100
+ return s;
101
+ }
102
+ function tokenize(input) {
103
+ const out = [];
104
+ let buf = "";
105
+ let inQuotes = false;
106
+ for (let i = 0; i < input.length; i++) {
107
+ const ch = input[i];
108
+ if (ch === '"') {
109
+ inQuotes = !inQuotes;
110
+ buf += ch;
111
+ continue;
112
+ }
113
+ if (!inQuotes && /\s/.test(ch)) {
114
+ if (buf) {
115
+ out.push(buf);
116
+ buf = "";
117
+ }
118
+ continue;
119
+ }
120
+ buf += ch;
121
+ }
122
+ if (buf) out.push(buf);
123
+ return out;
124
+ }
125
+
126
+ // src/svg-normalize.ts
127
+ function normalizeSvg(input) {
128
+ let svg = input;
129
+ const rootMatch = svg.match(/<svg[^>]*>/);
130
+ const rootTag = rootMatch?.[0] ?? "";
131
+ if (rootTag && !rootTag.includes("viewBox")) {
132
+ const wh = rootTag.match(/width="(\d+)"[^>]*height="(\d+)"/);
133
+ if (wh) {
134
+ svg = svg.replace(/<svg/, `<svg viewBox="0 0 ${wh[1]} ${wh[2]}"`);
135
+ }
136
+ }
137
+ svg = svg.replace(/(<svg[^>]*?) width="[^"]*"/g, "$1");
138
+ svg = svg.replace(/(<svg[^>]*?) height="[^"]*"/g, "$1");
139
+ svg = svg.replace(/(<svg[^>]*?style="[^"]*?)background:[^;"]*;?\s*/g, "$1");
140
+ svg = svg.replace(/<svg\s{2,}/g, "<svg ");
141
+ return svg;
142
+ }
143
+
144
+ // src/escape.ts
145
+ var HTML_ENTITIES = {
146
+ "&": "&amp;",
147
+ "<": "&lt;",
148
+ ">": "&gt;",
149
+ '"': "&quot;",
150
+ "'": "&#39;"
151
+ };
152
+ function escapeHtml(s) {
153
+ return s.replace(/[&<>"']/g, (ch) => HTML_ENTITIES[ch]);
154
+ }
155
+ function escapeAttr(s) {
156
+ return s.replace(/[&<>"']/g, (ch) => HTML_ENTITIES[ch]);
157
+ }
158
+
159
+ // src/render-block.ts
160
+ async function renderDgmoBlock(source, meta, integrationOptions = {}, location) {
161
+ const block = parseFenceMeta(meta);
162
+ const base = resolveOptions(integrationOptions);
163
+ const effectiveMode = block.mode ?? base.mode;
164
+ const showcase = effectiveMode === "showcase";
165
+ const opts = {
166
+ ...base,
167
+ mode: effectiveMode,
168
+ palette: block.palette ?? base.palette,
169
+ theme: block.theme ?? base.theme,
170
+ colorMode: block.colorMode ?? base.colorMode,
171
+ showSource: block.showSource ?? (block.mode ? showcase : base.showSource),
172
+ showCopy: block.showCopy ?? (block.mode ? showcase : base.showCopy),
173
+ showOpenInEditor: block.showOpenInEditor ?? (block.mode ? showcase : base.showOpenInEditor)
174
+ };
175
+ const trimmed = source.trim();
176
+ const palette = resolvePaletteWithWarning(opts.palette, location);
177
+ const allDiagnostics = [];
178
+ if (opts.colorMode === "auto") {
179
+ const [lightSvgRaw, darkSvgRaw] = await Promise.all([
180
+ renderForTheme(trimmed, palette, "light", opts.palette, location),
181
+ renderForTheme(trimmed, palette, "dark", opts.palette, location)
182
+ ]);
183
+ allDiagnostics.push(...lightSvgRaw.diagnostics, ...darkSvgRaw.diagnostics);
184
+ const lightSvg = normalizeSvg(lightSvgRaw.svg);
185
+ const darkSvg = normalizeSvg(darkSvgRaw.svg);
186
+ let editorUrl2;
187
+ if (opts.showOpenInEditor) {
188
+ const url = encodeDiagramUrl(trimmed, { baseUrl: opts.editorBaseUrl });
189
+ editorUrl2 = url ?? opts.editorBaseUrl;
190
+ }
191
+ const html2 = opts.mode === "showcase" ? renderShowcaseDual(trimmed, lightSvg, darkSvg, editorUrl2, opts, block.title) : renderSimpleDual(lightSvg, darkSvg, opts, block.title);
192
+ return { html: html2, diagnostics: allDiagnostics };
193
+ }
194
+ const themeForRender = opts.colorMode === "light" ? "light" : "dark";
195
+ const { svg: rawSvg, diagnostics } = await render(trimmed, {
196
+ palette,
197
+ theme: themeForRender
198
+ });
199
+ allDiagnostics.push(...diagnostics);
200
+ const svg = normalizeSvg(rawSvg);
201
+ let editorUrl;
202
+ if (opts.showOpenInEditor) {
203
+ const url = encodeDiagramUrl(trimmed, { baseUrl: opts.editorBaseUrl });
204
+ editorUrl = url ?? opts.editorBaseUrl;
205
+ }
206
+ const html = opts.mode === "showcase" ? renderShowcase(trimmed, svg, editorUrl, opts, block.title) : renderSimple(svg, opts, block.title);
207
+ return { html, diagnostics: allDiagnostics };
208
+ }
209
+ function locationSuffix(location) {
210
+ if (!location) return "";
211
+ if (location.path && location.line) return ` at ${location.path}:${location.line}`;
212
+ if (location.line) return ` at line ${location.line}`;
213
+ return "";
214
+ }
215
+ function resolvePaletteWithWarning(name, location) {
216
+ const found = Object.values(palettes).find((p) => p.id === name);
217
+ if (!found) {
218
+ console.warn(
219
+ `[remark-dgmo] palette "${name}" not registered, falling back to "nord"${locationSuffix(location)}`
220
+ );
221
+ return palettes.nord;
222
+ }
223
+ return found;
224
+ }
225
+ async function renderForTheme(source, palette, theme, requestedName, location) {
226
+ if (!palette[theme]) {
227
+ console.warn(
228
+ `[remark-dgmo] palette "${requestedName}" has no ${theme} mode; using nord ${theme} for the missing pair${locationSuffix(location)}`
229
+ );
230
+ const filled = {
231
+ ...palette,
232
+ [theme]: palettes.nord[theme]
233
+ };
234
+ return render(source, { palette: filled, theme });
235
+ }
236
+ return render(source, { palette, theme });
237
+ }
238
+ function buildWrapperClasses(resolved, variant) {
239
+ const base = `${resolved.className} ${resolved.className}--${variant}`;
240
+ const legacy = resolved.legacyClassNames.join(" ");
241
+ return legacy ? `${base} ${legacy}` : base;
242
+ }
243
+ function buildInnerClasses(resolved, primary) {
244
+ const legacy = resolved.legacyClassNames.join(" ");
245
+ return legacy ? `${primary} ${legacy}` : primary;
246
+ }
247
+ function renderSimple(svg, opts, title) {
248
+ const Wrapper = opts.wrapper;
249
+ const wrapperClass = buildWrapperClasses(opts, "diagram");
250
+ const captionHtml = title ? `<figcaption class="dgmo-caption">${escapeHtml(title)}</figcaption>` : "";
251
+ const captionFallback = title && Wrapper !== "figure" ? `<div class="dgmo-caption">${escapeHtml(title)}</div>` : "";
252
+ return `<${Wrapper} class="${escapeAttr(wrapperClass)}">` + (Wrapper === "figure" ? captionHtml : captionFallback) + `<div class="${escapeAttr(buildInnerClasses(opts, "dgmo-svg"))}">${svg}</div></${Wrapper}>`;
253
+ }
254
+ function renderSimpleDual(lightSvg, darkSvg, opts, title) {
255
+ const Wrapper = opts.wrapper;
256
+ const wrapperClass = buildWrapperClasses(opts, "diagram");
257
+ const captionHtml = title ? `<figcaption class="dgmo-caption">${escapeHtml(title)}</figcaption>` : "";
258
+ const captionFallback = title && Wrapper !== "figure" ? `<div class="dgmo-caption">${escapeHtml(title)}</div>` : "";
259
+ return `<${Wrapper} class="${escapeAttr(wrapperClass)}">` + (Wrapper === "figure" ? captionHtml : captionFallback) + `<div class="${escapeAttr(buildInnerClasses(opts, "dgmo-light"))}">${lightSvg}</div><div class="${escapeAttr(buildInnerClasses(opts, "dgmo-dark"))}">${darkSvg}</div></${Wrapper}>`;
260
+ }
261
+ function renderShowcase(source, svg, editorUrl, opts, title) {
262
+ const Wrapper = opts.wrapper;
263
+ const wrapperClass = buildWrapperClasses(opts, "showcase");
264
+ const cardClass = buildInnerClasses(opts, "dgmo-card");
265
+ const captionHtml = title ? Wrapper === "figure" ? `<figcaption class="dgmo-caption">${escapeHtml(title)}</figcaption>` : `<div class="dgmo-caption">${escapeHtml(title)}</div>` : "";
266
+ const sourceHtml = opts.showSource ? renderSource(source) : "";
267
+ const openButton = opts.showOpenInEditor && editorUrl ? `<a href="${escapeAttr(editorUrl)}" target="_blank" rel="noopener noreferrer" class="dgmo-toolbar-btn dgmo-open" aria-label="Open in online editor" title="Open in online editor">
268
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
269
+ <path d="M9.5 2.5h4v4"/>
270
+ <path d="M13.5 2.5 7 9"/>
271
+ <path d="M12.5 9.5v3a1 1 0 0 1-1 1h-8a1 1 0 0 1-1-1v-8a1 1 0 0 1 1-1h3"/>
272
+ </svg>
273
+ </a>` : "";
274
+ const copyButton = opts.showCopy ? `<button type="button" class="dgmo-toolbar-btn dgmo-copy" aria-label="Copy to clipboard" title="Copy to clipboard" data-dgmo-source="${escapeAttr(source)}">
275
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
276
+ <rect x="5.5" y="5.5" width="8" height="8" rx="1.5"/>
277
+ <path d="M10.5 5.5V3a1.5 1.5 0 0 0-1.5-1.5H3A1.5 1.5 0 0 0 1.5 3v6A1.5 1.5 0 0 0 3 10.5h2.5"/>
278
+ </svg>
279
+ </button>` : "";
280
+ const toolbarActions = openButton || copyButton ? `<div class="dgmo-toolbar-actions">${openButton}${copyButton}</div>` : "";
281
+ const toolbar = opts.showSource ? `<div class="dgmo-toolbar"><span class="dgmo-toolbar-label">dgmo</span>${toolbarActions}</div>` : "";
282
+ return `<${Wrapper} class="${escapeAttr(wrapperClass)}">` + captionHtml + `<div class="${escapeAttr(cardClass)}">` + (opts.showSource ? `<div class="dgmo-source-wrap">${toolbar}<div class="dgmo-source-inner">${sourceHtml}</div></div>` : "") + `<div class="${escapeAttr(buildInnerClasses(opts, "dgmo-svg"))}">${svg}</div></div></${Wrapper}>`;
283
+ }
284
+ function renderShowcaseDual(source, lightSvg, darkSvg, editorUrl, opts, title) {
285
+ const Wrapper = opts.wrapper;
286
+ const wrapperClass = buildWrapperClasses(opts, "showcase");
287
+ const cardClass = buildInnerClasses(opts, "dgmo-card");
288
+ const captionHtml = title ? Wrapper === "figure" ? `<figcaption class="dgmo-caption">${escapeHtml(title)}</figcaption>` : `<div class="dgmo-caption">${escapeHtml(title)}</div>` : "";
289
+ const sourceHtml = opts.showSource ? renderSource(source) : "";
290
+ const openButton = opts.showOpenInEditor && editorUrl ? `<a href="${escapeAttr(editorUrl)}" target="_blank" rel="noopener noreferrer" class="dgmo-toolbar-btn dgmo-open" aria-label="Open in online editor" title="Open in online editor">
291
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
292
+ <path d="M9.5 2.5h4v4"/>
293
+ <path d="M13.5 2.5 7 9"/>
294
+ <path d="M12.5 9.5v3a1 1 0 0 1-1 1h-8a1 1 0 0 1-1-1v-8a1 1 0 0 1 1-1h3"/>
295
+ </svg>
296
+ </a>` : "";
297
+ const copyButton = opts.showCopy ? `<button type="button" class="dgmo-toolbar-btn dgmo-copy" aria-label="Copy to clipboard" title="Copy to clipboard" data-dgmo-source="${escapeAttr(source)}">
298
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
299
+ <rect x="5.5" y="5.5" width="8" height="8" rx="1.5"/>
300
+ <path d="M10.5 5.5V3a1.5 1.5 0 0 0-1.5-1.5H3A1.5 1.5 0 0 0 1.5 3v6A1.5 1.5 0 0 0 3 10.5h2.5"/>
301
+ </svg>
302
+ </button>` : "";
303
+ const toolbarActions = openButton || copyButton ? `<div class="dgmo-toolbar-actions">${openButton}${copyButton}</div>` : "";
304
+ const toolbar = opts.showSource ? `<div class="dgmo-toolbar"><span class="dgmo-toolbar-label">dgmo</span>${toolbarActions}</div>` : "";
305
+ return `<${Wrapper} class="${escapeAttr(wrapperClass)}">` + captionHtml + `<div class="${escapeAttr(cardClass)}">` + (opts.showSource ? `<div class="dgmo-source-wrap">${toolbar}<div class="dgmo-source-inner">${sourceHtml}</div></div>` : "") + `<div class="${escapeAttr(buildInnerClasses(opts, "dgmo-light"))}">${lightSvg}</div><div class="${escapeAttr(buildInnerClasses(opts, "dgmo-dark"))}">${darkSvg}</div></div></${Wrapper}>`;
306
+ }
307
+ function renderSource(source) {
308
+ const tokens = highlightDgmo(source);
309
+ const inner = tokens.map((t) => {
310
+ const styles = NORD_ROLE_STYLES[t.role];
311
+ const text = escapeHtml(t.text);
312
+ if (!styles || Object.keys(styles).length === 0) return text;
313
+ const styleStr = Object.entries(styles).map(
314
+ ([k, v]) => `${k.replace(/[A-Z]/g, (m) => "-" + m.toLowerCase())}:${v}`
315
+ ).join(";");
316
+ return `<span style="${escapeAttr(styleStr)}">${text}</span>`;
317
+ }).join("");
318
+ return `<pre class="dgmo-pre"><span class="dgmo-code">${inner}</span></pre>`;
319
+ }
320
+
321
+ // src/remark-plugin.ts
322
+ function remarkDgmo(options = {}) {
323
+ return async function transformer(tree, file) {
324
+ const targets = [];
325
+ visit(tree, "code", (node, index, parent) => {
326
+ if (node.lang !== "dgmo") return;
327
+ if (!parent || index === void 0) return;
328
+ const loc = {};
329
+ if (file?.path) loc.path = file.path;
330
+ const line = node.position?.start.line;
331
+ if (typeof line === "number") loc.line = line;
332
+ targets.push({
333
+ parent,
334
+ index,
335
+ payload: { source: node.value, meta: node.meta ?? null, location: loc }
336
+ });
337
+ });
338
+ if (targets.length === 0) return;
339
+ const rendered = await Promise.all(
340
+ targets.map(
341
+ (t) => renderDgmoBlock(
342
+ t.payload.source,
343
+ t.payload.meta,
344
+ options,
345
+ t.payload.location
346
+ ).catch((err) => ({
347
+ html: errorHtml(err, t.payload.source, options),
348
+ diagnostics: []
349
+ }))
350
+ )
351
+ );
352
+ for (let i = targets.length - 1; i >= 0; i--) {
353
+ const t = targets[i];
354
+ const html = { type: "html", value: rendered[i].html };
355
+ t.parent.children[t.index] = html;
356
+ }
357
+ };
358
+ }
359
+ function errorHtml(err, source, options) {
360
+ const msg = err instanceof Error ? err.message : "Failed to render dgmo block.";
361
+ const safeMsg = msg.replace(
362
+ /[<>&]/g,
363
+ (ch) => ch === "<" ? "&lt;" : ch === ">" ? "&gt;" : "&amp;"
364
+ );
365
+ const safeSrc = source.replace(
366
+ /[<>&]/g,
367
+ (ch) => ch === "<" ? "&lt;" : ch === ">" ? "&gt;" : "&amp;"
368
+ );
369
+ const baseClass = options.className ?? "dgmo";
370
+ const legacy = (options.legacyClassNames ?? []).join(" ");
371
+ const cls = legacy ? `${baseClass} ${legacy} ${baseClass}--error` : `${baseClass} ${baseClass}--error`;
372
+ return `<div class="${cls}" role="alert"><strong>dgmo render error:</strong> ${safeMsg}<pre>${safeSrc}</pre></div>`;
373
+ }
374
+
375
+ export {
376
+ resolveOptions,
377
+ parseFenceMeta,
378
+ normalizeSvg,
379
+ renderDgmoBlock,
380
+ remarkDgmo
381
+ };
382
+ //# sourceMappingURL=chunk-7VVNWBXS.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/remark-plugin.ts","../src/render-block.ts","../src/options.ts","../src/fence-meta.ts","../src/svg-normalize.ts","../src/escape.ts"],"sourcesContent":["import { visit } from 'unist-util-visit';\nimport type { Root, Code, Html, Parent } from 'mdast';\nimport { renderDgmoBlock, type BlockLocation } from './render-block.js';\nimport type { DgmoOptions } from './options.js';\n\nexport type RemarkDgmoOptions = DgmoOptions;\n\ninterface FencePayload {\n source: string;\n meta: string | null;\n location: BlockLocation;\n}\n\ninterface Target {\n parent: Parent;\n index: number;\n payload: FencePayload;\n}\n\n/**\n * Remark plugin that finds ```dgmo fenced code blocks and replaces them with\n * an HTML node containing the rendered SVG (and optional showcase chrome).\n *\n * The `lang` field on the code node is the fence language (the word after the\n * backticks). The `meta` field is everything that follows on the same line,\n * which we use to allow per-block options like ```dgmo showcase palette=catppuccin.\n *\n * Replaces the code node entirely (parent.children[index] = newNode) rather\n * than mutating it in place — otherwise downstream rehype/Shiki plugins still\n * see the lingering `lang: 'dgmo'` and `value: '...source...'` properties and\n * may re-process the block as a plaintext code listing, clobbering our\n * syntax-highlighted output.\n *\n * Async-safe: replacement is collected first, applied after parsing finishes.\n */\nexport default function remarkDgmo(options: RemarkDgmoOptions = {}) {\n return async function transformer(\n tree: Root,\n file?: { path?: string }\n ): Promise<void> {\n const targets: Target[] = [];\n visit(tree, 'code', (node: Code, index, parent) => {\n if (node.lang !== 'dgmo') return;\n if (!parent || index === undefined) return;\n const loc: BlockLocation = {};\n if (file?.path) loc.path = file.path;\n const line = node.position?.start.line;\n if (typeof line === 'number') loc.line = line;\n targets.push({\n parent: parent as Parent,\n index,\n payload: { source: node.value, meta: node.meta ?? null, location: loc },\n });\n });\n if (targets.length === 0) return;\n\n const rendered = await Promise.all(\n targets.map(t =>\n renderDgmoBlock(\n t.payload.source,\n t.payload.meta,\n options,\n t.payload.location\n ).catch(err => ({\n html: errorHtml(err, t.payload.source, options),\n diagnostics: [],\n }))\n )\n );\n\n // Replace in reverse index order per parent so earlier replacements don't\n // shift indices of later targets in the same parent. (Visit walks in tree\n // order, so within a single parent's children targets are also ordered;\n // reversing is sufficient.)\n for (let i = targets.length - 1; i >= 0; i--) {\n const t = targets[i];\n const html: Html = { type: 'html', value: rendered[i].html };\n t.parent.children[t.index] = html;\n }\n };\n}\n\nfunction errorHtml(\n err: unknown,\n source: string,\n options: RemarkDgmoOptions\n): string {\n const msg =\n err instanceof Error ? err.message : 'Failed to render dgmo block.';\n const safeMsg = msg.replace(/[<>&]/g, ch =>\n ch === '<' ? '&lt;' : ch === '>' ? '&gt;' : '&amp;'\n );\n const safeSrc = source.replace(/[<>&]/g, ch =>\n ch === '<' ? '&lt;' : ch === '>' ? '&gt;' : '&amp;'\n );\n const baseClass = options.className ?? 'dgmo';\n const legacy = (options.legacyClassNames ?? []).join(' ');\n const cls = legacy\n ? `${baseClass} ${legacy} ${baseClass}--error`\n : `${baseClass} ${baseClass}--error`;\n return (\n `<div class=\"${cls}\" role=\"alert\">` +\n `<strong>dgmo render error:</strong> ${safeMsg}` +\n `<pre>${safeSrc}</pre></div>`\n );\n}\n","import {\n render,\n encodeDiagramUrl,\n palettes,\n type PaletteConfig,\n} from '@diagrammo/dgmo';\nimport { highlightDgmo, NORD_ROLE_STYLES } from '@diagrammo/dgmo/highlight';\nimport {\n resolveOptions,\n type DgmoOptions,\n type ResolvedOptions,\n type Theme,\n} from './options.js';\nimport { parseFenceMeta } from './fence-meta.js';\nimport { normalizeSvg } from './svg-normalize.js';\nimport { escapeHtml, escapeAttr } from './escape.js';\n\nexport interface RenderBlockResult {\n html: string;\n diagnostics: Array<{ message: string; line?: number; severity?: string }>;\n}\n\n/**\n * Optional source-location hint, passed through from the remark transformer so\n * palette-fallback warnings can point at the offending block.\n */\nexport interface BlockLocation {\n path?: string;\n line?: number;\n}\n\n/**\n * Render a single ```dgmo block to inline HTML. Pure function: takes source +\n * options and returns the HTML string and any diagnostics from the parser.\n *\n * The remark plugin calls this for every matched code node.\n */\nexport async function renderDgmoBlock(\n source: string,\n meta: string | null | undefined,\n integrationOptions: DgmoOptions = {},\n location?: BlockLocation\n): Promise<RenderBlockResult> {\n const block = parseFenceMeta(meta);\n const base = resolveOptions(integrationOptions);\n const effectiveMode = block.mode ?? base.mode;\n const showcase = effectiveMode === 'showcase';\n const opts: ResolvedOptions = {\n ...base,\n mode: effectiveMode,\n palette: block.palette ?? base.palette,\n theme: block.theme ?? base.theme,\n colorMode: block.colorMode ?? base.colorMode,\n showSource:\n block.showSource ?? (block.mode ? showcase : base.showSource),\n showCopy: block.showCopy ?? (block.mode ? showcase : base.showCopy),\n showOpenInEditor:\n block.showOpenInEditor ?? (block.mode ? showcase : base.showOpenInEditor),\n };\n\n const trimmed = source.trim();\n const palette = resolvePaletteWithWarning(opts.palette, location);\n\n // collect diagnostics from however many render passes we end up doing\n const allDiagnostics: RenderBlockResult['diagnostics'] = [];\n\n if (opts.colorMode === 'auto') {\n const [lightSvgRaw, darkSvgRaw] = await Promise.all([\n renderForTheme(trimmed, palette, 'light', opts.palette, location),\n renderForTheme(trimmed, palette, 'dark', opts.palette, location),\n ]);\n allDiagnostics.push(...lightSvgRaw.diagnostics, ...darkSvgRaw.diagnostics);\n\n const lightSvg = normalizeSvg(lightSvgRaw.svg);\n const darkSvg = normalizeSvg(darkSvgRaw.svg);\n\n let editorUrl: string | undefined;\n if (opts.showOpenInEditor) {\n const url = encodeDiagramUrl(trimmed, { baseUrl: opts.editorBaseUrl });\n editorUrl = url ?? opts.editorBaseUrl;\n }\n\n const html =\n opts.mode === 'showcase'\n ? renderShowcaseDual(trimmed, lightSvg, darkSvg, editorUrl, opts, block.title)\n : renderSimpleDual(lightSvg, darkSvg, opts, block.title);\n\n return { html, diagnostics: allDiagnostics };\n }\n\n // Single-render path. colorMode is narrowed to 'light' | 'dark' here since\n // 'auto' is handled above. The dgmo render() also accepts 'transparent', but\n // we don't surface that via colorMode — it's reachable via `theme`.\n const themeForRender: Theme = opts.colorMode === 'light' ? 'light' : 'dark';\n const { svg: rawSvg, diagnostics } = await render(trimmed, {\n palette,\n theme: themeForRender,\n });\n allDiagnostics.push(...diagnostics);\n const svg = normalizeSvg(rawSvg);\n\n let editorUrl: string | undefined;\n if (opts.showOpenInEditor) {\n const url = encodeDiagramUrl(trimmed, { baseUrl: opts.editorBaseUrl });\n editorUrl = url ?? opts.editorBaseUrl;\n }\n\n const html =\n opts.mode === 'showcase'\n ? renderShowcase(trimmed, svg, editorUrl, opts, block.title)\n : renderSimple(svg, opts, block.title);\n\n return { html, diagnostics: allDiagnostics };\n}\n\nfunction locationSuffix(location: BlockLocation | undefined): string {\n if (!location) return '';\n if (location.path && location.line) return ` at ${location.path}:${location.line}`;\n if (location.line) return ` at line ${location.line}`;\n return '';\n}\n\nfunction resolvePaletteWithWarning(\n name: string,\n location: BlockLocation | undefined\n): PaletteConfig {\n const found = Object.values(palettes).find(p => p.id === name);\n if (!found) {\n // eslint-disable-next-line no-console\n console.warn(\n `[remark-dgmo] palette \"${name}\" not registered, falling back to \"nord\"${locationSuffix(location)}`\n );\n return palettes.nord;\n }\n return found;\n}\n\n/**\n * Render one theme. If `colorMode: 'auto'` is requested but the palette is\n * missing the requested mode, fall back to nord's mode and emit a warning.\n *\n * Today this is defensive: dgmo's palette registry validates both modes at\n * registration time, so every registered palette has both pairs by\n * construction. User-supplied palettes via `registerPalette()` that slip past\n * validation would hit this path; tests exercise it via mocking.\n */\nasync function renderForTheme(\n source: string,\n palette: PaletteConfig,\n theme: 'light' | 'dark',\n requestedName: string,\n location: BlockLocation | undefined\n): Promise<{ svg: string; diagnostics: RenderBlockResult['diagnostics'] }> {\n if (!palette[theme]) {\n // eslint-disable-next-line no-console\n console.warn(\n `[remark-dgmo] palette \"${requestedName}\" has no ${theme} mode; using nord ${theme} for the missing pair${locationSuffix(location)}`\n );\n // Build a synthetic palette where the missing mode is borrowed from nord.\n const filled: PaletteConfig = {\n ...palette,\n [theme]: palettes.nord[theme],\n } as PaletteConfig;\n return render(source, { palette: filled, theme });\n }\n return render(source, { palette, theme });\n}\n\nfunction buildWrapperClasses(\n resolved: Pick<ResolvedOptions, 'className' | 'legacyClassNames'>,\n variant: 'diagram' | 'showcase' | 'error'\n): string {\n const base = `${resolved.className} ${resolved.className}--${variant}`;\n const legacy = resolved.legacyClassNames.join(' ');\n return legacy ? `${base} ${legacy}` : base;\n}\n\nfunction buildInnerClasses(\n resolved: Pick<ResolvedOptions, 'legacyClassNames'>,\n primary: string\n): string {\n const legacy = resolved.legacyClassNames.join(' ');\n return legacy ? `${primary} ${legacy}` : primary;\n}\n\nfunction renderSimple(\n svg: string,\n opts: ResolvedOptions,\n title?: string\n): string {\n const Wrapper = opts.wrapper;\n const wrapperClass = buildWrapperClasses(opts, 'diagram');\n const captionHtml = title\n ? `<figcaption class=\"dgmo-caption\">${escapeHtml(title)}</figcaption>`\n : '';\n const captionFallback =\n title && Wrapper !== 'figure'\n ? `<div class=\"dgmo-caption\">${escapeHtml(title)}</div>`\n : '';\n return (\n `<${Wrapper} class=\"${escapeAttr(wrapperClass)}\">` +\n (Wrapper === 'figure' ? captionHtml : captionFallback) +\n `<div class=\"${escapeAttr(buildInnerClasses(opts, 'dgmo-svg'))}\">${svg}</div>` +\n `</${Wrapper}>`\n );\n}\n\nfunction renderSimpleDual(\n lightSvg: string,\n darkSvg: string,\n opts: ResolvedOptions,\n title?: string\n): string {\n const Wrapper = opts.wrapper;\n const wrapperClass = buildWrapperClasses(opts, 'diagram');\n const captionHtml = title\n ? `<figcaption class=\"dgmo-caption\">${escapeHtml(title)}</figcaption>`\n : '';\n const captionFallback =\n title && Wrapper !== 'figure'\n ? `<div class=\"dgmo-caption\">${escapeHtml(title)}</div>`\n : '';\n return (\n `<${Wrapper} class=\"${escapeAttr(wrapperClass)}\">` +\n (Wrapper === 'figure' ? captionHtml : captionFallback) +\n `<div class=\"${escapeAttr(buildInnerClasses(opts, 'dgmo-light'))}\">${lightSvg}</div>` +\n `<div class=\"${escapeAttr(buildInnerClasses(opts, 'dgmo-dark'))}\">${darkSvg}</div>` +\n `</${Wrapper}>`\n );\n}\n\nfunction renderShowcase(\n source: string,\n svg: string,\n editorUrl: string | undefined,\n opts: ResolvedOptions,\n title?: string\n): string {\n const Wrapper = opts.wrapper;\n const wrapperClass = buildWrapperClasses(opts, 'showcase');\n const cardClass = buildInnerClasses(opts, 'dgmo-card');\n\n const captionHtml = title\n ? Wrapper === 'figure'\n ? `<figcaption class=\"dgmo-caption\">${escapeHtml(title)}</figcaption>`\n : `<div class=\"dgmo-caption\">${escapeHtml(title)}</div>`\n : '';\n\n const sourceHtml = opts.showSource ? renderSource(source) : '';\n\n const openButton =\n opts.showOpenInEditor && editorUrl\n ? `<a href=\"${escapeAttr(editorUrl)}\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"dgmo-toolbar-btn dgmo-open\" aria-label=\"Open in online editor\" title=\"Open in online editor\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M9.5 2.5h4v4\"/>\n <path d=\"M13.5 2.5 7 9\"/>\n <path d=\"M12.5 9.5v3a1 1 0 0 1-1 1h-8a1 1 0 0 1-1-1v-8a1 1 0 0 1 1-1h3\"/>\n </svg>\n </a>`\n : '';\n\n const copyButton = opts.showCopy\n ? `<button type=\"button\" class=\"dgmo-toolbar-btn dgmo-copy\" aria-label=\"Copy to clipboard\" title=\"Copy to clipboard\" data-dgmo-source=\"${escapeAttr(source)}\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <rect x=\"5.5\" y=\"5.5\" width=\"8\" height=\"8\" rx=\"1.5\"/>\n <path d=\"M10.5 5.5V3a1.5 1.5 0 0 0-1.5-1.5H3A1.5 1.5 0 0 0 1.5 3v6A1.5 1.5 0 0 0 3 10.5h2.5\"/>\n </svg>\n </button>`\n : '';\n\n const toolbarActions =\n openButton || copyButton\n ? `<div class=\"dgmo-toolbar-actions\">${openButton}${copyButton}</div>`\n : '';\n\n const toolbar = opts.showSource\n ? `<div class=\"dgmo-toolbar\"><span class=\"dgmo-toolbar-label\">dgmo</span>${toolbarActions}</div>`\n : '';\n\n return (\n `<${Wrapper} class=\"${escapeAttr(wrapperClass)}\">` +\n captionHtml +\n `<div class=\"${escapeAttr(cardClass)}\">` +\n (opts.showSource\n ? `<div class=\"dgmo-source-wrap\">${toolbar}<div class=\"dgmo-source-inner\">${sourceHtml}</div></div>`\n : '') +\n `<div class=\"${escapeAttr(buildInnerClasses(opts, 'dgmo-svg'))}\">${svg}</div>` +\n `</div>` +\n `</${Wrapper}>`\n );\n}\n\nfunction renderShowcaseDual(\n source: string,\n lightSvg: string,\n darkSvg: string,\n editorUrl: string | undefined,\n opts: ResolvedOptions,\n title?: string\n): string {\n const Wrapper = opts.wrapper;\n const wrapperClass = buildWrapperClasses(opts, 'showcase');\n const cardClass = buildInnerClasses(opts, 'dgmo-card');\n\n const captionHtml = title\n ? Wrapper === 'figure'\n ? `<figcaption class=\"dgmo-caption\">${escapeHtml(title)}</figcaption>`\n : `<div class=\"dgmo-caption\">${escapeHtml(title)}</div>`\n : '';\n\n const sourceHtml = opts.showSource ? renderSource(source) : '';\n\n const openButton =\n opts.showOpenInEditor && editorUrl\n ? `<a href=\"${escapeAttr(editorUrl)}\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"dgmo-toolbar-btn dgmo-open\" aria-label=\"Open in online editor\" title=\"Open in online editor\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M9.5 2.5h4v4\"/>\n <path d=\"M13.5 2.5 7 9\"/>\n <path d=\"M12.5 9.5v3a1 1 0 0 1-1 1h-8a1 1 0 0 1-1-1v-8a1 1 0 0 1 1-1h3\"/>\n </svg>\n </a>`\n : '';\n\n const copyButton = opts.showCopy\n ? `<button type=\"button\" class=\"dgmo-toolbar-btn dgmo-copy\" aria-label=\"Copy to clipboard\" title=\"Copy to clipboard\" data-dgmo-source=\"${escapeAttr(source)}\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <rect x=\"5.5\" y=\"5.5\" width=\"8\" height=\"8\" rx=\"1.5\"/>\n <path d=\"M10.5 5.5V3a1.5 1.5 0 0 0-1.5-1.5H3A1.5 1.5 0 0 0 1.5 3v6A1.5 1.5 0 0 0 3 10.5h2.5\"/>\n </svg>\n </button>`\n : '';\n\n const toolbarActions =\n openButton || copyButton\n ? `<div class=\"dgmo-toolbar-actions\">${openButton}${copyButton}</div>`\n : '';\n\n const toolbar = opts.showSource\n ? `<div class=\"dgmo-toolbar\"><span class=\"dgmo-toolbar-label\">dgmo</span>${toolbarActions}</div>`\n : '';\n\n return (\n `<${Wrapper} class=\"${escapeAttr(wrapperClass)}\">` +\n captionHtml +\n `<div class=\"${escapeAttr(cardClass)}\">` +\n (opts.showSource\n ? `<div class=\"dgmo-source-wrap\">${toolbar}<div class=\"dgmo-source-inner\">${sourceHtml}</div></div>`\n : '') +\n `<div class=\"${escapeAttr(buildInnerClasses(opts, 'dgmo-light'))}\">${lightSvg}</div>` +\n `<div class=\"${escapeAttr(buildInnerClasses(opts, 'dgmo-dark'))}\">${darkSvg}</div>` +\n `</div>` +\n `</${Wrapper}>`\n );\n}\n\nfunction renderSource(source: string): string {\n const tokens = highlightDgmo(source);\n const inner = tokens\n .map(t => {\n const styles = NORD_ROLE_STYLES[t.role];\n const text = escapeHtml(t.text);\n if (!styles || Object.keys(styles).length === 0) return text;\n const styleStr = Object.entries(styles)\n .map(\n ([k, v]) => `${k.replace(/[A-Z]/g, m => '-' + m.toLowerCase())}:${v}`\n )\n .join(';');\n return `<span style=\"${escapeAttr(styleStr)}\">${text}</span>`;\n })\n .join('');\n // NOTE: We deliberately use <pre><span> rather than <pre><code> here. Astro's\n // Shiki rehype plugin (and Docusaurus's MDX pipeline) walk the hast and\n // post-process any <pre><code> pair (even ones we emit from a remark plugin\n // via raw HTML), which clobbers our pre-rendered highlight spans with a\n // plaintext listing. Using a <span> as the inner element bypasses the\n // matcher while preserving preformatted-text semantics on the outer <pre>.\n return `<pre class=\"dgmo-pre\"><span class=\"dgmo-code\">${inner}</span></pre>`;\n}\n","export type Mode = 'diagram' | 'showcase';\n\nexport type Theme = 'light' | 'dark' | 'transparent';\n\n/**\n * Framework-agnostic options for `remarkDgmo` / `renderDgmoBlock`.\n *\n * Wrapper packages (`astro-dgmo`, `docusaurus-plugin-dgmo`) re-export this\n * shape under their own conventional names (`DgmoIntegrationOptions`,\n * `DocusaurusDgmoOptions`).\n */\nexport interface DgmoOptions {\n /**\n * Output mode for `dgmo` fenced blocks.\n * - `diagram` (default): render the SVG only, in a `<figure>`.\n * - `showcase`: render syntax-highlighted source + SVG + copy + open-in-editor.\n *\n * Override per-block via the fence info string: ```dgmo showcase\n */\n mode?: Mode;\n\n /** Default palette name. Default: `nord`. */\n palette?: string;\n\n /**\n * Default theme (`light` | `dark` | `transparent`). Default: `dark`.\n *\n * NOTE: under the default `colorMode: 'auto'`, this option is unreachable —\n * dual-render emits both light and dark SVGs regardless. `theme` is consulted\n * only when `colorMode` is explicitly set to `'light'` or `'dark'`.\n */\n theme?: Theme;\n\n /**\n * Color-mode strategy for emitted SVG(s). Default: `auto`.\n *\n * - `auto` — render every block twice (light + dark palettes) and wrap each\n * SVG in a `<div class=\"dgmo-light\">` / `<div class=\"dgmo-dark\">` so the\n * shipped CSS can flip visibility based on the host site's color-mode\n * signal (`[data-theme=\"dark\"]` by default).\n * - `light` / `dark` — single-render with the matching theme. Halves the\n * emitted SVG bytes; recommended only for single-mode sites.\n */\n colorMode?: 'auto' | 'light' | 'dark';\n\n /**\n * Show source code above the diagram. Defaults to `true` in showcase mode,\n * `false` in diagram mode.\n */\n showSource?: boolean;\n\n /**\n * Show a copy-to-clipboard button. Defaults to `true` in showcase mode,\n * `false` in diagram mode.\n */\n showCopy?: boolean;\n\n /**\n * Show an \"Open in online editor\" link. Defaults to `true` in showcase mode,\n * `false` in diagram mode.\n */\n showOpenInEditor?: boolean;\n\n /**\n * Base URL for the \"Open in editor\" link. Default: `https://online.diagrammo.app`.\n * The plugin appends `?dgmo=...` (compressed source) to the base.\n */\n editorBaseUrl?: string;\n\n /**\n * Wrapper element. Default: `figure`.\n */\n wrapper?: 'figure' | 'div';\n\n /**\n * Class added to the outer wrapper. Defaults to `dgmo`.\n * Useful as a styling hook.\n */\n className?: string;\n\n /**\n * Additional class names appended to every emitted wrapper's `class`\n * attribute. Used by `astro-dgmo` v0.3.0 to emit both the new `dgmo-*` class\n * names and the legacy `astro-dgmo-*` ones for one minor cycle of backward\n * compatibility. Default: `[]`.\n */\n legacyClassNames?: string[];\n}\n\nexport type ResolvedOptions = Required<\n Omit<DgmoOptions, 'showSource' | 'showCopy' | 'showOpenInEditor'>\n> & {\n showSource: boolean;\n showCopy: boolean;\n showOpenInEditor: boolean;\n};\n\n/**\n * Apply defaults, including mode-dependent defaults for showSource/showCopy/showOpenInEditor.\n */\nexport function resolveOptions(opts: DgmoOptions = {}): ResolvedOptions {\n const mode: Mode = opts.mode ?? 'diagram';\n const showcase = mode === 'showcase';\n return {\n mode,\n palette: opts.palette ?? 'nord',\n theme: opts.theme ?? 'dark',\n colorMode: opts.colorMode ?? 'auto',\n showSource: opts.showSource ?? showcase,\n showCopy: opts.showCopy ?? showcase,\n showOpenInEditor: opts.showOpenInEditor ?? showcase,\n editorBaseUrl: opts.editorBaseUrl ?? 'https://online.diagrammo.app',\n wrapper: opts.wrapper ?? 'figure',\n className: opts.className ?? 'dgmo',\n legacyClassNames: opts.legacyClassNames ?? [],\n };\n}\n","import type { Mode, Theme } from './options.js';\n\n/**\n * Per-block options that can be set via the fence info string, e.g.\n *\n * ```dgmo showcase palette=catppuccin theme=light title=\"Login flow\"\n *\n * Tokens are space-separated. Boolean tokens are bare words (`showcase`,\n * `diagram`, `noSource`). Key=value pairs use `=`; values may be quoted with\n * double quotes to include spaces.\n */\nexport interface BlockOptions {\n mode?: Mode;\n palette?: string;\n theme?: Theme;\n colorMode?: 'auto' | 'light' | 'dark';\n showSource?: boolean;\n showCopy?: boolean;\n showOpenInEditor?: boolean;\n title?: string;\n}\n\nconst BARE_FLAGS: Record<string, Partial<BlockOptions>> = {\n diagram: { mode: 'diagram' },\n showcase: { mode: 'showcase' },\n noSource: { showSource: false },\n source: { showSource: true },\n noCopy: { showCopy: false },\n copy: { showCopy: true },\n noOpenInEditor: { showOpenInEditor: false },\n openInEditor: { showOpenInEditor: true },\n};\n\nconst VALID_THEMES = new Set<Theme>(['light', 'dark', 'transparent']);\nconst VALID_COLOR_MODES = new Set<'auto' | 'light' | 'dark'>([\n 'auto',\n 'light',\n 'dark',\n]);\n\n/**\n * Parse the meta string that follows ```dgmo on the fence line.\n * Robust to: empty, missing, malformed input.\n */\nexport function parseFenceMeta(meta: string | null | undefined): BlockOptions {\n if (!meta) return {};\n const out: BlockOptions = {};\n const tokens = tokenize(meta);\n for (const tok of tokens) {\n if (BARE_FLAGS[tok]) {\n Object.assign(out, BARE_FLAGS[tok]);\n continue;\n }\n const eq = tok.indexOf('=');\n if (eq < 0) continue;\n const key = tok.slice(0, eq).trim();\n const rawVal = tok.slice(eq + 1).trim();\n const val = unquote(rawVal);\n switch (key) {\n case 'palette':\n if (val) out.palette = val;\n break;\n case 'theme':\n if (VALID_THEMES.has(val as Theme)) out.theme = val as Theme;\n break;\n case 'colorMode':\n if (VALID_COLOR_MODES.has(val as 'auto' | 'light' | 'dark')) {\n out.colorMode = val as 'auto' | 'light' | 'dark';\n }\n break;\n case 'mode':\n if (val === 'diagram' || val === 'showcase') out.mode = val;\n break;\n case 'showSource':\n out.showSource = parseBool(val);\n break;\n case 'showCopy':\n out.showCopy = parseBool(val);\n break;\n case 'showOpenInEditor':\n out.showOpenInEditor = parseBool(val);\n break;\n case 'title':\n if (val) out.title = val;\n break;\n }\n }\n return out;\n}\n\nfunction parseBool(s: string): boolean {\n return s === 'true' || s === '1' || s === 'yes';\n}\n\nfunction unquote(s: string): string {\n if (s.length >= 2 && s.startsWith('\"') && s.endsWith('\"')) {\n return s.slice(1, -1);\n }\n return s;\n}\n\n/**\n * Split on whitespace, respecting double-quoted segments so a value like\n * `title=\"Login flow\"` survives intact.\n */\nfunction tokenize(input: string): string[] {\n const out: string[] = [];\n let buf = '';\n let inQuotes = false;\n for (let i = 0; i < input.length; i++) {\n const ch = input[i];\n if (ch === '\"') {\n inQuotes = !inQuotes;\n buf += ch;\n continue;\n }\n if (!inQuotes && /\\s/.test(ch)) {\n if (buf) {\n out.push(buf);\n buf = '';\n }\n continue;\n }\n buf += ch;\n }\n if (buf) out.push(buf);\n return out;\n}\n","/**\n * Normalize an SVG produced by `@diagrammo/dgmo` for inline embedding:\n *\n * - Ensure the root `<svg>` has a `viewBox` so it scales responsively.\n * - Strip fixed `width=\"N\"` / `height=\"N\"` so CSS controls sizing.\n * - Remove any inline `background:` from the root style so the page background\n * shows through.\n */\nexport function normalizeSvg(input: string): string {\n let svg = input;\n const rootMatch = svg.match(/<svg[^>]*>/);\n const rootTag = rootMatch?.[0] ?? '';\n if (rootTag && !rootTag.includes('viewBox')) {\n const wh = rootTag.match(/width=\"(\\d+)\"[^>]*height=\"(\\d+)\"/);\n if (wh) {\n svg = svg.replace(/<svg/, `<svg viewBox=\"0 0 ${wh[1]} ${wh[2]}\"`);\n }\n }\n svg = svg.replace(/(<svg[^>]*?) width=\"[^\"]*\"/g, '$1');\n svg = svg.replace(/(<svg[^>]*?) height=\"[^\"]*\"/g, '$1');\n svg = svg.replace(/(<svg[^>]*?style=\"[^\"]*?)background:[^;\"]*;?\\s*/g, '$1');\n svg = svg.replace(/<svg\\s{2,}/g, '<svg ');\n return svg;\n}\n","const HTML_ENTITIES: Record<string, string> = {\n '&': '&amp;',\n '<': '&lt;',\n '>': '&gt;',\n '\"': '&quot;',\n \"'\": '&#39;',\n};\n\nexport function escapeHtml(s: string): string {\n return s.replace(/[&<>\"']/g, ch => HTML_ENTITIES[ch]);\n}\n\nexport function escapeAttr(s: string): string {\n return s.replace(/[&<>\"']/g, ch => HTML_ENTITIES[ch]);\n}\n"],"mappings":"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","names":["editorUrl","html"]}
@@ -0,0 +1,4 @@
1
+ /* remark-dgmo color-mode visibility — override the [data-theme="dark"] selector for .dark-style toggles. */
2
+ .dgmo-dark { display: none; }
3
+ [data-theme="dark"] .dgmo-light { display: none; }
4
+ [data-theme="dark"] .dgmo-dark { display: block; }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Framework-neutral client-side enhancement for diagrams emitted by
3
+ * `remark-dgmo`:
4
+ *
5
+ * - Bind a delegated click handler for `button.dgmo-copy` to copy the
6
+ * source string in `data-dgmo-source` to the clipboard.
7
+ * - Tighten each diagram's `viewBox` to its actual content bounds via
8
+ * `SVGGraphicsElement.getBBox()`, since SVG-export from the renderer
9
+ * embeds a generous bounding box.
10
+ *
11
+ * `bindDgmo()` is safe to call multiple times: the click handler is bound
12
+ * once-and-only-once (idempotent), and viewBox tightening is run every
13
+ * invocation so SPA-style frameworks (Docusaurus) can re-run it after
14
+ * route changes.
15
+ *
16
+ * In a non-browser environment (Node SSR), `bindDgmo()` is a no-op.
17
+ */
18
+ declare function bindDgmo(): void;
19
+
20
+ export { bindDgmo };
package/dist/client.js ADDED
@@ -0,0 +1,52 @@
1
+ // src/client.ts
2
+ var clickHandlerBound = false;
3
+ function bindDgmo() {
4
+ if (typeof window === "undefined" || typeof document === "undefined") return;
5
+ if (!clickHandlerBound) {
6
+ document.addEventListener("click", handleCopyClick);
7
+ clickHandlerBound = true;
8
+ }
9
+ tightenViewBoxes();
10
+ }
11
+ async function handleCopyClick(e) {
12
+ const target = e.target;
13
+ if (!target || typeof target.closest !== "function") return;
14
+ const btn = target.closest("button.dgmo-copy");
15
+ if (!btn) return;
16
+ const src = btn.dataset.dgmoSource ?? "";
17
+ try {
18
+ await navigator.clipboard.writeText(src);
19
+ } catch {
20
+ return;
21
+ }
22
+ btn.classList.add("dgmo-copy--success");
23
+ setTimeout(() => btn.classList.remove("dgmo-copy--success"), 1500);
24
+ }
25
+ function tightenViewBoxes() {
26
+ const SVG_SELECTORS = ".dgmo-svg svg, .dgmo-light svg, .dgmo-dark svg";
27
+ document.querySelectorAll(SVG_SELECTORS).forEach((node) => {
28
+ const svg = node;
29
+ try {
30
+ const bbox = svg.getBBox();
31
+ if (bbox.width > 0 && bbox.height > 0) {
32
+ const pad = 16;
33
+ svg.setAttribute(
34
+ "viewBox",
35
+ `${bbox.x - pad} ${bbox.y - pad} ${bbox.width + pad * 2} ${bbox.height + pad * 2}`
36
+ );
37
+ }
38
+ } catch {
39
+ }
40
+ });
41
+ }
42
+ if (typeof window !== "undefined" && typeof document !== "undefined") {
43
+ if (document.readyState === "loading") {
44
+ document.addEventListener("DOMContentLoaded", bindDgmo);
45
+ } else {
46
+ bindDgmo();
47
+ }
48
+ }
49
+ export {
50
+ bindDgmo
51
+ };
52
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/client.ts"],"sourcesContent":["/**\n * Framework-neutral client-side enhancement for diagrams emitted by\n * `remark-dgmo`:\n *\n * - Bind a delegated click handler for `button.dgmo-copy` to copy the\n * source string in `data-dgmo-source` to the clipboard.\n * - Tighten each diagram's `viewBox` to its actual content bounds via\n * `SVGGraphicsElement.getBBox()`, since SVG-export from the renderer\n * embeds a generous bounding box.\n *\n * `bindDgmo()` is safe to call multiple times: the click handler is bound\n * once-and-only-once (idempotent), and viewBox tightening is run every\n * invocation so SPA-style frameworks (Docusaurus) can re-run it after\n * route changes.\n *\n * In a non-browser environment (Node SSR), `bindDgmo()` is a no-op.\n */\n\nlet clickHandlerBound = false;\n\nexport function bindDgmo(): void {\n if (typeof window === 'undefined' || typeof document === 'undefined') return;\n\n if (!clickHandlerBound) {\n document.addEventListener('click', handleCopyClick);\n clickHandlerBound = true;\n }\n tightenViewBoxes();\n}\n\nasync function handleCopyClick(e: Event): Promise<void> {\n const target = e.target as Element | null;\n if (!target || typeof target.closest !== 'function') return;\n const btn = target.closest('button.dgmo-copy') as HTMLElement | null;\n if (!btn) return;\n const src = btn.dataset.dgmoSource ?? '';\n try {\n await navigator.clipboard.writeText(src);\n } catch {\n return;\n }\n btn.classList.add('dgmo-copy--success');\n setTimeout(() => btn.classList.remove('dgmo-copy--success'), 1500);\n}\n\nfunction tightenViewBoxes(): void {\n const SVG_SELECTORS = '.dgmo-svg svg, .dgmo-light svg, .dgmo-dark svg';\n document.querySelectorAll(SVG_SELECTORS).forEach(node => {\n const svg = node as SVGSVGElement;\n try {\n const bbox = (svg as unknown as SVGGraphicsElement).getBBox();\n if (bbox.width > 0 && bbox.height > 0) {\n const pad = 16;\n svg.setAttribute(\n 'viewBox',\n `${bbox.x - pad} ${bbox.y - pad} ${bbox.width + pad * 2} ${\n bbox.height + pad * 2\n }`\n );\n }\n } catch {\n // ignore: SVG not yet in the DOM, or getBBox unsupported\n }\n });\n}\n\n// Auto-init on initial load. Docusaurus-style SPA wrappers also re-call\n// bindDgmo on route changes; that's safe (the click handler is bound once,\n// viewBox tightening runs every time).\nif (typeof window !== 'undefined' && typeof document !== 'undefined') {\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', bindDgmo);\n } else {\n bindDgmo();\n }\n}\n"],"mappings":";AAkBA,IAAI,oBAAoB;AAEjB,SAAS,WAAiB;AAC/B,MAAI,OAAO,WAAW,eAAe,OAAO,aAAa,YAAa;AAEtE,MAAI,CAAC,mBAAmB;AACtB,aAAS,iBAAiB,SAAS,eAAe;AAClD,wBAAoB;AAAA,EACtB;AACA,mBAAiB;AACnB;AAEA,eAAe,gBAAgB,GAAyB;AACtD,QAAM,SAAS,EAAE;AACjB,MAAI,CAAC,UAAU,OAAO,OAAO,YAAY,WAAY;AACrD,QAAM,MAAM,OAAO,QAAQ,kBAAkB;AAC7C,MAAI,CAAC,IAAK;AACV,QAAM,MAAM,IAAI,QAAQ,cAAc;AACtC,MAAI;AACF,UAAM,UAAU,UAAU,UAAU,GAAG;AAAA,EACzC,QAAQ;AACN;AAAA,EACF;AACA,MAAI,UAAU,IAAI,oBAAoB;AACtC,aAAW,MAAM,IAAI,UAAU,OAAO,oBAAoB,GAAG,IAAI;AACnE;AAEA,SAAS,mBAAyB;AAChC,QAAM,gBAAgB;AACtB,WAAS,iBAAiB,aAAa,EAAE,QAAQ,UAAQ;AACvD,UAAM,MAAM;AACZ,QAAI;AACF,YAAM,OAAQ,IAAsC,QAAQ;AAC5D,UAAI,KAAK,QAAQ,KAAK,KAAK,SAAS,GAAG;AACrC,cAAM,MAAM;AACZ,YAAI;AAAA,UACF;AAAA,UACA,GAAG,KAAK,IAAI,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI,KAAK,QAAQ,MAAM,CAAC,IACrD,KAAK,SAAS,MAAM,CACtB;AAAA,QACF;AAAA,MACF;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF,CAAC;AACH;AAKA,IAAI,OAAO,WAAW,eAAe,OAAO,aAAa,aAAa;AACpE,MAAI,SAAS,eAAe,WAAW;AACrC,aAAS,iBAAiB,oBAAoB,QAAQ;AAAA,EACxD,OAAO;AACL,aAAS;AAAA,EACX;AACF;","names":[]}
@@ -0,0 +1,64 @@
1
+ import { D as DgmoOptions, M as Mode, T as Theme } from './remark-plugin-CNBL_aZc.js';
2
+ export { R as RemarkDgmoOptions, a as ResolvedOptions, r as default, r as remarkDgmo, b as resolveOptions } from './remark-plugin-CNBL_aZc.js';
3
+ import 'mdast';
4
+
5
+ interface RenderBlockResult {
6
+ html: string;
7
+ diagnostics: Array<{
8
+ message: string;
9
+ line?: number;
10
+ severity?: string;
11
+ }>;
12
+ }
13
+ /**
14
+ * Optional source-location hint, passed through from the remark transformer so
15
+ * palette-fallback warnings can point at the offending block.
16
+ */
17
+ interface BlockLocation {
18
+ path?: string;
19
+ line?: number;
20
+ }
21
+ /**
22
+ * Render a single ```dgmo block to inline HTML. Pure function: takes source +
23
+ * options and returns the HTML string and any diagnostics from the parser.
24
+ *
25
+ * The remark plugin calls this for every matched code node.
26
+ */
27
+ declare function renderDgmoBlock(source: string, meta: string | null | undefined, integrationOptions?: DgmoOptions, location?: BlockLocation): Promise<RenderBlockResult>;
28
+
29
+ /**
30
+ * Per-block options that can be set via the fence info string, e.g.
31
+ *
32
+ * ```dgmo showcase palette=catppuccin theme=light title="Login flow"
33
+ *
34
+ * Tokens are space-separated. Boolean tokens are bare words (`showcase`,
35
+ * `diagram`, `noSource`). Key=value pairs use `=`; values may be quoted with
36
+ * double quotes to include spaces.
37
+ */
38
+ interface BlockOptions {
39
+ mode?: Mode;
40
+ palette?: string;
41
+ theme?: Theme;
42
+ colorMode?: 'auto' | 'light' | 'dark';
43
+ showSource?: boolean;
44
+ showCopy?: boolean;
45
+ showOpenInEditor?: boolean;
46
+ title?: string;
47
+ }
48
+ /**
49
+ * Parse the meta string that follows ```dgmo on the fence line.
50
+ * Robust to: empty, missing, malformed input.
51
+ */
52
+ declare function parseFenceMeta(meta: string | null | undefined): BlockOptions;
53
+
54
+ /**
55
+ * Normalize an SVG produced by `@diagrammo/dgmo` for inline embedding:
56
+ *
57
+ * - Ensure the root `<svg>` has a `viewBox` so it scales responsively.
58
+ * - Strip fixed `width="N"` / `height="N"` so CSS controls sizing.
59
+ * - Remove any inline `background:` from the root style so the page background
60
+ * shows through.
61
+ */
62
+ declare function normalizeSvg(input: string): string;
63
+
64
+ export { type BlockLocation, type BlockOptions, DgmoOptions, Mode, type RenderBlockResult, Theme, normalizeSvg, parseFenceMeta, renderDgmoBlock };
package/dist/index.js ADDED
@@ -0,0 +1,16 @@
1
+ import {
2
+ normalizeSvg,
3
+ parseFenceMeta,
4
+ remarkDgmo,
5
+ renderDgmoBlock,
6
+ resolveOptions
7
+ } from "./chunk-7VVNWBXS.js";
8
+ export {
9
+ remarkDgmo as default,
10
+ normalizeSvg,
11
+ parseFenceMeta,
12
+ remarkDgmo,
13
+ renderDgmoBlock,
14
+ resolveOptions
15
+ };
16
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,110 @@
1
+ import { Root } from 'mdast';
2
+
3
+ type Mode = 'diagram' | 'showcase';
4
+ type Theme = 'light' | 'dark' | 'transparent';
5
+ /**
6
+ * Framework-agnostic options for `remarkDgmo` / `renderDgmoBlock`.
7
+ *
8
+ * Wrapper packages (`astro-dgmo`, `docusaurus-plugin-dgmo`) re-export this
9
+ * shape under their own conventional names (`DgmoIntegrationOptions`,
10
+ * `DocusaurusDgmoOptions`).
11
+ */
12
+ interface DgmoOptions {
13
+ /**
14
+ * Output mode for `dgmo` fenced blocks.
15
+ * - `diagram` (default): render the SVG only, in a `<figure>`.
16
+ * - `showcase`: render syntax-highlighted source + SVG + copy + open-in-editor.
17
+ *
18
+ * Override per-block via the fence info string: ```dgmo showcase
19
+ */
20
+ mode?: Mode;
21
+ /** Default palette name. Default: `nord`. */
22
+ palette?: string;
23
+ /**
24
+ * Default theme (`light` | `dark` | `transparent`). Default: `dark`.
25
+ *
26
+ * NOTE: under the default `colorMode: 'auto'`, this option is unreachable —
27
+ * dual-render emits both light and dark SVGs regardless. `theme` is consulted
28
+ * only when `colorMode` is explicitly set to `'light'` or `'dark'`.
29
+ */
30
+ theme?: Theme;
31
+ /**
32
+ * Color-mode strategy for emitted SVG(s). Default: `auto`.
33
+ *
34
+ * - `auto` — render every block twice (light + dark palettes) and wrap each
35
+ * SVG in a `<div class="dgmo-light">` / `<div class="dgmo-dark">` so the
36
+ * shipped CSS can flip visibility based on the host site's color-mode
37
+ * signal (`[data-theme="dark"]` by default).
38
+ * - `light` / `dark` — single-render with the matching theme. Halves the
39
+ * emitted SVG bytes; recommended only for single-mode sites.
40
+ */
41
+ colorMode?: 'auto' | 'light' | 'dark';
42
+ /**
43
+ * Show source code above the diagram. Defaults to `true` in showcase mode,
44
+ * `false` in diagram mode.
45
+ */
46
+ showSource?: boolean;
47
+ /**
48
+ * Show a copy-to-clipboard button. Defaults to `true` in showcase mode,
49
+ * `false` in diagram mode.
50
+ */
51
+ showCopy?: boolean;
52
+ /**
53
+ * Show an "Open in online editor" link. Defaults to `true` in showcase mode,
54
+ * `false` in diagram mode.
55
+ */
56
+ showOpenInEditor?: boolean;
57
+ /**
58
+ * Base URL for the "Open in editor" link. Default: `https://online.diagrammo.app`.
59
+ * The plugin appends `?dgmo=...` (compressed source) to the base.
60
+ */
61
+ editorBaseUrl?: string;
62
+ /**
63
+ * Wrapper element. Default: `figure`.
64
+ */
65
+ wrapper?: 'figure' | 'div';
66
+ /**
67
+ * Class added to the outer wrapper. Defaults to `dgmo`.
68
+ * Useful as a styling hook.
69
+ */
70
+ className?: string;
71
+ /**
72
+ * Additional class names appended to every emitted wrapper's `class`
73
+ * attribute. Used by `astro-dgmo` v0.3.0 to emit both the new `dgmo-*` class
74
+ * names and the legacy `astro-dgmo-*` ones for one minor cycle of backward
75
+ * compatibility. Default: `[]`.
76
+ */
77
+ legacyClassNames?: string[];
78
+ }
79
+ type ResolvedOptions = Required<Omit<DgmoOptions, 'showSource' | 'showCopy' | 'showOpenInEditor'>> & {
80
+ showSource: boolean;
81
+ showCopy: boolean;
82
+ showOpenInEditor: boolean;
83
+ };
84
+ /**
85
+ * Apply defaults, including mode-dependent defaults for showSource/showCopy/showOpenInEditor.
86
+ */
87
+ declare function resolveOptions(opts?: DgmoOptions): ResolvedOptions;
88
+
89
+ type RemarkDgmoOptions = DgmoOptions;
90
+ /**
91
+ * Remark plugin that finds ```dgmo fenced code blocks and replaces them with
92
+ * an HTML node containing the rendered SVG (and optional showcase chrome).
93
+ *
94
+ * The `lang` field on the code node is the fence language (the word after the
95
+ * backticks). The `meta` field is everything that follows on the same line,
96
+ * which we use to allow per-block options like ```dgmo showcase palette=catppuccin.
97
+ *
98
+ * Replaces the code node entirely (parent.children[index] = newNode) rather
99
+ * than mutating it in place — otherwise downstream rehype/Shiki plugins still
100
+ * see the lingering `lang: 'dgmo'` and `value: '...source...'` properties and
101
+ * may re-process the block as a plaintext code listing, clobbering our
102
+ * syntax-highlighted output.
103
+ *
104
+ * Async-safe: replacement is collected first, applied after parsing finishes.
105
+ */
106
+ declare function remarkDgmo(options?: RemarkDgmoOptions): (tree: Root, file?: {
107
+ path?: string;
108
+ }) => Promise<void>;
109
+
110
+ export { type DgmoOptions as D, type Mode as M, type RemarkDgmoOptions as R, type Theme as T, type ResolvedOptions as a, resolveOptions as b, remarkDgmo as r };
@@ -0,0 +1,2 @@
1
+ import 'mdast';
2
+ export { R as RemarkDgmoOptions, r as default } from './remark-plugin-CNBL_aZc.js';
@@ -0,0 +1,7 @@
1
+ import {
2
+ remarkDgmo
3
+ } from "./chunk-7VVNWBXS.js";
4
+ export {
5
+ remarkDgmo as default
6
+ };
7
+ //# sourceMappingURL=remark-plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/package.json ADDED
@@ -0,0 +1,82 @@
1
+ {
2
+ "name": "remark-dgmo",
3
+ "version": "0.1.0",
4
+ "description": "Remark plugin to render DGMO diagrams from fenced code blocks at build time. Framework-agnostic core shared by astro-dgmo, docusaurus-plugin-dgmo, and any unified pipeline.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/diagrammo/remark-dgmo.git"
9
+ },
10
+ "homepage": "https://github.com/diagrammo/remark-dgmo#readme",
11
+ "bugs": {
12
+ "url": "https://github.com/diagrammo/remark-dgmo/issues"
13
+ },
14
+ "keywords": [
15
+ "remark",
16
+ "remark-plugin",
17
+ "mdast",
18
+ "unified",
19
+ "dgmo",
20
+ "diagrammo",
21
+ "diagram",
22
+ "diagrams",
23
+ "mdx",
24
+ "markdown",
25
+ "astro",
26
+ "docusaurus",
27
+ "starlight",
28
+ "color-mode"
29
+ ],
30
+ "type": "module",
31
+ "main": "./dist/index.js",
32
+ "module": "./dist/index.js",
33
+ "types": "./dist/index.d.ts",
34
+ "exports": {
35
+ ".": {
36
+ "types": "./dist/index.d.ts",
37
+ "import": "./dist/index.js"
38
+ },
39
+ "./remark": {
40
+ "types": "./dist/remark-plugin.d.ts",
41
+ "import": "./dist/remark-plugin.js"
42
+ },
43
+ "./client.js": "./dist/client.js",
44
+ "./client.css": "./dist/client.css"
45
+ },
46
+ "files": [
47
+ "dist",
48
+ "README.md",
49
+ "LICENSE"
50
+ ],
51
+ "sideEffects": false,
52
+ "engines": {
53
+ "node": ">=20.6"
54
+ },
55
+ "scripts": {
56
+ "build": "tsup && cp styles/client.css dist/client.css",
57
+ "postbuild": "test -f dist/client.css && test -f dist/client.js",
58
+ "dev": "tsup --watch",
59
+ "typecheck": "tsc --noEmit",
60
+ "test": "vitest run",
61
+ "test:watch": "vitest",
62
+ "prepublishOnly": "pnpm build && pnpm test"
63
+ },
64
+ "peerDependencies": {
65
+ "@diagrammo/dgmo": "^0.15.0"
66
+ },
67
+ "dependencies": {
68
+ "@lezer/common": "^1.5.1",
69
+ "@lezer/highlight": "^1.2.3",
70
+ "@lezer/lr": "^1.4.8",
71
+ "unist-util-visit": "^5.0.0"
72
+ },
73
+ "devDependencies": {
74
+ "@diagrammo/dgmo": "^0.15.0",
75
+ "@types/mdast": "^4.0.4",
76
+ "@types/node": "^22.10.0",
77
+ "postcss": "^8.4.49",
78
+ "tsup": "^8.5.1",
79
+ "typescript": "^6.0.2",
80
+ "vitest": "^4.1.4"
81
+ }
82
+ }