tiptap-markdown-react 0.8.12 → 0.9.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.
Files changed (42) hide show
  1. package/README.md +32 -0
  2. package/dist/ReportContent-Re3hNps8.js +368 -0
  3. package/dist/ReportContent-Re3hNps8.js.map +1 -0
  4. package/dist/chart/ChartExtension.d.ts +21 -0
  5. package/dist/chart/chartRoundTrip.test.d.ts +1 -0
  6. package/dist/chart/columnMatching.d.ts +19 -0
  7. package/dist/chart/createChart.d.ts +19 -0
  8. package/dist/chart/mapData.d.ts +22 -0
  9. package/dist/chart/mapData.test.d.ts +1 -0
  10. package/dist/chart/parse.d.ts +31 -0
  11. package/dist/chart/parse.test.d.ts +1 -0
  12. package/dist/chart/prepareChartMarkdown.d.ts +5 -0
  13. package/dist/chart/renderChart.d.ts +12 -0
  14. package/dist/chart/serialize.d.ts +1 -0
  15. package/dist/chart/theme.d.ts +3 -0
  16. package/dist/chart/types.d.ts +41 -0
  17. package/dist/components/ChartEditorPopover.d.ts +12 -0
  18. package/dist/components/ChartMount.d.ts +14 -0
  19. package/dist/components/ChartView.d.ts +2 -0
  20. package/dist/components/MarkdownPreview.d.ts +3 -2
  21. package/dist/components/ReportContentWithCharts.d.ts +13 -0
  22. package/dist/extensions.d.ts +4 -3
  23. package/dist/{extractToc-CecK9W_w.js → extractToc-Dv40DtV5.js} +145 -9
  24. package/dist/extractToc-Dv40DtV5.js.map +1 -0
  25. package/dist/index.d.ts +8 -2
  26. package/dist/index.js +423 -126
  27. package/dist/index.js.map +1 -1
  28. package/dist/labels.d.ts +9 -0
  29. package/dist/reader.d.ts +3 -8
  30. package/dist/reader.js +9 -6
  31. package/dist/scrollToTocHeading-CIJu2AcX.js +496 -0
  32. package/dist/scrollToTocHeading-CIJu2AcX.js.map +1 -0
  33. package/dist/server.d.ts +4 -1
  34. package/dist/server.js +17 -9
  35. package/dist/server.js.map +1 -1
  36. package/dist/style.css +144 -0
  37. package/package.json +12 -4
  38. package/dist/ReportContent-c_UlJnVY.js +0 -68
  39. package/dist/ReportContent-c_UlJnVY.js.map +0 -1
  40. package/dist/extractToc-CecK9W_w.js.map +0 -1
  41. package/dist/scrollToTocHeading-CwapFaVP.js +0 -126
  42. package/dist/scrollToTocHeading-CwapFaVP.js.map +0 -1
package/README.md CHANGED
@@ -4,6 +4,7 @@ A batteries-included, self-styled **Markdown WYSIWYG editor + reader** suite bui
4
4
 
5
5
  - **Markdown-first**: content goes in and comes out as markdown (`getMarkdown()`), with `getHTML()` / `getJSON()` also exposed.
6
6
  - **Equations**: toolbar inserts inline / block math (KaTeX). Markdown round-trip uses `$$…$$` (inline) and newline-wrapped `$$` (block). Typing `$` / `$$` stays as text so dollar amounts are safe.
