@fyeeme/pi-mermaid-viewer 1.0.0 → 1.0.2
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/CHANGELOG.md +16 -0
- package/README.md +14 -0
- package/index.ts +50 -1
- package/package.json +50 -50
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [1.0.2] - 2026-10-02
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Dev toolchain pinned to `@earendil-works/pi-coding-agent` 1.0.0 (peer floors unchanged, `>=0.99.0`); typecheck and tests pass against 1.0.0 unchanged.
|
|
10
|
+
|
|
11
|
+
## [1.0.1] - 2026-10-01
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- Peer dependency floor raised to `@earendil-works/pi-coding-agent >= 0.99.0`; dev toolchain pinned to 0.99.2 (typecheck and tests pass against 0.99.2 unchanged).
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
## [1.0.0] - 2026-06-15
|
|
19
|
+
|
|
5
20
|
### Fixed
|
|
6
21
|
- **Format-switcher menu was invisible.** The `.dl-split` container had `overflow:hidden` (for rounded corners), which clipped the absolutely-positioned `.dl-pop` format menu — it existed in the DOM but was neither visible nor clickable (`elementFromPoint` hit the canvas behind it). Removed `overflow:hidden` and moved the rounding to the individual buttons. SVG/PNG export itself was unaffected.
|
|
7
22
|
- **PNG export silently failed** (download never triggered). Root cause: the `EMOJI_RE` regex was authored inline inside the `renderHtml` template literal, whose backslash-eating corrupted `/[\p{Emoji...}]/gu` into `/[p{Emoji...}]/gu`. As a character class that matches the literal letters p/E/m/o/j/i/..., it stripped ~45% of every exported SVG, so the `<img>` used to rasterize failed to load (`onerror`), `img.onload` never fired, and PNG export hung forever. SVG export was unaffected (it serves the blob directly). Fixed by authoring the regex source at module scope and injecting it, like `quoteBareLabels`.
|
|
@@ -24,3 +39,4 @@
|
|
|
24
39
|
- Emoji support: emoji are now preserved and rendered natively (previously stripped). Verified with Mermaid v11 + `securityLevel: "loose"` + `htmlLabels: true`. Emoji are kept on screen but stripped from SVG/PNG exports for portability.
|
|
25
40
|
- Multi-diagram navigation when conversation contains multiple blocks.
|
|
26
41
|
- Unit tests for the redesign in `test/mermaid-viewer.test.ts` (vitest, 98 cases): `quoteBareLabels` healing/idempotency + the regression that motivated try-first, the `toString()` template-injection invariant (guards the backslash-eating gotcha), and `renderHtml` output validity (`node --check` on the generated `<script>`, healer injected, no stale `sanitize`/`d.fixes`). Added `"test": "vitest --run"` script.
|
|
42
|
+
- **Edge labels are now auto-quoted.** The healer previously covered only node labels and skipped edge labels entirely (the suite even asserted `// Edge labels are untouched`), so a link like `A -->|@HttpExchange| B` failed with `Parse error ... got 'LINK_ID'` and no fix was attempted. `quoteBareLabels` now heals all three Mermaid edge-label forms — pipe `|text|`, dotted `-. text .->`, and dash `-- text -->` / `-- text ---` — quoting any bare label containing a special char (`@`, `/`, `()`, …). Labels are stashed behind placeholders before the node-shape pass, so `|foo(x)|` is never mistaken for a node `foo(x)`. Added 13 edge-label cases + 3 `toString()` re-eval cases (111 tests total).
|
package/README.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
Render Mermaid diagrams found in the conversation as an HTML page opened in the default browser.
|
|
4
4
|
|
|
5
|
+
### 与 pi 0.84.0 内置 TUI mermaid 的区别
|
|
6
|
+
|
|
7
|
+
pi 0.84.0 起内置在终端内(TUI)渲染 Mermaid(`markdown.mermaid` 设置,默认 `streaming`)——适合**终端内速览**。本扩展与之**不是替代关系**,而是互补:
|
|
8
|
+
|
|
9
|
+
| | 本扩展(pi-mermaid-viewer) | pi 内置 TUI mermaid |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| 查看方式 | 浏览器打开 HTML 页面 | 终端内联预览 |
|
|
12
|
+
| 导出 | PNG / SVG 下载 | 无 |
|
|
13
|
+
| 多图 | 多图 tab 导航 | 逐块内联 |
|
|
14
|
+
| 解析失败 | try-first 修复器(仅当 Mermaid 解析失败时自动引号修复裸标签) | 直接显示解析错误 |
|
|
15
|
+
| 适用场景 | 需要检查/导出/分享图时 | 对话中快速扫一眼 |
|
|
16
|
+
|
|
17
|
+
两者可以共存:内置预览用于日常速览,需要细看或导出时再跑 `/mermaid`。
|
|
18
|
+
|
|
5
19
|
## Install
|
|
6
20
|
|
|
7
21
|
Requires the [pi](https://pi.dev) CLI.
|
package/index.ts
CHANGED
|
@@ -55,6 +55,25 @@ export function quoteBareLabels(code: string): { code: string; fixes: string[] }
|
|
|
55
55
|
// One alternation per node; longer wrappers first so ((...)) beats (...).
|
|
56
56
|
const SHAPE_RE = /(\w+)(?:\(\(\(([^)]*)\)\)\)|\(\(([^)]*)\)\)|\{\{([^}]*)\}\}|\[\[([^\]]*)\]\]|\[\(([^)]*)\)\]|\[\/([^/]*)\/\]|\{([^}]*)\}|\(([^)]*)\)|\[([^\]]*)\])/g;
|
|
57
57
|
|
|
58
|
+
// Edge-label syntaxes Mermaid accepts on a link. Text inside any of these
|
|
59
|
+
// must be quoted when it contains a special char, or the parser rejects it
|
|
60
|
+
// (the "got 'LINK_ID'" / "got 'TEXT'" parse errors). Pipe is the canonical
|
|
61
|
+
// form; dotted and dash are the inline-text forms. All three are protected
|
|
62
|
+
// from the node pass below via placeholders, so a label like |foo(x)| is
|
|
63
|
+
// never mistaken for a node shape `foo(x)`.
|
|
64
|
+
const PIPE_RE = /\|([^|\n]*)\|/g; // |text|
|
|
65
|
+
const DOTTED_RE = /(-\.)\s+([^\n]*?)\s+(\.-?>)/g; // -. text .->
|
|
66
|
+
const DASH_RE = /(--)\s+([^\n]*?)\s+(-->|---)/g; // -- text --> / ---
|
|
67
|
+
|
|
68
|
+
// Quote a bare edge-label text if it contains a special char. Already
|
|
69
|
+
// quoted labels are left untouched.
|
|
70
|
+
const healEdgeText = (text: string): { out: string; changed: boolean } => {
|
|
71
|
+
const trimmed = text.trim();
|
|
72
|
+
if (trimmed.charAt(0) === '"' && trimmed.slice(-1) === '"') return { out: text, changed: false };
|
|
73
|
+
if (!SPECIAL.test(text)) return { out: text, changed: false };
|
|
74
|
+
return { out: '"' + text.replace(/^"+|"+$/g, "").trim() + '"', changed: true };
|
|
75
|
+
};
|
|
76
|
+
|
|
58
77
|
const fixed = code.split("\n").map((line) => {
|
|
59
78
|
// Subgraph: only heal a genuinely BARE label. Skip the canonical
|
|
60
79
|
// `ID[...]` form, quoted forms, and bracket forms — all already safe.
|
|
@@ -68,10 +87,37 @@ export function quoteBareLabels(code: string): { code: string; fixes: string[] }
|
|
|
68
87
|
}
|
|
69
88
|
return line;
|
|
70
89
|
}
|
|
90
|
+
|
|
91
|
+
// Protect every edge label with a placeholder BEFORE the node pass, so
|
|
92
|
+
// a label like |foo(x)| or -- foo(x) --> is never mistaken for a node
|
|
93
|
+
// shape `foo(x)`. The node pass runs on the placeholder line and the
|
|
94
|
+
// real labels are restored afterwards.
|
|
95
|
+
const stash: string[] = [];
|
|
96
|
+
const park = (s: string) => { stash.push(s); return "\x00E" + (stash.length - 1) + "\x00"; };
|
|
97
|
+
|
|
98
|
+
let work = line.replace(PIPE_RE, (m, text) => {
|
|
99
|
+
const h = healEdgeText(text);
|
|
100
|
+
if (!h.changed) return park(m);
|
|
101
|
+
fixes.push("edge |" + text.trim() + "|");
|
|
102
|
+
return park("|" + h.out + "|");
|
|
103
|
+
});
|
|
104
|
+
work = work.replace(DOTTED_RE, (m, pre, text, post) => {
|
|
105
|
+
const h = healEdgeText(text);
|
|
106
|
+
if (!h.changed) return park(m);
|
|
107
|
+
fixes.push("edge -. " + text.trim() + " " + post);
|
|
108
|
+
return park(pre + " " + h.out + " " + post);
|
|
109
|
+
});
|
|
110
|
+
work = work.replace(DASH_RE, (m, pre, text, post) => {
|
|
111
|
+
const h = healEdgeText(text);
|
|
112
|
+
if (!h.changed) return park(m);
|
|
113
|
+
fixes.push("edge -- " + text.trim() + " " + post);
|
|
114
|
+
return park(pre + " " + h.out + " " + post);
|
|
115
|
+
});
|
|
116
|
+
|
|
71
117
|
// Nodes: quote bare labels containing special chars. Labels that are
|
|
72
118
|
// already double-quoted are left untouched (defence in depth — though
|
|
73
119
|
// this path only runs after a parse failure anyway).
|
|
74
|
-
|
|
120
|
+
work = work.replace(SHAPE_RE, (match, id, ...groups) => {
|
|
75
121
|
const idx = groups.findIndex((g) => g !== undefined);
|
|
76
122
|
if (idx < 0) return match;
|
|
77
123
|
const shape = SHAPES[idx];
|
|
@@ -83,6 +129,9 @@ export function quoteBareLabels(code: string): { code: string; fixes: string[] }
|
|
|
83
129
|
const clean = label.replace(/^"+|"+$/g, "").trim();
|
|
84
130
|
return id + shape.o + '"' + clean + '"' + shape.c;
|
|
85
131
|
});
|
|
132
|
+
|
|
133
|
+
// Restore the protected edge labels.
|
|
134
|
+
return work.replace(/\x00E(\d+)\x00/g, (_m, i) => stash[Number(i)]);
|
|
86
135
|
}).join("\n");
|
|
87
136
|
|
|
88
137
|
return { code: fixed, fixes };
|
package/package.json
CHANGED
|
@@ -1,52 +1,52 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
2
|
+
"name": "@fyeeme/pi-mermaid-viewer",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "Render Mermaid diagrams from the conversation in the browser.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "fyeeme",
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "https://github.com/fyeeme/pi-packages"
|
|
14
|
+
},
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/fyeeme/pi-packages/issues"
|
|
17
|
+
},
|
|
18
|
+
"homepage": "https://github.com/fyeeme/pi-packages#readme",
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=18"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"pi-package",
|
|
24
|
+
"pi",
|
|
25
|
+
"mermaid",
|
|
26
|
+
"diagram",
|
|
27
|
+
"viewer"
|
|
28
|
+
],
|
|
29
|
+
"files": [
|
|
30
|
+
"index.ts",
|
|
31
|
+
"README.md",
|
|
32
|
+
"LICENSE",
|
|
33
|
+
"CHANGELOG.md"
|
|
34
|
+
],
|
|
35
|
+
"pi": {
|
|
36
|
+
"extensions": [
|
|
37
|
+
"./index.ts"
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"test": "vitest --run",
|
|
42
|
+
"typecheck": "tsc"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"@earendil-works/pi-coding-agent": ">=0.99.0"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@earendil-works/pi-coding-agent": "1.0.0",
|
|
49
|
+
"@types/node": "22.19.19",
|
|
50
|
+
"typescript": "5.9.3"
|
|
51
|
+
}
|
|
52
52
|
}
|