7
+ - **Charts**: agentic-ui-compatible data charts (`<!-- {"chartType":…} -->` + GFM table). Chart.js renders in editor / preview / reader (SSR placeholder → client hydrate). MVP: line, bar, column, pie, donut, area.
7
8
  - **Own opinionated UI**: toolbar, color palette, code block, and table of contents ship styled out of the box. Zero `antd`. Dropdowns/popovers use [Radix](https://www.radix-ui.com/) primitives; icons are inline SVG.
8
9
  - **Editor + Preview + Static reader**: edit, live client-side preview, and a pure `renderReportHtml()` for server rendering (Next.js Server Components / ISR). Reading pages that only hydrate footnotes should import `tiptap-markdown-react/reader` so they do not load `TableKit`.
9
10
  - **Table of contents**: stable, shareable slug anchors that match between the editor preview and the published reading page.
@@ -150,6 +151,37 @@ export function ArticleBody({ html }: { html: string }) {
150
151
 
151
152
  Do not also import `tiptap-markdown-react` (the editor entry) from that same client module graph.
152
153
 
154
+ ### 3b. Charts (comment + table)
155
+
156
+ Author / LLM form (matches agentic-ui / invret backend):
157
+
158
+ ```markdown
159
+ <!-- {"chartType": "line", "x": "date", "y": "close", "title": "Price"} -->
160
+
161
+ | date | close |
162
+ |------|------|
163
+ | 2024-01-01 | 100 |
164
+ ```
165
+
166
+ Supported MVP `chartType` values: `line`, `bar`, `column`, `pie`, `donut`, `area`.
167
+ Multiple configs in one comment (`[{...},{...}]`) render as tabs.
168
+ Unsupported types fall back to a normal table.
169
+
170
+ Reading page with hydrate:
171
+
172
+ ```tsx
173
+ 'use client';
174
+ import { ReportContentWithCharts } from 'tiptap-markdown-react/reader';
175
+ import 'tiptap-markdown-react/style.css';
176
+
177
+ export function ArticleBody({ html }: { html: string }) {
178
+ return <ReportContentWithCharts html={html} />;
179
+ }
180
+ ```
181
+
182
+ `renderReportHtml` emits a `div[data-type=chart]` placeholder (no Chart.js on the server).
183
+ Click a chart in the editor to edit JSON config + Markdown table source.
184
+
153
185
  ### 4. Comment anchoring (edit session only)
154
186
 
155
187
  Review / annotation workflows: pass decoded comment segments to the editor; the
@@ -0,0 +1,368 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ function escapeAttr(value) {
3
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
4
+ }
5
+ function enrichMarkdownCitations(markdown, sources) {
6
+ if (!sources.length) return markdown;
7
+ return markdown.replace(/\[\^(\d+)\]/g, (match, num) => {
8
+ const source = sources.find((s) => s.index === num);
9
+ if (!source) return match;
10
+ const attrs = [`class="citation-ref"`, `data-index="${num}"`];
11
+ if (source.url) attrs.push(`data-url="${escapeAttr(source.url)}"`);
12
+ if (source.title) attrs.push(`data-title="${escapeAttr(source.title)}"`);
13
+ if (source.url) {
14
+ return `<span ${attrs.join(" ")}><a href="${escapeAttr(source.url)}" title="${escapeAttr(source.title || "")}" target="_blank" rel="noopener">${num}</a></span>`;
15
+ }
16
+ return `<span ${attrs.join(" ")}>${num}</span>`;
17
+ });
18
+ }
19
+ function patchCitationNode(node, byIndex) {
20
+ var _a, _b, _c, _d;
21
+ if (node.type === "citationRef") {
22
+ const index = String(((_a = node.attrs) == null ? void 0 : _a.index) ?? "");
23
+ const source = byIndex.get(index);
24
+ if (!source) return node;
25
+ return {
26
+ ...node,
27
+ attrs: {
28
+ ...node.attrs,
29
+ index,
30
+ url: source.url ?? ((_b = node.attrs) == null ? void 0 : _b.url) ?? null,
31
+ title: source.title ?? ((_c = node.attrs) == null ? void 0 : _c.title) ?? null
32
+ }
33
+ };
34
+ }
35
+ if (!((_d = node.content) == null ? void 0 : _d.length)) return node;
36
+ return {
37
+ ...node,
38
+ content: node.content.map((child) => patchCitationNode(child, byIndex))
39
+ };
40
+ }
41
+ function applyCitationSources(doc, sources) {
42
+ if (!sources.length) return doc;
43
+ const byIndex = new Map(sources.map((s) => [s.index, s]));
44
+ return patchCitationNode(doc, byIndex);
45
+ }
46
+ const TRAILING_UNIT_SUFFIX_PATTERN = /^(\s*[((][^))]+[))])+\s*$/;
47
+ function columnKeyMatchesConfiguredField(columnKey, configuredField) {
48
+ const ck = (columnKey || "").trim();
49
+ const f = (configuredField || "").trim();
50
+ if (!ck || !f) return false;
51
+ if (ck === f) return true;
52
+ if (!ck.startsWith(f)) return false;
53
+ const rest = ck.slice(f.length);
54
+ return rest === "" || TRAILING_UNIT_SUFFIX_PATTERN.test(rest);
55
+ }
56
+ function resolveChartAxisFieldToColumnKey(configuredField, columnKeys) {
57
+ if (configuredField === void 0 || configuredField === null) {
58
+ return configuredField;
59
+ }
60
+ const f = configuredField.trim();
61
+ if (!f) return configuredField;
62
+ const keySet = new Set(columnKeys);
63
+ if (keySet.has(f)) return f;
64
+ const hit = columnKeys.find((k) => columnKeyMatchesConfiguredField(k, f));
65
+ return hit ?? configuredField;
66
+ }
67
+ function normalizeChartConfigAxisFields(cfg, columnKeys) {
68
+ return {
69
+ ...cfg,
70
+ ...cfg.x !== void 0 ? { x: resolveChartAxisFieldToColumnKey(cfg.x, columnKeys) } : {},
71
+ ...cfg.y !== void 0 ? { y: resolveChartAxisFieldToColumnKey(cfg.y, columnKeys) } : {},
72
+ ...cfg.groupBy !== void 0 ? { groupBy: resolveChartAxisFieldToColumnKey(cfg.groupBy, columnKeys) } : {},
73
+ ...cfg.filterBy !== void 0 ? {
74
+ filterBy: resolveChartAxisFieldToColumnKey(cfg.filterBy, columnKeys)
75
+ } : {},
76
+ ...cfg.colorLegend !== void 0 ? {
77
+ colorLegend: resolveChartAxisFieldToColumnKey(
78
+ cfg.colorLegend,
79
+ columnKeys
80
+ )
81
+ } : {},
82
+ ...cfg.sortBy !== void 0 ? { sortBy: resolveChartAxisFieldToColumnKey(cfg.sortBy, columnKeys) } : {}
83
+ };
84
+ }
85
+ const MVP_CHART_TYPES = [
86
+ "line",
87
+ "bar",
88
+ "column",
89
+ "pie",
90
+ "donut",
91
+ "area"
92
+ ];
93
+ function isMvpChartType(value) {
94
+ return typeof value === "string" && MVP_CHART_TYPES.includes(value);
95
+ }
96
+ const CHART_COMMENT_RE = /^<!--\s*(\[[\s\S]*?\]|\{[\s\S]*?\})\s*-->\s*$/;
97
+ const GFM_TABLE_BLOCK_RE = /^\|(.+)\|\s*\n\|([-:\s|]+)\|\s*\n((?:\|.*\|\s*\n?)*)/;
98
+ function splitRow(line) {
99
+ const trimmed = line.trim();
100
+ const inner = trimmed.startsWith("|") && trimmed.endsWith("|") ? trimmed.slice(1, -1) : trimmed;
101
+ return inner.split("|").map((c) => c.trim());
102
+ }
103
+ function isSeparatorRow(line) {
104
+ const cells = splitRow(line);
105
+ return cells.length > 0 && cells.every((c) => /^:?-+:?$/.test(c));
106
+ }
107
+ function coerceCell(raw) {
108
+ const t = raw.trim();
109
+ if (t === "") return t;
110
+ const normalized = t.replace(/,/g, "");
111
+ if (/^[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?$/.test(normalized)) {
112
+ const n = Number(normalized);
113
+ if (!Number.isNaN(n)) return n;
114
+ }
115
+ return t;
116
+ }
117
+ function parseGfmTable(markdown) {
118
+ const match = GFM_TABLE_BLOCK_RE.exec(markdown);
119
+ if (!match) return null;
120
+ const headerLine = `|${match[1]}|`;
121
+ const sepLine = `|${match[2]}|`;
122
+ if (!isSeparatorRow(sepLine)) return null;
123
+ const headers = splitRow(headerLine);
124
+ if (headers.length === 0 || headers.every((h) => !h)) return null;
125
+ const columns = headers.map((h) => ({
126
+ title: h,
127
+ dataIndex: h
128
+ }));
129
+ const body = match[3] ?? "";
130
+ const dataSource = [];
131
+ for (const line of body.split("\n")) {
132
+ if (!line.trim()) continue;
133
+ if (!line.includes("|")) continue;
134
+ const cells = splitRow(line);
135
+ if (cells.length === 0) continue;
136
+ const row = {};
137
+ headers.forEach((h, i) => {
138
+ row[h] = coerceCell(cells[i] ?? "");
139
+ });
140
+ dataSource.push(row);
141
+ }
142
+ return {
143
+ columns,
144
+ dataSource,
145
+ rawLength: match[0].length
146
+ };
147
+ }
148
+ function looksLikeChartCommentJson(parsed) {
149
+ if (Array.isArray(parsed)) {
150
+ return parsed.some(
151
+ (c) => c && typeof c === "object" && "chartType" in c && typeof c.chartType === "string"
152
+ );
153
+ }
154
+ if (parsed && typeof parsed === "object") {
155
+ const o = parsed;
156
+ if (Array.isArray(o.config)) return true;
157
+ if (typeof o.chartType === "string") return true;
158
+ }
159
+ return false;
160
+ }
161
+ function unwrapChartConfigs(raw) {
162
+ if (raw == null) return null;
163
+ if (Array.isArray(raw)) {
164
+ return raw.filter(
165
+ (c) => !!c && typeof c === "object" && typeof c.chartType === "string"
166
+ );
167
+ }
168
+ if (typeof raw !== "object") return null;
169
+ const o = raw;
170
+ if (Array.isArray(o.config)) {
171
+ return unwrapChartConfigs(o.config);
172
+ }
173
+ if (typeof o.chartType === "string") {
174
+ return [o];
175
+ }
176
+ return null;
177
+ }
178
+ function inferPieAxes(cfg, columns) {
179
+ if (cfg.x && cfg.y) return cfg;
180
+ if (columns.length < 2) return cfg;
181
+ return {
182
+ ...cfg,
183
+ x: cfg.x ?? columns[0].dataIndex,
184
+ y: cfg.y ?? columns[1].dataIndex
185
+ };
186
+ }
187
+ function axisExists(field, keys) {
188
+ if (!field) return false;
189
+ return keys.includes(field);
190
+ }
191
+ function normalizeChartPayload(configs, columns, dataSource) {
192
+ const keys = columns.map((c) => c.dataIndex);
193
+ const normalized = [];
194
+ for (const raw of configs) {
195
+ if (!isMvpChartType(raw.chartType)) continue;
196
+ let cfg = normalizeChartConfigAxisFields({ ...raw }, keys);
197
+ const type = cfg.chartType;
198
+ if (type === "pie" || type === "donut") {
199
+ cfg = inferPieAxes(cfg, columns);
200
+ }
201
+ if (!axisExists(cfg.x, keys) || !axisExists(cfg.y, keys)) continue;
202
+ if (cfg.subgraphBy) {
203
+ const { subgraphBy: _drop, ...rest } = cfg;
204
+ cfg = rest;
205
+ }
206
+ normalized.push(cfg);
207
+ }
208
+ if (normalized.length === 0) return null;
209
+ return { config: normalized, columns, dataSource };
210
+ }
211
+ function parseChartFenceBody(body) {
212
+ let parsed;
213
+ try {
214
+ parsed = JSON.parse(body.trim());
215
+ } catch {
216
+ return null;
217
+ }
218
+ if (!parsed || typeof parsed !== "object") return null;
219
+ const o = parsed;
220
+ if (Array.isArray(o.columns) || Array.isArray(o.dataSource)) {
221
+ const configs2 = unwrapChartConfigs(o.config ?? o) ?? [];
222
+ const columns = o.columns ?? [];
223
+ const dataSource = o.dataSource ?? o.data ?? [];
224
+ if (!columns.length) {
225
+ if (Array.isArray(o.data) && typeof o.chartType === "string") {
226
+ const simpleCols = [
227
+ { title: String(o.x ?? "x"), dataIndex: "x" },
228
+ { title: String(o.y ?? "y"), dataIndex: "y" }
229
+ ];
230
+ const rows = o.data.map((item) => {
231
+ if (item && typeof item === "object") return item;
232
+ return {};
233
+ });
234
+ return normalizeChartPayload(
235
+ [{ chartType: String(o.chartType), x: "x", y: "y", title: o.title }],
236
+ simpleCols,
237
+ rows
238
+ );
239
+ }
240
+ return null;
241
+ }
242
+ const colObjs = columns.map(
243
+ (c) => typeof c === "string" ? { title: c, dataIndex: c } : {
244
+ title: String(c.title ?? c.dataIndex),
245
+ dataIndex: String(c.dataIndex ?? c.title)
246
+ }
247
+ );
248
+ return normalizeChartPayload(configs2, colObjs, dataSource);
249
+ }
250
+ const configs = unwrapChartConfigs(parsed);
251
+ if (!configs) return null;
252
+ return null;
253
+ }
254
+ function tryParseCommentTableChart(src) {
255
+ const trimmedStart = src.replace(/^\s*/, "");
256
+ const offset = src.length - trimmedStart.length;
257
+ if (!trimmedStart.startsWith("<!--")) return null;
258
+ const endComment = trimmedStart.indexOf("-->");
259
+ if (endComment < 0) return null;
260
+ const commentBlock = trimmedStart.slice(0, endComment + 3);
261
+ const cm = CHART_COMMENT_RE.exec(commentBlock.trim());
262
+ if (!cm) return null;
263
+ let json;
264
+ try {
265
+ json = JSON.parse(cm[1]);
266
+ } catch {
267
+ return null;
268
+ }
269
+ if (!looksLikeChartCommentJson(json)) return null;
270
+ const configs = unwrapChartConfigs(json);
271
+ if (!configs || configs.length === 0) return null;
272
+ if (configs.every((c) => c.chartType === "table")) return null;
273
+ const afterComment = trimmedStart.slice(endComment + 3).replace(/^\s*\n/, "\n");
274
+ const tableStart = afterComment.search(/\|/);
275
+ if (tableStart < 0) return null;
276
+ const beforeTable = afterComment.slice(0, tableStart);
277
+ if (beforeTable.replace(/\s/g, "") !== "") return null;
278
+ const tableMd = afterComment.slice(tableStart);
279
+ const table = parseGfmTable(tableMd);
280
+ if (!table) return null;
281
+ const payload = normalizeChartPayload(
282
+ configs,
283
+ table.columns,
284
+ table.dataSource
285
+ );
286
+ if (!payload) return null;
287
+ const raw = src.slice(0, offset) + commentBlock + afterComment.slice(0, tableStart) + tableMd.slice(0, table.rawLength);
288
+ return { payload, raw };
289
+ }
290
+ function serializeChartPayload(payload) {
291
+ const flat = payload.config.length === 1 ? payload.config[0] : payload.config;
292
+ const comment = `<!-- ${JSON.stringify(flat)} -->`;
293
+ const headers = payload.columns.map((c) => c.title);
294
+ const keys = payload.columns.map((c) => c.dataIndex);
295
+ const header = `| ${headers.join(" | ")} |`;
296
+ const sep = `| ${headers.map(() => "---").join(" | ")} |`;
297
+ const rows = payload.dataSource.map((row) => {
298
+ const cells = keys.map((k) => {
299
+ const v = row[k];
300
+ if (v == null) return "";
301
+ return String(v);
302
+ });
303
+ return `| ${cells.join(" | ")} |`;
304
+ });
305
+ return [comment, "", header, sep, ...rows].join("\n");
306
+ }
307
+ function normalizeChartMarkdown(markdown) {
308
+ if (!markdown) return markdown;
309
+ let rest = markdown;
310
+ let out = "";
311
+ while (rest.length > 0) {
312
+ const idx = rest.indexOf("<!--");
313
+ if (idx < 0) {
314
+ out += rest;
315
+ break;
316
+ }
317
+ out += rest.slice(0, idx);
318
+ const attempt = tryParseCommentTableChart(rest.slice(idx));
319
+ if (!attempt) {
320
+ out += "<!--";
321
+ rest = rest.slice(idx + 4);
322
+ continue;
323
+ }
324
+ const fenceBody = JSON.stringify({
325
+ config: attempt.payload.config,
326
+ columns: attempt.payload.columns,
327
+ dataSource: attempt.payload.dataSource
328
+ });
329
+ out += `\`\`\`tmr-chart
330
+ ${fenceBody}
331
+ \`\`\``;
332
+ rest = rest.slice(idx + attempt.raw.length);
333
+ if (rest.startsWith("\n")) ;
334
+ else if (rest.length > 0) {
335
+ out += "\n";
336
+ }
337
+ }
338
+ return out;
339
+ }
340
+ const editorScroll = "_editorScroll_1rmvx_4";
341
+ const editorContent = "_editorContent_1rmvx_9";
342
+ const styles = {
343
+ editorScroll,
344
+ editorContent
345
+ };
346
+ function ReportContent({ html, className, ...rest }) {
347
+ return /* @__PURE__ */ jsx(
348
+ "div",
349
+ {
350
+ ...rest,
351
+ className: className ? `${styles.editorContent} ${className}` : styles.editorContent,
352
+ dangerouslySetInnerHTML: { __html: html }
353
+ }
354
+ );
355
+ }
356
+ export {
357
+ ReportContent as R,
358
+ applyCitationSources as a,
359
+ parseGfmTable as b,
360
+ normalizeChartPayload as c,
361
+ styles as d,
362
+ enrichMarkdownCitations as e,
363
+ normalizeChartMarkdown as n,
364
+ parseChartFenceBody as p,
365
+ serializeChartPayload as s,
366
+ unwrapChartConfigs as u
367
+ };
368
+ //# sourceMappingURL=ReportContent-Re3hNps8.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReportContent-Re3hNps8.js","sources":["../src/citationUtils.ts","../src/chart/columnMatching.ts","../src/chart/types.ts","../src/chart/parse.ts","../src/ReportContent.tsx"],"sourcesContent":["import type { JSONContent } from '@tiptap/core';\n\nexport interface SourceRef {\n /** 与 markdown `[^n]` 中的 n 对齐(字符串,如 `\"1\"`)。 */\n index: string;\n url?: string;\n title?: string;\n /** 原文片段等;库本身不渲染 Popover,留给消费方。 */\n excerpt?: string;\n}\n\nfunction escapeAttr(value: string): string {\n return value\n .replace(/&/g, '&amp;')\n .replace(/\"/g, '&quot;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;');\n}\n\n/**\n * 将 markdown 中能匹配到 `sources` 的 `[^n]` 换成带 data-* 的\n * `<span class=\"citation-ref\">`(**不是** `<sup>`,避免被 Superscript 吃掉、\n * 也避免视觉上标)。未匹配的 `[^n]` 原样保留,留给 CitationRef tokenizer。\n *\n * 浏览器端 @tiptap/markdown 可解析内联 HTML;server 端无 DOMParser 时请改用\n * {@link applyCitationSources}(先 parse 再补 attrs)。\n */\nexport function enrichMarkdownCitations(\n markdown: string,\n sources: SourceRef[],\n): string {\n if (!sources.length) return markdown;\n\n return markdown.replace(/\\[\\^(\\d+)\\]/g, (match, num: string) => {\n const source = sources.find((s) => s.index === num);\n if (!source) return match;\n\n const attrs = [`class=\"citation-ref\"`, `data-index=\"${num}\"`];\n if (source.url) attrs.push(`data-url=\"${escapeAttr(source.url)}\"`);\n if (source.title) attrs.push(`data-title=\"${escapeAttr(source.title)}\"`);\n\n if (source.url) {\n return `<span ${attrs.join(' ')}><a href=\"${escapeAttr(source.url)}\" title=\"${escapeAttr(source.title || '')}\" target=\"_blank\" rel=\"noopener\">${num}</a></span>`;\n }\n return `<span ${attrs.join(' ')}>${num}</span>`;\n });\n}\n\nfunction patchCitationNode(\n node: JSONContent,\n byIndex: Map<string, SourceRef>,\n): JSONContent {\n if (node.type === 'citationRef') {\n const index = String(node.attrs?.index ?? '');\n const source = byIndex.get(index);\n if (!source) return node;\n return {\n ...node,\n attrs: {\n ...node.attrs,\n index,\n url: source.url ?? node.attrs?.url ?? null,\n title: source.title ?? node.attrs?.title ?? null,\n },\n };\n }\n\n if (!node.content?.length) return node;\n return {\n ...node,\n content: node.content.map((child) => patchCitationNode(child, byIndex)),\n };\n}\n\n/**\n * 在已 parse 的 Tiptap JSON 上,按 `sources` 给 `citationRef` 节点补 url/title。\n * SSR / 无 DOMParser 环境的首选路径。\n */\nexport function applyCitationSources(\n doc: JSONContent,\n sources: SourceRef[],\n): JSONContent {\n if (!sources.length) return doc;\n const byIndex = new Map(sources.map((s) => [s.index, s]));\n return patchCitationNode(doc, byIndex);\n}\n","/**\n * Loose header ↔ config field matching (ported from agentic-ui columnMatching).\n * Zero runtime deps beyond string/regex.\n */\n\nconst TRAILING_UNIT_SUFFIX_PATTERN = /^(\\s*[((][^))]+[))])+\\s*$/;\n\n/** Exact match, or column = field + parenthetical unit suffix. */\nexport function columnKeyMatchesConfiguredField(\n columnKey: string,\n configuredField: string,\n): boolean {\n const ck = (columnKey || '').trim();\n const f = (configuredField || '').trim();\n if (!ck || !f) return false;\n if (ck === f) return true;\n if (!ck.startsWith(f)) return false;\n const rest = ck.slice(f.length);\n return rest === '' || TRAILING_UNIT_SUFFIX_PATTERN.test(rest);\n}\n\n/**\n * Resolve configured x/y name to a real column dataIndex.\n * Returns original field when no match (caller decides degrade).\n */\nexport function resolveChartAxisFieldToColumnKey(\n configuredField: string | undefined,\n columnKeys: string[],\n): string | undefined {\n if (configuredField === undefined || configuredField === null) {\n return configuredField;\n }\n const f = configuredField.trim();\n if (!f) return configuredField;\n const keySet = new Set(columnKeys);\n if (keySet.has(f)) return f;\n const hit = columnKeys.find((k) => columnKeyMatchesConfiguredField(k, f));\n return hit ?? configuredField;\n}\n\nexport function normalizeChartConfigAxisFields<\n T extends { x?: string; y?: string; groupBy?: string; filterBy?: string; colorLegend?: string; sortBy?: string },\n>(cfg: T, columnKeys: string[]): T {\n return {\n ...cfg,\n ...(cfg.x !== undefined\n ? { x: resolveChartAxisFieldToColumnKey(cfg.x, columnKeys) }\n : {}),\n ...(cfg.y !== undefined\n ? { y: resolveChartAxisFieldToColumnKey(cfg.y, columnKeys) }\n : {}),\n ...(cfg.groupBy !== undefined\n ? { groupBy: resolveChartAxisFieldToColumnKey(cfg.groupBy, columnKeys) }\n : {}),\n ...(cfg.filterBy !== undefined\n ? {\n filterBy: resolveChartAxisFieldToColumnKey(cfg.filterBy, columnKeys),\n }\n : {}),\n ...(cfg.colorLegend !== undefined\n ? {\n colorLegend: resolveChartAxisFieldToColumnKey(\n cfg.colorLegend,\n columnKeys,\n ),\n }\n : {}),\n ...(cfg.sortBy !== undefined\n ? { sortBy: resolveChartAxisFieldToColumnKey(cfg.sortBy, columnKeys) }\n : {}),\n };\n}\n","/** MVP chart types aligned with agentic-ui (subset). */\nexport const MVP_CHART_TYPES = [\n 'line',\n 'bar',\n 'column',\n 'pie',\n 'donut',\n 'area',\n] as const;\n\nexport type MvpChartType = (typeof MVP_CHART_TYPES)[number];\n\nexport function isMvpChartType(value: unknown): value is MvpChartType {\n return (\n typeof value === 'string' &&\n (MVP_CHART_TYPES as readonly string[]).includes(value)\n );\n}\n\n/**\n * Flat chart config (author / LLM form).\n * `subgraphBy` is accepted but ignored in v1.\n */\nexport interface ChartConfig {\n chartType: string;\n x?: string;\n y?: string;\n title?: string;\n height?: number;\n dataTime?: string;\n groupBy?: string;\n filterBy?: string;\n colorLegend?: string;\n sortBy?: string;\n subgraphBy?: string;\n [key: string]: unknown;\n}\n\nexport interface ChartColumn {\n title: string;\n dataIndex: string;\n}\n\n/** Normalized payload stored on the TipTap chart node. */\nexport interface ChartPayload {\n config: ChartConfig[];\n columns: ChartColumn[];\n dataSource: Record<string, unknown>[];\n}\n\n/** Optional host override for Chart.js colors / fonts. */\nexport interface ChartTheme {\n backgroundColor?: string;\n textColor?: string;\n gridColor?: string;\n borderColor?: string;\n fontFamily?: string;\n palette?: string[];\n}\n","import { normalizeChartConfigAxisFields } from './columnMatching';\nimport {\n isMvpChartType,\n type ChartColumn,\n type ChartConfig,\n type ChartPayload,\n type MvpChartType,\n} from './types';\n\nconst CHART_COMMENT_RE = /^<!--\\s*(\\[[\\s\\S]*?\\]|\\{[\\s\\S]*?\\})\\s*-->\\s*$/;\n\n/** GFM table: header + separator + optional body rows. */\nconst GFM_TABLE_BLOCK_RE =\n /^\\|(.+)\\|\\s*\\n\\|([-:\\s|]+)\\|\\s*\\n((?:\\|.*\\|\\s*\\n?)*)/;\n\nfunction splitRow(line: string): string[] {\n const trimmed = line.trim();\n const inner =\n trimmed.startsWith('|') && trimmed.endsWith('|')\n ? trimmed.slice(1, -1)\n : trimmed;\n return inner.split('|').map((c) => c.trim());\n}\n\nfunction isSeparatorRow(line: string): boolean {\n const cells = splitRow(line);\n return cells.length > 0 && cells.every((c) => /^:?-+:?$/.test(c));\n}\n\nfunction coerceCell(raw: string): string | number {\n const t = raw.trim();\n if (t === '') return t;\n const normalized = t.replace(/,/g, '');\n if (/^[-+]?(\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?$/.test(normalized)) {\n const n = Number(normalized);\n if (!Number.isNaN(n)) return n;\n }\n return t;\n}\n\n/** Parse a GFM markdown table into columns + rows. */\nexport function parseGfmTable(markdown: string): {\n columns: ChartColumn[];\n dataSource: Record<string, unknown>[];\n rawLength: number;\n} | null {\n const match = GFM_TABLE_BLOCK_RE.exec(markdown);\n if (!match) return null;\n const headerLine = `|${match[1]}|`;\n const sepLine = `|${match[2]}|`;\n if (!isSeparatorRow(sepLine)) return null;\n\n const headers = splitRow(headerLine);\n if (headers.length === 0 || headers.every((h) => !h)) return null;\n\n const columns: ChartColumn[] = headers.map((h) => ({\n title: h,\n dataIndex: h,\n }));\n\n const body = match[3] ?? '';\n const dataSource: Record<string, unknown>[] = [];\n for (const line of body.split('\\n')) {\n if (!line.trim()) continue;\n if (!line.includes('|')) continue;\n const cells = splitRow(line);\n if (cells.length === 0) continue;\n const row: Record<string, unknown> = {};\n headers.forEach((h, i) => {\n row[h] = coerceCell(cells[i] ?? '');\n });\n dataSource.push(row);\n }\n\n return {\n columns,\n dataSource,\n rawLength: match[0].length,\n };\n}\n\nfunction looksLikeChartCommentJson(parsed: unknown): boolean {\n if (Array.isArray(parsed)) {\n return parsed.some(\n (c) =>\n c &&\n typeof c === 'object' &&\n 'chartType' in (c as object) &&\n typeof (c as ChartConfig).chartType === 'string',\n );\n }\n if (parsed && typeof parsed === 'object') {\n const o = parsed as Record<string, unknown>;\n if (Array.isArray(o.config)) return true;\n if (typeof o.chartType === 'string') return true;\n }\n return false;\n}\n\n/** Unwrap comment JSON into a flat config list (may include non-MVP types). */\nexport function unwrapChartConfigs(raw: unknown): ChartConfig[] | null {\n if (raw == null) return null;\n if (Array.isArray(raw)) {\n return raw.filter(\n (c): c is ChartConfig =>\n !!c && typeof c === 'object' && typeof (c as ChartConfig).chartType === 'string',\n );\n }\n if (typeof raw !== 'object') return null;\n const o = raw as Record<string, unknown>;\n if (Array.isArray(o.config)) {\n return unwrapChartConfigs(o.config);\n }\n if (typeof o.chartType === 'string') {\n return [o as ChartConfig];\n }\n return null;\n}\n\nfunction inferPieAxes(\n cfg: ChartConfig,\n columns: ChartColumn[],\n): ChartConfig {\n if (cfg.x && cfg.y) return cfg;\n if (columns.length < 2) return cfg;\n return {\n ...cfg,\n x: cfg.x ?? columns[0].dataIndex,\n y: cfg.y ?? columns[1].dataIndex,\n };\n}\n\nfunction axisExists(field: string | undefined, keys: string[]): boolean {\n if (!field) return false;\n return keys.includes(field);\n}\n\n/**\n * Keep only MVP chart configs that have resolvable axes.\n * Returns null when nothing left (caller should leave as plain table).\n */\nexport function normalizeChartPayload(\n configs: ChartConfig[],\n columns: ChartColumn[],\n dataSource: Record<string, unknown>[],\n): ChartPayload | null {\n const keys = columns.map((c) => c.dataIndex);\n const normalized: ChartConfig[] = [];\n\n for (const raw of configs) {\n if (!isMvpChartType(raw.chartType)) continue;\n let cfg = normalizeChartConfigAxisFields({ ...raw }, keys);\n const type = cfg.chartType as MvpChartType;\n if (type === 'pie' || type === 'donut') {\n cfg = inferPieAxes(cfg, columns);\n }\n if (!axisExists(cfg.x, keys) || !axisExists(cfg.y, keys)) continue;\n // v1: ignore subgraphBy\n if (cfg.subgraphBy) {\n const { subgraphBy: _drop, ...rest } = cfg;\n cfg = rest;\n }\n normalized.push(cfg);\n }\n\n if (normalized.length === 0) return null;\n return { config: normalized, columns, dataSource };\n}\n\n/** Parse ```chart / ```json-chart / ```tmr-chart fence body JSON. */\nexport function parseChartFenceBody(body: string): ChartPayload | null {\n let parsed: unknown;\n try {\n parsed = JSON.parse(body.trim());\n } catch {\n return null;\n }\n if (!parsed || typeof parsed !== 'object') return null;\n const o = parsed as Record<string, unknown>;\n\n // Full form\n if (Array.isArray(o.columns) || Array.isArray(o.dataSource)) {\n const configs = unwrapChartConfigs(o.config ?? o) ?? [];\n const columns = (o.columns as ChartColumn[]) ?? [];\n const dataSource =\n (o.dataSource as Record<string, unknown>[]) ??\n (o.data as Record<string, unknown>[]) ??\n [];\n if (!columns.length) {\n // Simple { chartType, x, y, data }\n if (Array.isArray(o.data) && typeof o.chartType === 'string') {\n const simpleCols: ChartColumn[] = [\n { title: String(o.x ?? 'x'), dataIndex: 'x' },\n { title: String(o.y ?? 'y'), dataIndex: 'y' },\n ];\n const rows = (o.data as unknown[]).map((item) => {\n if (item && typeof item === 'object') return item as Record<string, unknown>;\n return {};\n });\n return normalizeChartPayload(\n [{ chartType: String(o.chartType), x: 'x', y: 'y', title: o.title as string | undefined }],\n simpleCols,\n rows,\n );\n }\n return null;\n }\n const colObjs: ChartColumn[] = columns.map((c) =>\n typeof c === 'string'\n ? { title: c, dataIndex: c }\n : {\n title: String((c as ChartColumn).title ?? (c as ChartColumn).dataIndex),\n dataIndex: String((c as ChartColumn).dataIndex ?? (c as ChartColumn).title),\n },\n );\n return normalizeChartPayload(configs, colObjs, dataSource);\n }\n\n const configs = unwrapChartConfigs(parsed);\n if (!configs) return null;\n return null;\n}\n\n/**\n * Try to parse leading `<!-- chart json -->` + GFM table from `src`.\n * Returns payload + consumed char length, or null.\n */\nexport function tryParseCommentTableChart(\n src: string,\n): { payload: ChartPayload; raw: string } | null {\n const trimmedStart = src.replace(/^\\s*/, '');\n const offset = src.length - trimmedStart.length;\n if (!trimmedStart.startsWith('<!--')) return null;\n\n const endComment = trimmedStart.indexOf('-->');\n if (endComment < 0) return null;\n const commentBlock = trimmedStart.slice(0, endComment + 3);\n const cm = CHART_COMMENT_RE.exec(commentBlock.trim());\n if (!cm) return null;\n\n let json: unknown;\n try {\n json = JSON.parse(cm[1]);\n } catch {\n return null;\n }\n if (!looksLikeChartCommentJson(json)) return null;\n\n const configs = unwrapChartConfigs(json);\n if (!configs || configs.length === 0) return null;\n // All table-only → not a chart\n if (configs.every((c) => c.chartType === 'table')) return null;\n\n const afterComment = trimmedStart.slice(endComment + 3).replace(/^\\s*\\n/, '\\n');\n const tableStart = afterComment.search(/\\|/);\n if (tableStart < 0) return null;\n const beforeTable = afterComment.slice(0, tableStart);\n if (beforeTable.replace(/\\s/g, '') !== '') return null;\n\n const tableMd = afterComment.slice(tableStart);\n const table = parseGfmTable(tableMd);\n if (!table) return null;\n\n const payload = normalizeChartPayload(\n configs,\n table.columns,\n table.dataSource,\n );\n if (!payload) return null;\n\n const raw =\n src.slice(0, offset) +\n commentBlock +\n afterComment.slice(0, tableStart) +\n tableMd.slice(0, table.rawLength);\n\n return { payload, raw };\n}\n\n/** Serialize payload to flat comment + GFM table (author form). */\nexport function serializeChartPayload(payload: ChartPayload): string {\n const flat =\n payload.config.length === 1 ? payload.config[0] : payload.config;\n const comment = `<!-- ${JSON.stringify(flat)} -->`;\n const headers = payload.columns.map((c) => c.title);\n const keys = payload.columns.map((c) => c.dataIndex);\n const header = `| ${headers.join(' | ')} |`;\n const sep = `| ${headers.map(() => '---').join(' | ')} |`;\n const rows = payload.dataSource.map((row) => {\n const cells = keys.map((k) => {\n const v = row[k];\n if (v == null) return '';\n return String(v);\n });\n return `| ${cells.join(' | ')} |`;\n });\n return [comment, '', header, sep, ...rows].join('\\n');\n}\n\n/**\n * Replace valid comment+table chart pairs with ```tmr-chart fences\n * so TipTap markdown can tokenize a single fence.\n */\nexport function normalizeChartMarkdown(markdown: string): string {\n if (!markdown) return markdown;\n let rest = markdown;\n let out = '';\n\n while (rest.length > 0) {\n const idx = rest.indexOf('<!--');\n if (idx < 0) {\n out += rest;\n break;\n }\n out += rest.slice(0, idx);\n const attempt = tryParseCommentTableChart(rest.slice(idx));\n if (!attempt) {\n // Skip this comment opener without consuming as chart\n out += '<!--';\n rest = rest.slice(idx + 4);\n continue;\n }\n const fenceBody = JSON.stringify({\n config: attempt.payload.config,\n columns: attempt.payload.columns,\n dataSource: attempt.payload.dataSource,\n });\n out += `\\`\\`\\`tmr-chart\\n${fenceBody}\\n\\`\\`\\``;\n rest = rest.slice(idx + attempt.raw.length);\n // Preserve a newline after fence if original had trailing content\n if (rest.startsWith('\\n')) {\n /* keep */\n } else if (rest.length > 0) {\n out += '\\n';\n }\n }\n\n return out;\n}\n","import type { HTMLAttributes } from 'react';\nimport styles from './styles/content.module.css';\n\nexport interface ReportContentProps extends HTMLAttributes<HTMLDivElement> {\n /** `renderReportHtml(...).html` 产出的正文 HTML 字符串。 */\n html: string;\n}\n\n/**\n * 只读静态正文渲染(RSC / SSR 安全,无 client 代码、无编辑器实例)。\n *\n * 把 `renderReportHtml` 产出的 HTML 注入容器,并套上与编辑器 / 只读预览**同一个\n * 规范正文类**,保证「编辑所见 = 预览所见 = 发布后读者所见」。\n * 用于阅读页首屏直出(SEO 可见),无需 JS 即可显示。\n */\nexport function ReportContent({ html, className, ...rest }: ReportContentProps) {\n return (\n <div\n {...rest}\n className={\n className ? `${styles.editorContent} ${className}` : styles.editorContent\n }\n dangerouslySetInnerHTML={{ __html: html }}\n />\n );\n}\n"],"names":["configs"],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import { Node } from '@tiptap/core';
2
+ import { normalizeChartMarkdown } from './parse';
3
+ import { ChartColumn, ChartConfig } from './types';
4
+ export declare const CHART_NODE_NAME = "chart";
5
+ export interface ChartNodeAttrs {
6
+ config: ChartConfig[];
7
+ columns: ChartColumn[];
8
+ dataSource: Record<string, unknown>[];
9
+ }
10
+ export interface ReportChartOptions {
11
+ HTMLAttributes: Record<string, unknown>;
12
+ }
13
+ /**
14
+ * Pure chart block node — safe for server / SSR.
15
+ * Does not import chart.js or React. renderHTML emits a hydrate placeholder.
16
+ */
17
+ export declare const reportChart: Node<ReportChartOptions, any>;
18
+ /** Re-export preprocess for parse pipelines. */
19
+ export { normalizeChartMarkdown };
20
+ /** Pure chart extension alias for server / preview injection. */
21
+ export declare const pureChart: Node<ReportChartOptions, any>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Loose header ↔ config field matching (ported from agentic-ui columnMatching).
3
+ * Zero runtime deps beyond string/regex.
4
+ */
5
+ /** Exact match, or column = field + parenthetical unit suffix. */
6
+ export declare function columnKeyMatchesConfiguredField(columnKey: string, configuredField: string): boolean;
7
+ /**
8
+ * Resolve configured x/y name to a real column dataIndex.
9
+ * Returns original field when no match (caller decides degrade).
10
+ */
11
+ export declare function resolveChartAxisFieldToColumnKey(configuredField: string | undefined, columnKeys: string[]): string | undefined;
12
+ export declare function normalizeChartConfigAxisFields<T extends {
13
+ x?: string;
14
+ y?: string;
15
+ groupBy?: string;
16
+ filterBy?: string;
17
+ colorLegend?: string;
18
+ sortBy?: string;
19
+ }>(cfg: T, columnKeys: string[]): T;
@@ -0,0 +1,19 @@
1
+ import { ReportChartOptions } from './ChartExtension';
2
+ import { ChartTheme } from './types';
3
+ import { ChartLabels } from '../labels';
4
+ export interface CreateChartOptions {
5
+ /** When false, NodeView is read-only (no edit popover). Default true. */
6
+ editable?: boolean;
7
+ chartTheme?: ChartTheme | null;
8
+ chartLabels?: Partial<ChartLabels>;
9
+ }
10
+ export interface InteractiveChartOptions extends ReportChartOptions {
11
+ chartEditable: boolean;
12
+ chartTheme: ChartTheme | null;
13
+ chartLabels?: Partial<ChartLabels>;
14
+ }
15
+ /**
16
+ * Client chart extension with React NodeView.
17
+ * Server / pure HTML paths should use {@link reportChart} / {@link pureChart}.
18
+ */
19
+ export declare function createChart(options?: CreateChartOptions): import('@tiptap/core').Node<InteractiveChartOptions, any>;
@@ -0,0 +1,22 @@
1
+ import { ChartConfig, ChartPayload } from './types';
2
+ export interface ChartJsPoint {
3
+ x: string | number;
4
+ y: number;
5
+ category?: string;
6
+ type?: string;
7
+ }
8
+ /**
9
+ * Map table rows to flat chart points for one config.
10
+ * colorLegend → series `type`; groupBy stored as category (no UI filter in v1).
11
+ */
12
+ export declare function mapChartDataItems(config: ChartConfig, dataSource: Record<string, unknown>[]): ChartJsPoint[];
13
+ export interface BuiltChartData {
14
+ labels: string[];
15
+ datasets: {
16
+ label: string;
17
+ data: number[];
18
+ }[];
19
+ isCartesian: boolean;
20
+ }
21
+ /** Build Chart.js labels/datasets for one active config. */
22
+ export declare function buildChartJsData(config: ChartConfig, payload: Pick<ChartPayload, 'dataSource'>): BuiltChartData;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,31 @@
1
+ import { ChartColumn, ChartConfig, ChartPayload } from './types';
2
+ /** Parse a GFM markdown table into columns + rows. */
3
+ export declare function parseGfmTable(markdown: string): {
4
+ columns: ChartColumn[];
5
+ dataSource: Record<string, unknown>[];
6
+ rawLength: number;
7
+ } | null;
8
+ /** Unwrap comment JSON into a flat config list (may include non-MVP types). */
9
+ export declare function unwrapChartConfigs(raw: unknown): ChartConfig[] | null;
10
+ /**
11
+ * Keep only MVP chart configs that have resolvable axes.
12
+ * Returns null when nothing left (caller should leave as plain table).
13
+ */
14
+ export declare function normalizeChartPayload(configs: ChartConfig[], columns: ChartColumn[], dataSource: Record<string, unknown>[]): ChartPayload | null;
15
+ /** Parse ```chart / ```json-chart / ```tmr-chart fence body JSON. */
16
+ export declare function parseChartFenceBody(body: string): ChartPayload | null;
17
+ /**
18
+ * Try to parse leading `<!-- chart json -->` + GFM table from `src`.
19
+ * Returns payload + consumed char length, or null.
20
+ */
21
+ export declare function tryParseCommentTableChart(src: string): {
22
+ payload: ChartPayload;
23
+ raw: string;
24
+ } | null;
25
+ /** Serialize payload to flat comment + GFM table (author form). */
26
+ export declare function serializeChartPayload(payload: ChartPayload): string;
27
+ /**
28
+ * Replace valid comment+table chart pairs with ```tmr-chart fences
29
+ * so TipTap markdown can tokenize a single fence.
30
+ */
31
+ export declare function normalizeChartMarkdown(markdown: string): string;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Prepare markdown before TipTap / MarkdownManager parse:
3
+ * collapse `<!-- chart -->` + GFM table pairs into ```tmr-chart fences.
4
+ */
5
+ export declare function prepareChartMarkdown(markdown: string): string;
@@ -0,0 +1,12 @@
1
+ import { Chart } from 'chart.js';
2
+ import { ChartConfig, ChartPayload, ChartTheme } from './types';
3
+ export interface RenderChartOptions {
4
+ canvas: HTMLCanvasElement;
5
+ config: ChartConfig;
6
+ payload: Pick<ChartPayload, 'dataSource' | 'columns'>;
7
+ theme?: ChartTheme | null;
8
+ /** Existing instance to update in place (streaming). */
9
+ chart?: Chart | null;
10
+ }
11
+ export declare function renderOrUpdateChart({ canvas, config, payload, theme, chart, }: RenderChartOptions): Chart | null;
12
+ export declare function destroyChart(chart: Chart | null | undefined): void;
@@ -0,0 +1 @@
1
+ export { serializeChartPayload } from './parse';
@@ -0,0 +1,3 @@
1
+ import { ChartTheme } from './types';
2
+ /** Resolve theme from --tmr-* CSS variables + optional host override. */
3
+ export declare function resolveChartTheme(host: HTMLElement | null, override?: ChartTheme | null): Required<Pick<ChartTheme, 'backgroundColor' | 'textColor' | 'gridColor' | 'borderColor' | 'fontFamily' | 'palette'>>;