tiptap-markdown-react 1.2.0 → 1.3.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.
@@ -423,18 +423,283 @@ ${fenceBody}
423
423
  }
424
424
  return out;
425
425
  }
426
- const editorHost = "_editorHost_1v6pj_6";
427
- const findBarAnchor = "_findBarAnchor_1v6pj_13";
428
- const findBar = "_findBar_1v6pj_13";
429
- const editorScroll = "_editorScroll_1v6pj_5";
430
- const withBlockDrag = "_withBlockDrag_1v6pj_33";
431
- const withToc = "_withToc_1v6pj_38";
432
- const editorContent = "_editorContent_1v6pj_50";
433
- const editorStatus = "_editorStatus_1v6pj_1944";
434
- const shortcutFab = "_shortcutFab_1v6pj_1956";
435
- const charCount = "_charCount_1v6pj_1962";
436
- const charCountSep = "_charCountSep_1v6pj_1984";
437
- const shortcutFabHosted = "_shortcutFabHosted_1v6pj_2018";
426
+ const CROP_MIN = 0.05;
427
+ const CROP_FULL_EPS = 5e-3;
428
+ const IMAGE_ALIGN_VALUES = /* @__PURE__ */ new Set(["left", "center", "right"]);
429
+ function normalizeImageAlign(value) {
430
+ return typeof value === "string" && IMAGE_ALIGN_VALUES.has(value) ? value : null;
431
+ }
432
+ const imageAlignAttribute = {
433
+ default: null,
434
+ parseHTML: (element) => normalizeImageAlign(element.getAttribute("data-align")),
435
+ renderHTML: (attributes) => {
436
+ const align = normalizeImageAlign(attributes.align);
437
+ return align ? { "data-align": align } : {};
438
+ }
439
+ };
440
+ function round4(n) {
441
+ return Math.round(n * 1e4) / 1e4;
442
+ }
443
+ function parseCropParts(value) {
444
+ if (typeof value === "string") {
445
+ const parts = value.split(",").map((part) => Number(part.trim()));
446
+ if (parts.length !== 4 || parts.some((n) => !Number.isFinite(n))) return null;
447
+ return { x: parts[0] ?? 0, y: parts[1] ?? 0, w: parts[2] ?? 0, h: parts[3] ?? 0 };
448
+ }
449
+ if (value && typeof value === "object") {
450
+ const record = value;
451
+ const x = Number(record.x);
452
+ const y = Number(record.y);
453
+ const w = Number(record.w);
454
+ const h = Number(record.h);
455
+ if (![x, y, w, h].every((n) => Number.isFinite(n))) return null;
456
+ return { x, y, w, h };
457
+ }
458
+ return null;
459
+ }
460
+ function normalizeImageCrop(value) {
461
+ const parsed = parseCropParts(value);
462
+ if (!parsed || parsed.w <= 0 || parsed.h <= 0) return null;
463
+ const w = Math.min(1, Math.max(CROP_MIN, parsed.w));
464
+ const h = Math.min(1, Math.max(CROP_MIN, parsed.h));
465
+ const x = Math.min(Math.max(0, parsed.x), 1 - w);
466
+ const y = Math.min(Math.max(0, parsed.y), 1 - h);
467
+ const crop = {
468
+ x: round4(x),
469
+ y: round4(y),
470
+ w: round4(Math.min(w, 1 - x)),
471
+ h: round4(Math.min(h, 1 - y))
472
+ };
473
+ if (crop.w < CROP_MIN || crop.h < CROP_MIN) return null;
474
+ if (crop.x <= CROP_FULL_EPS && crop.y <= CROP_FULL_EPS && crop.x + crop.w >= 1 - CROP_FULL_EPS && crop.y + crop.h >= 1 - CROP_FULL_EPS) {
475
+ return null;
476
+ }
477
+ return crop;
478
+ }
479
+ function formatImageCrop(crop) {
480
+ return [crop.x, crop.y, crop.w, crop.h].map((n) => n.toFixed(4)).join(",");
481
+ }
482
+ function cropPercent(value) {
483
+ const rounded = Math.round(value * 1e4) / 1e4;
484
+ return `${rounded}%`;
485
+ }
486
+ function cropImageBox(crop) {
487
+ return {
488
+ width: cropPercent(100 / crop.w),
489
+ height: cropPercent(100 / crop.h),
490
+ left: cropPercent(-100 * crop.x / crop.w),
491
+ top: cropPercent(-100 * crop.y / crop.h)
492
+ };
493
+ }
494
+ function cropImageCss(crop) {
495
+ const box = cropImageBox(crop);
496
+ return `position:absolute;max-width:none;margin:0;pointer-events:none;width:${box.width};height:${box.height};left:${box.left};top:${box.top}`;
497
+ }
498
+ function cropFrameCss(width, height) {
499
+ return `width:${width}px;aspect-ratio:${width}/${height}`;
500
+ }
501
+ const imageCropAttribute = {
502
+ default: null,
503
+ parseHTML: (element) => normalizeImageCrop(element.getAttribute("data-crop")),
504
+ renderHTML: (attributes) => {
505
+ const crop = normalizeImageCrop(attributes.crop);
506
+ return crop ? { "data-crop": formatImageCrop(crop) } : {};
507
+ }
508
+ };
509
+ const imageCaptionAttribute = {
510
+ default: null,
511
+ parseHTML: (element) => {
512
+ var _a;
513
+ const figure = element.closest("figure");
514
+ return normalizeImageCaption(
515
+ ((_a = figure == null ? void 0 : figure.querySelector("figcaption")) == null ? void 0 : _a.textContent) ?? null
516
+ );
517
+ },
518
+ renderHTML: () => ({})
519
+ };
520
+ function imageElementSpec(output) {
521
+ const crop = normalizeImageCrop(output.attrs.crop);
522
+ if (!crop) return ["img", output.htmlAttributes];
523
+ const width = toPixel(
524
+ output.attrs.width == null ? null : String(output.attrs.width)
525
+ );
526
+ const height = toPixel(
527
+ output.attrs.height == null ? null : String(output.attrs.height)
528
+ );
529
+ return [
530
+ "span",
531
+ {
532
+ class: "tmr-image-crop",
533
+ ...width != null && height != null ? { style: cropFrameCss(width, height) } : null
534
+ },
535
+ ["img", { ...output.htmlAttributes, style: cropImageCss(crop) }]
536
+ ];
537
+ }
538
+ function imageNodeHtml(output) {
539
+ const caption = normalizeImageCaption(output.attrs.caption);
540
+ const image = imageElementSpec(output);
541
+ if (!caption) return image;
542
+ return [
543
+ "figure",
544
+ { class: "tmr-image-figure" },
545
+ image,
546
+ ["figcaption", { class: "tmr-image-caption" }, caption]
547
+ ];
548
+ }
549
+ function imageFigureParseRule() {
550
+ return {
551
+ tag: "figure",
552
+ getAttrs: (element) => {
553
+ const img = element.querySelector("img");
554
+ if (!img) return false;
555
+ const caption = element.querySelector("figcaption");
556
+ return {
557
+ src: img.getAttribute("src"),
558
+ alt: img.getAttribute("alt"),
559
+ title: img.getAttribute("title"),
560
+ width: toPixel(img.getAttribute("width")),
561
+ height: toPixel(img.getAttribute("height")),
562
+ align: normalizeImageAlign(img.getAttribute("data-align")),
563
+ crop: normalizeImageCrop(img.getAttribute("data-crop")),
564
+ caption: normalizeImageCaption((caption == null ? void 0 : caption.textContent) ?? null)
565
+ };
566
+ }
567
+ };
568
+ }
569
+ function attrValue(attrs, name) {
570
+ const pattern = new RegExp(
571
+ `(?:^|\\s)${name}\\s*=\\s*(?:"([^"]*)"|'([^']*)'|([^\\s"'=<>]+))`,
572
+ "i"
573
+ );
574
+ const match = pattern.exec(attrs);
575
+ if (!match) return null;
576
+ return match[1] ?? match[2] ?? match[3] ?? null;
577
+ }
578
+ const ENTITIES = {
579
+ "&amp;": "&",
580
+ "&lt;": "<",
581
+ "&gt;": ">",
582
+ "&quot;": '"',
583
+ "&#39;": "'"
584
+ };
585
+ function decodeEntities(text) {
586
+ return text.replace(/&(?:amp|lt|gt|quot|#39);/g, (m) => ENTITIES[m] ?? m);
587
+ }
588
+ function encodeAttr(value) {
589
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
590
+ }
591
+ function encodeText(value) {
592
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
593
+ }
594
+ function normalizeImageCaption(value) {
595
+ if (typeof value !== "string") return null;
596
+ const text = value.trim();
597
+ return text === "" ? null : text;
598
+ }
599
+ function toPixel(value) {
600
+ if (value == null) return null;
601
+ const n = Number(value);
602
+ return Number.isFinite(n) && n > 0 ? Math.round(n) : null;
603
+ }
604
+ function imageRenderMarkdown(node) {
605
+ const attrs = node.attrs ?? {};
606
+ const src = typeof attrs.src === "string" ? attrs.src : "";
607
+ const alt = typeof attrs.alt === "string" ? attrs.alt : "";
608
+ const title = typeof attrs.title === "string" ? attrs.title : "";
609
+ const width = toPixel(attrs.width == null ? null : String(attrs.width));
610
+ const height = toPixel(attrs.height == null ? null : String(attrs.height));
611
+ const align = normalizeImageAlign(attrs.align);
612
+ const crop = normalizeImageCrop(attrs.crop);
613
+ const caption = normalizeImageCaption(attrs.caption);
614
+ if (width == null && height == null && !align && !crop && !caption) {
615
+ return title ? `![${alt}](${src} "${title}")` : `![${alt}](${src})`;
616
+ }
617
+ const parts = [`src="${encodeAttr(src)}"`];
618
+ if (alt) parts.push(`alt="${encodeAttr(alt)}"`);
619
+ if (title) parts.push(`title="${encodeAttr(title)}"`);
620
+ if (width != null) parts.push(`width="${width}"`);
621
+ if (height != null) parts.push(`height="${height}"`);
622
+ if (align) parts.push(`data-align="${align}"`);
623
+ if (crop) parts.push(`data-crop="${formatImageCrop(crop)}"`);
624
+ const imgTag = `<img ${parts.join(" ")}>`;
625
+ if (!caption) return imgTag;
626
+ return `<figure>
627
+ ${imgTag}
628
+ <figcaption>${encodeText(caption)}</figcaption>
629
+ </figure>`;
630
+ }
631
+ function imageTokenFromImgTag(raw, attrsString, caption = null) {
632
+ const srcValue = attrValue(attrsString, "src");
633
+ if (!srcValue) return void 0;
634
+ const alt = attrValue(attrsString, "alt");
635
+ const title = attrValue(attrsString, "title");
636
+ return {
637
+ type: "image",
638
+ raw,
639
+ src: decodeEntities(srcValue),
640
+ alt: alt == null ? null : decodeEntities(alt),
641
+ title: title == null ? null : decodeEntities(title),
642
+ width: toPixel(attrValue(attrsString, "width")),
643
+ height: toPixel(attrValue(attrsString, "height")),
644
+ align: normalizeImageAlign(attrValue(attrsString, "data-align")),
645
+ crop: normalizeImageCrop(attrValue(attrsString, "data-crop")),
646
+ caption
647
+ };
648
+ }
649
+ const imageMarkdownTokenizer = {
650
+ name: "image",
651
+ level: "block",
652
+ start(src) {
653
+ const figure = /<figure[\s>]/i.exec(src);
654
+ const img = /<img[\s>]/i.exec(src);
655
+ const indexes = [figure == null ? void 0 : figure.index, img == null ? void 0 : img.index].filter(
656
+ (index) => index != null
657
+ );
658
+ return indexes.length ? Math.min(...indexes) : -1;
659
+ },
660
+ tokenize(src) {
661
+ const figure = /^<figure\s*>([\s\S]*?)<\/figure\s*>/i.exec(src);
662
+ if (figure) {
663
+ const inner = figure[1] ?? "";
664
+ const img = /<img\s+([^>]*?)\s*\/?>/.exec(inner);
665
+ if (!img) return void 0;
666
+ const cap = /<figcaption[^>]*>([\s\S]*?)<\/figcaption\s*>/i.exec(inner);
667
+ const caption = cap ? normalizeImageCaption(
668
+ decodeEntities((cap[1] ?? "").replace(/<[^>]*>/g, ""))
669
+ ) : null;
670
+ return imageTokenFromImgTag(figure[0], img[1] ?? "", caption);
671
+ }
672
+ const match = /^<img\s+([^>]*?)\s*\/?>/.exec(src);
673
+ if (!match) return void 0;
674
+ return imageTokenFromImgTag(match[0], match[1] ?? "");
675
+ }
676
+ };
677
+ function imageParseMarkdown(token, helpers) {
678
+ const src = typeof token.src === "string" ? token.src : token.href ?? null;
679
+ const alt = typeof token.alt === "string" ? token.alt : token.text ?? null;
680
+ return helpers.createNode("image", {
681
+ src,
682
+ alt,
683
+ title: token.title ?? null,
684
+ width: token.width ?? null,
685
+ height: token.height ?? null,
686
+ align: token.align ?? null,
687
+ crop: normalizeImageCrop(token.crop),
688
+ caption: normalizeImageCaption(token.caption)
689
+ });
690
+ }
691
+ const editorHost = "_editorHost_1rvl4_6";
692
+ const findBarAnchor = "_findBarAnchor_1rvl4_13";
693
+ const findBar = "_findBar_1rvl4_13";
694
+ const editorScroll = "_editorScroll_1rvl4_5";
695
+ const withBlockDrag = "_withBlockDrag_1rvl4_33";
696
+ const withToc = "_withToc_1rvl4_38";
697
+ const editorContent = "_editorContent_1rvl4_50";
698
+ const editorStatus = "_editorStatus_1rvl4_1990";
699
+ const shortcutFab = "_shortcutFab_1rvl4_2002";
700
+ const charCount = "_charCount_1rvl4_2008";
701
+ const charCountSep = "_charCountSep_1rvl4_2030";
702
+ const shortcutFabHosted = "_shortcutFabHosted_1rvl4_2064";
438
703
  const styles = {
439
704
  editorHost,
440
705
  findBarAnchor,
@@ -463,10 +728,22 @@ export {
463
728
  ReportContent as R,
464
729
  applyCitationSources as a,
465
730
  extractFootnoteSources as b,
466
- styles as c,
731
+ imageParseMarkdown as c,
732
+ imageRenderMarkdown as d,
467
733
  enrichMarkdownCitations as e,
734
+ imageNodeHtml as f,
735
+ imageFigureParseRule as g,
736
+ imageCropAttribute as h,
737
+ imageMarkdownTokenizer as i,
738
+ imageCaptionAttribute as j,
739
+ imageAlignAttribute as k,
740
+ normalizeImageCrop as l,
741
+ cropImageBox as m,
468
742
  normalizeChartMarkdown as n,
743
+ styles as o,
469
744
  parseChartFenceBody as p,
745
+ normalizeImageAlign as q,
746
+ normalizeImageCaption as r,
470
747
  serializeChartPayload as s
471
748
  };
472
- //# sourceMappingURL=ReportContent-B7okwVW0.js.map
749
+ //# sourceMappingURL=ReportContent-Cb67MCs9.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReportContent-Cb67MCs9.js","sources":["../src/citationUtils.ts","../src/chart/columnMatching.ts","../src/chart/types.ts","../src/chart/parse.ts","../src/imageMarkdown.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/** 单条脚注定义行:行首 0–3 空格 + `[^数字]:`。 */\nconst FOOTNOTE_DEF_RE = /^ {0,3}\\[\\^(\\d+)\\]:[ \\t]?(.*)$/;\n\n/** 内联链接:`[文本](url)`。URL 支持 `<...>` 包裹(可含空格)或一层配对括号(维基百科式)。 */\nconst MD_LINK_RE =\n /\\[([^\\]]*)\\]\\(\\s*(?:<([^<>]*)>|((?:[^()\\s]|\\([^()]*\\))+)(?:[ \\t]+\"[^\"]*\")?)\\s*\\)/;\nconst MD_LINK_RE_G = new RegExp(MD_LINK_RE.source, 'g');\n\n/** 把定义内容压成纯文本(链接只留文本),供 excerpt 与 title 兜底。 */\nfunction flattenFootnoteContent(content: string): string {\n return content\n .replace(\n MD_LINK_RE_G,\n (_match, text: string, angleUrl: string, plainUrl: string) =>\n text || angleUrl || plainUrl || '',\n )\n .trim();\n}\n\nexport interface ExtractedFootnotes {\n /** 剥离定义行后的正文(首尾与多余空行已收敛)。 */\n markdown: string;\n /** 解析出的来源列表,同一 label 重复定义取首次。 */\n sources: SourceRef[];\n}\n\n/**\n * 把 markdown 中的 GFM 脚注定义行(`[^n]: …`)抽成 {@link SourceRef} 并从正文剥离。\n *\n * 与 {@link enrichMarkdownCitations} / {@link applyCitationSources} 同属脚注一族:\n * 它们消费 `sources` 处理 `[^n]` 标记这一半,本函数生产 `sources`、处理定义行\n * 另一半——不剥离,定义行会以普通段落渲染;不抽取,标记没有 url/title 可补。\n * 典型用法(纯字符串变换,流式每帧重算也便宜):\n *\n * ```ts\n * const { markdown: body, sources } = extractFootnoteSources(raw);\n * // <MarkdownPreview markdown={body} sources={sources} … />\n * ```\n *\n * 规则与边界:\n * - label 仅支持数字,与 enrich / CitationRef tokenizer 的 `\\[\\^(\\d+)\\]` 一致;\n * 非数字定义行不识别、原样留在正文;\n * - 围栏代码块(``` / ~~~,判定与 {@link stabilizeMarkdown} 一致)内的定义行跳过;\n * 缩进代码块不识别(已知限制);\n * - 续行:紧跟定义的缩进(≥4 空格或 Tab)非空行并入内容,空行结束定义;\n * - 内容中第一个内联链接取作 url、链接文本作 title;excerpt 取纯文本全文;\n * 无链接时 title 取纯文本首行;\n * - 流式半行(如 `[^1]: [原文](htt`)按 best-effort 解析,下一帧自愈。\n */\nexport function extractFootnoteSources(markdown: string): ExtractedFootnotes {\n if (!markdown || !markdown.includes('[^')) {\n return { markdown, sources: [] };\n }\n\n const lines = markdown.split('\\n');\n const bodyLines: string[] = [];\n const sources: SourceRef[] = [];\n const seen = new Set<string>();\n let removed = false;\n\n let inFence = false;\n let fenceChar = '`';\n let fenceLen = 3;\n\n for (let i = 0; i < lines.length; i += 1) {\n const line = lines[i];\n\n const fence = line.match(/^(`{3,}|~{3,})/);\n if (fence) {\n const char = fence[1][0];\n if (!inFence) {\n inFence = true;\n fenceChar = char;\n fenceLen = fence[1].length;\n } else if (\n char === fenceChar &&\n fence[1].length >= fenceLen &&\n /^[`~]+\\s*$/.test(line)\n ) {\n inFence = false;\n }\n bodyLines.push(line);\n continue;\n }\n if (inFence) {\n bodyLines.push(line);\n continue;\n }\n\n const def = FOOTNOTE_DEF_RE.exec(line);\n if (!def) {\n bodyLines.push(line);\n continue;\n }\n removed = true;\n\n const parts = [def[2]];\n let j = i + 1;\n while (\n j < lines.length &&\n lines[j].trim() &&\n /^(?: {4,}|\\t)/.test(lines[j])\n ) {\n parts.push(lines[j].trim());\n j += 1;\n }\n i = j - 1;\n\n const index = def[1];\n if (seen.has(index)) continue;\n seen.add(index);\n\n const content = parts.join('\\n');\n const link = MD_LINK_RE.exec(content);\n const source: SourceRef = { index };\n if (link) {\n const url = link[2] ?? link[3];\n if (url) source.url = url;\n const text = link[1]?.trim();\n if (text) source.title = text;\n }\n const plain = flattenFootnoteContent(content);\n if (plain) {\n source.excerpt = plain;\n if (!source.title) source.title = plain.split('\\n')[0];\n }\n sources.push(source);\n }\n\n if (!removed) {\n return { markdown, sources: [] };\n }\n\n return {\n markdown: bodyLines\n .join('\\n')\n .replace(/\\n{3,}/g, '\\n\\n')\n .replace(/^\\n+/, '')\n .replace(/\\s+$/, ''),\n sources,\n };\n}\n","/**\n * Loose header ↔ config field matching for chart data tables.\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 (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 + `raw`(`src` 里被消费掉的**精确前缀**,调用方按 `raw.length`\n * 跳过这一段), 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 afterCommentRaw = trimmedStart.slice(endComment + 3);\n // 注释与表格之间隔空行时折成一个 \\n 再解析(表格内容本身不受影响)。\n const afterComment = afterCommentRaw.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 // 表格起点要在**原文**里定位:折过的 afterComment 比原文短,拿它当基准会让\n // raw 短掉那几个空行字符,调用方按 raw.length 切 src 就会多吃掉后一个字符\n // —— 表格是最后一块时,被吃掉的正是表格收尾的 `|`。\n const tableStartInSrc = afterCommentRaw.search(/\\|/);\n const raw = src.slice(\n 0,\n offset + commentBlock.length + tableStartInSrc + table.rawLength,\n );\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 {\n JSONContent,\n MarkdownParseHelpers,\n MarkdownToken,\n MarkdownTokenizer,\n} from '@tiptap/core';\n// renderHTML 期望的是 prosemirror 的 DOMOutputSpec(支持多子节点数组),\n// 不是 tiptap 那个单链递归的 DOMOutputSpecArray\nimport type { DOMOutputSpec } from '@tiptap/pm/model';\n\n/**\n * 图片节点的 markdown 往返(客户端 / 预览 / server 三端共用)。\n *\n * 尺寸(拖拽缩放的产物,整数像素)、对齐(`data-align`)、裁剪(`data-crop`)与描述\n * (图注)在 markdown 里都没有原生语法,HTML 是唯一能带上它们的 CommonMark 合法形式:\n * `<img src=\"…\" alt=\"…\" width=\"300\" data-align=\"center\" data-crop=\"0.1000,0.0000,0.5000,1.0000\">`,\n * 带描述时再包一层 `<figure><img …><figcaption>…</figcaption></figure>`(HTML 标准语义,\n * GitHub 同样认)。因此:\n *\n * - 序列化:**只有带尺寸 / 对齐 / 裁剪 / 描述的图**输出 HTML 形式;没动过的图保持标准\n * `![alt](src \"title\")`,markdown 不为没用过的功能付可读性代价。\n * - 裁剪不改 `src`(原图还在)。`data-crop` 是相对原图的 `x,y,w,h`(0–1,四位小数);\n * HTML 里再用一层 `.tmr-image-crop` 把可见区域裁出来,编辑态 / 预览 / SSR 同一套。\n * - 解析:自建 block 级 tokenizer 认领 `<img …>` 与 `<figure>…</figure>`(一个扩展\n * 只能挂一个 tokenizer)。不认领的话,客户端靠 `window.DOMParser` 隐式还原、\n * server 端没有 window 会把它转义成字面文本(与 sup/sub 当初踩的是同一条边界)。\n *\n * 对齐的属性名用 `data-align`(attrs 里叫 `align`):与官方 image-align-button\n * 约定的属性名一致,宿主已有的 `img[data-align]` 样式可以直接吃。\n */\n\n/** 图片尺寸(整数像素);无尺寸为 null。 */\nexport interface ImageSizeAttrs {\n width?: number | null;\n height?: number | null;\n}\n\n/** 图片对齐(HTML 属性 `data-align`,与官方 image-align-button 同名)。 */\nexport type ImageAlign = 'left' | 'center' | 'right';\n\n/**\n * 裁剪矩形,相对原图宽高的比例(0–1)。\n * `x` / `y` 是左上角,`w` / `h` 是宽高。整张图(或几乎整张)归一成 null,不进 markdown。\n */\nexport interface ImageCrop {\n x: number;\n y: number;\n w: number;\n h: number;\n}\n\n/** 裁剪框最短边。再小既难点中,显示也会糊成一条线。 */\nconst CROP_MIN = 0.05;\n/** 贴着整张图的边(四边都在这个误差内)视为没裁,避免无意义的 data-crop。 */\nconst CROP_FULL_EPS = 0.005;\n\nconst IMAGE_ALIGN_VALUES = new Set<string>(['left', 'center', 'right']);\n\n/** 归一化对齐值:只认 left / center / right,其余(含空、任意脏值)为 null。 */\nexport function normalizeImageAlign(value: unknown): ImageAlign | null {\n return typeof value === 'string' && IMAGE_ALIGN_VALUES.has(value)\n ? (value as ImageAlign)\n : null;\n}\n\n/**\n * `align` 属性的共享配置(编辑器增强版与 pureImage 都挂它):attrs 名语义化、\n * HTML 渲染成 `data-align`。两份扩展都必须挂——attrs 只声明在扩展上,\n * markdown 解析出的 `align` 才有地方落。\n */\nexport const imageAlignAttribute = {\n default: null,\n parseHTML: (element: HTMLElement) =>\n normalizeImageAlign(element.getAttribute('data-align')),\n renderHTML: (attributes: Record<string, unknown>) => {\n const align = normalizeImageAlign(attributes.align);\n return align ? { 'data-align': align } : {};\n },\n};\n\n/**\n * `caption`(图注)属性的共享配置:不进 img 标签(是节点级 renderHTML 包的\n * `<figure>/<figcaption>` 结构),解析侧从 figure 里读。\n */\nfunction round4(n: number): number {\n return Math.round(n * 10000) / 10000;\n}\n\nfunction parseCropParts(\n value: unknown,\n): { x: number; y: number; w: number; h: number } | null {\n if (typeof value === 'string') {\n const parts = value.split(',').map((part) => Number(part.trim()));\n if (parts.length !== 4 || parts.some((n) => !Number.isFinite(n))) return null;\n return { x: parts[0] ?? 0, y: parts[1] ?? 0, w: parts[2] ?? 0, h: parts[3] ?? 0 };\n }\n if (value && typeof value === 'object') {\n const record = value as Record<string, unknown>;\n const x = Number(record.x);\n const y = Number(record.y);\n const w = Number(record.w);\n const h = Number(record.h);\n if (![x, y, w, h].every((n) => Number.isFinite(n))) return null;\n return { x, y, w, h };\n }\n return null;\n}\n\n/**\n * 归一化裁剪:只认落在原图内、边长够得着的矩形。\n * 整张图、空值、脏字符串(`data-crop=\"nope\"`)都是 null。\n */\nexport function normalizeImageCrop(value: unknown): ImageCrop | null {\n const parsed = parseCropParts(value);\n if (!parsed || parsed.w <= 0 || parsed.h <= 0) return null;\n const w = Math.min(1, Math.max(CROP_MIN, parsed.w));\n const h = Math.min(1, Math.max(CROP_MIN, parsed.h));\n const x = Math.min(Math.max(0, parsed.x), 1 - w);\n const y = Math.min(Math.max(0, parsed.y), 1 - h);\n const crop: ImageCrop = {\n x: round4(x),\n y: round4(y),\n w: round4(Math.min(w, 1 - x)),\n h: round4(Math.min(h, 1 - y)),\n };\n if (crop.w < CROP_MIN || crop.h < CROP_MIN) return null;\n if (\n crop.x <= CROP_FULL_EPS &&\n crop.y <= CROP_FULL_EPS &&\n crop.x + crop.w >= 1 - CROP_FULL_EPS &&\n crop.y + crop.h >= 1 - CROP_FULL_EPS\n ) {\n return null;\n }\n return crop;\n}\n\n/** `data-crop` 的规范写法(四位小数,方便往返幂等)。 */\nexport function formatImageCrop(crop: ImageCrop): string {\n return [crop.x, crop.y, crop.w, crop.h].map((n) => n.toFixed(4)).join(',');\n}\n\nfunction cropPercent(value: number): string {\n const rounded = Math.round(value * 10000) / 10000;\n return `${rounded}%`;\n}\n\n/** img 在裁剪框里的百分比定位(框是可见区域,img 铺开整张原图)。 */\nexport function cropImageBox(crop: ImageCrop): {\n width: string;\n height: string;\n left: string;\n top: string;\n} {\n return {\n width: cropPercent(100 / crop.w),\n height: cropPercent(100 / crop.h),\n left: cropPercent((-100 * crop.x) / crop.w),\n top: cropPercent((-100 * crop.y) / crop.h),\n };\n}\n\n/** 静态 HTML(预览 / SSR)里 img 的内联样式,与编辑态 frame 里的 img 同一套数字。 */\nexport function cropImageCss(crop: ImageCrop): string {\n const box = cropImageBox(crop);\n return `position:absolute;max-width:none;margin:0;pointer-events:none;width:${box.width};height:${box.height};left:${box.left};top:${box.top}`;\n}\n\n/** 裁剪框的布局尺寸:宽是可见像素,高交给 aspect-ratio,窄屏 max-width:100% 时还能保持比例。 */\nexport function cropFrameCss(width: number, height: number): string {\n return `width:${width}px;aspect-ratio:${width}/${height}`;\n}\n\n/**\n * `crop` 属性的共享配置:attrs 里是 {@link ImageCrop},HTML 渲染成 `data-crop`。\n * 视觉裁剪不在这个属性上(img 的 style / 外包 frame 由 imageNodeHtml 与编辑态 NodeView 写)。\n */\nexport const imageCropAttribute = {\n default: null,\n parseHTML: (element: HTMLElement) =>\n normalizeImageCrop(element.getAttribute('data-crop')),\n renderHTML: (attributes: Record<string, unknown>) => {\n const crop = normalizeImageCrop(attributes.crop);\n return crop ? { 'data-crop': formatImageCrop(crop) } : {};\n },\n};\n\nexport const imageCaptionAttribute = {\n default: null,\n parseHTML: (element: HTMLElement) => {\n // 匹配到 img 时从它所在的 figure 里取(figure 规则通常已用 getAttrs 给全,\n // 这里是 HTML 粘贴的兜底)\n const figure = element.closest('figure');\n return normalizeImageCaption(\n figure?.querySelector('figcaption')?.textContent ?? null,\n );\n },\n renderHTML: () => ({}),\n};\n\n/** 有裁剪时把 img 包进 `.tmr-image-crop`(可见区域 = width/height,img 按比例铺开)。 */\nfunction imageElementSpec(output: {\n attrs: Record<string, unknown>;\n htmlAttributes: Record<string, unknown>;\n}): DOMOutputSpec {\n const crop = normalizeImageCrop(output.attrs.crop);\n if (!crop) return ['img', output.htmlAttributes];\n const width = toPixel(\n output.attrs.width == null ? null : String(output.attrs.width),\n );\n const height = toPixel(\n output.attrs.height == null ? null : String(output.attrs.height),\n );\n return [\n 'span',\n {\n class: 'tmr-image-crop',\n ...(width != null && height != null\n ? { style: cropFrameCss(width, height) }\n : null),\n },\n ['img', { ...output.htmlAttributes, style: cropImageCss(crop) }],\n ];\n}\n\n/**\n * 节点级 HTML 渲染:有描述时输出 `<figure><img …><figcaption>…</figcaption></figure>`\n * (HTML 标准语义),否则维持官方单 `<img>` 结构。有裁剪时 img 外包一层裁剪框。\n * 两份扩展共用。\n */\nexport function imageNodeHtml(output: {\n attrs: Record<string, unknown>;\n htmlAttributes: Record<string, unknown>;\n}): DOMOutputSpec {\n const caption = normalizeImageCaption(output.attrs.caption);\n const image = imageElementSpec(output);\n if (!caption) return image;\n // 多子节点直接展开跟在元素后面(嵌套数组会被 prosemirror 的 renderSpec 判为非法)\n return [\n 'figure',\n { class: 'tmr-image-figure' },\n image,\n ['figcaption', { class: 'tmr-image-caption' }, caption],\n ];\n}\n\n/**\n * `<figure><img …><figcaption>…</figcaption></figure>` 的 parse 规则(两份扩展共用,\n * 要排在 `img[src]` 规则之前)。读子 img 的全部属性 + figcaption 的纯文本描述。\n */\nexport function imageFigureParseRule(): {\n tag: string;\n getAttrs: (element: HTMLElement) => Record<string, unknown> | false;\n} {\n return {\n tag: 'figure',\n getAttrs: (element: HTMLElement) => {\n const img = element.querySelector('img');\n if (!img) return false;\n const caption = element.querySelector('figcaption');\n return {\n src: img.getAttribute('src'),\n alt: img.getAttribute('alt'),\n title: img.getAttribute('title'),\n width: toPixel(img.getAttribute('width')),\n height: toPixel(img.getAttribute('height')),\n align: normalizeImageAlign(img.getAttribute('data-align')),\n crop: normalizeImageCrop(img.getAttribute('data-crop')),\n caption: normalizeImageCaption(caption?.textContent ?? null),\n };\n },\n };\n}\n\n/** 从 HTML 属性串里取一个属性值;双引号 / 单引号 / 无引号都认。 */\nfunction attrValue(attrs: string, name: string): string | null {\n const pattern = new RegExp(\n `(?:^|\\\\s)${name}\\\\s*=\\\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\\\s\"'=<>]+))`,\n 'i',\n );\n const match = pattern.exec(attrs);\n if (!match) return null;\n return match[1] ?? match[2] ?? match[3] ?? null;\n}\n\n/** 常见实体还原(`&amp;` / `&quot;` 等),与序列化侧的转义对称。 */\nconst ENTITIES: Record<string, string> = {\n '&amp;': '&',\n '&lt;': '<',\n '&gt;': '>',\n '&quot;': '\"',\n '&#39;': \"'\",\n};\n\nfunction decodeEntities(text: string): string {\n return text.replace(/&(?:amp|lt|gt|quot|#39);/g, (m) => ENTITIES[m] ?? m);\n}\n\n/** 属性值最小转义(`&` 与 `\"` 必须转,其余顺手,保证与 decodeEntities 对称)。 */\nfunction encodeAttr(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/** 文本节点最小转义(属性无关的 `\"` 不用转)。 */\nfunction encodeText(value: string): string {\n return value\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;');\n}\n\n/** 图片描述(图注)归一化:去掉首尾空白,空串归 null。 */\nexport function normalizeImageCaption(value: unknown): string | null {\n if (typeof value !== 'string') return null;\n const text = value.trim();\n return text === '' ? null : text;\n}\n\n/** HTML 宽高只认纯数字(`50%` / `auto` 这类留给 CSS,不当作像素尺寸)。 */\nfunction toPixel(value: string | null): number | null {\n if (value == null) return null;\n const n = Number(value);\n return Number.isFinite(n) && n > 0 ? Math.round(n) : null;\n}\n\n/**\n * 图片的 markdown 序列化:带尺寸 / 对齐 / 裁剪 / 描述的输出 HTML 形式,否则标准语法。\n * 描述(图注)用 `<figure>` + `<figcaption>` 包裹(HTML 标准语义,GitHub 同样认);\n * 覆盖 @tiptap/extension-image 的内置实现(它只输出 src/alt/title,其余都会丢)。\n */\nexport function imageRenderMarkdown(node: {\n attrs?: Record<string, unknown>;\n}): string {\n const attrs = node.attrs ?? {};\n const src = typeof attrs.src === 'string' ? attrs.src : '';\n const alt = typeof attrs.alt === 'string' ? attrs.alt : '';\n const title = typeof attrs.title === 'string' ? attrs.title : '';\n const width = toPixel(attrs.width == null ? null : String(attrs.width));\n const height = toPixel(attrs.height == null ? null : String(attrs.height));\n const align = normalizeImageAlign(attrs.align);\n const crop = normalizeImageCrop(attrs.crop);\n const caption = normalizeImageCaption(attrs.caption);\n\n if (width == null && height == null && !align && !crop && !caption) {\n return title ? `![${alt}](${src} \"${title}\")` : `![${alt}](${src})`;\n }\n\n const parts = [`src=\"${encodeAttr(src)}\"`];\n if (alt) parts.push(`alt=\"${encodeAttr(alt)}\"`);\n if (title) parts.push(`title=\"${encodeAttr(title)}\"`);\n if (width != null) parts.push(`width=\"${width}\"`);\n if (height != null) parts.push(`height=\"${height}\"`);\n if (align) parts.push(`data-align=\"${align}\"`);\n if (crop) parts.push(`data-crop=\"${formatImageCrop(crop)}\"`);\n const imgTag = `<img ${parts.join(' ')}>`;\n\n if (!caption) return imgTag;\n return `<figure>\\n${imgTag}\\n<figcaption>${encodeText(caption)}</figcaption>\\n</figure>`;\n}\n\n/** 从 `<img …>` 的属性串里抽一个 token 需要的一切(figure / 裸 img 两种形态共用)。 */\nfunction imageTokenFromImgTag(\n raw: string,\n attrsString: string,\n caption: string | null = null,\n): MarkdownToken | undefined {\n const srcValue = attrValue(attrsString, 'src');\n if (!srcValue) return undefined;\n const alt = attrValue(attrsString, 'alt');\n const title = attrValue(attrsString, 'title');\n return {\n type: 'image',\n raw,\n src: decodeEntities(srcValue),\n alt: alt == null ? null : decodeEntities(alt),\n title: title == null ? null : decodeEntities(title),\n width: toPixel(attrValue(attrsString, 'width')),\n height: toPixel(attrValue(attrsString, 'height')),\n align: normalizeImageAlign(attrValue(attrsString, 'data-align')),\n crop: normalizeImageCrop(attrValue(attrsString, 'data-crop')),\n caption,\n };\n}\n\n/**\n * 认领独占一行的 `<img …>` 与 `<figure>…</figure>`(block 级 tokenizer,一个扩展\n * 只能挂一个 tokenizer,所以两种形态都归它管)。属性在 tokenize 里就抽好,\n * `parseMarkdown` 只做搬运——与 CitationRef 的 tokenizer 同一套写法。\n */\nexport const imageMarkdownTokenizer: MarkdownTokenizer = {\n name: 'image',\n level: 'block',\n start(src) {\n const figure = /<figure[\\s>]/i.exec(src);\n const img = /<img[\\s>]/i.exec(src);\n const indexes = [figure?.index, img?.index].filter(\n (index): index is number => index != null,\n );\n return indexes.length ? Math.min(...indexes) : -1;\n },\n tokenize(src) {\n // 带描述的形式(本库写出的:figure > img + figcaption)\n const figure = /^<figure\\s*>([\\s\\S]*?)<\\/figure\\s*>/i.exec(src);\n if (figure) {\n const inner = figure[1] ?? '';\n const img = /<img\\s+([^>]*?)\\s*\\/?>/.exec(inner);\n if (!img) return undefined;\n const cap = /<figcaption[^>]*>([\\s\\S]*?)<\\/figcaption\\s*>/i.exec(inner);\n // 外部 markdown 的 figcaption 可能带内部标签:只取纯文本\n const caption = cap\n ? normalizeImageCaption(\n decodeEntities((cap[1] ?? '').replace(/<[^>]*>/g, '')),\n )\n : null;\n return imageTokenFromImgTag(figure[0], img[1] ?? '', caption);\n }\n // 裸 `<img …>` 形态\n const match = /^<img\\s+([^>]*?)\\s*\\/?>/.exec(src);\n if (!match) return undefined;\n return imageTokenFromImgTag(match[0], match[1] ?? '');\n },\n};\n\n/** 把上面认领的 token 还原成 image 节点(含尺寸、对齐、裁剪与描述)。 */\nexport function imageParseMarkdown(\n token: MarkdownToken,\n helpers: MarkdownParseHelpers,\n): JSONContent {\n // 两种 token 形态:标准 `![alt](src \"title\")` 走 marked 的 image token\n // (字段是 href / text / title),本库 tokenizer 认领的 `<img …>` / `<figure>` 用 src / alt。\n const src = typeof token.src === 'string' ? token.src : token.href ?? null;\n const alt = typeof token.alt === 'string' ? token.alt : token.text ?? null;\n return helpers.createNode('image', {\n src,\n alt,\n title: token.title ?? null,\n width: token.width ?? null,\n height: token.height ?? null,\n align: token.align ?? null,\n crop: normalizeImageCrop(token.crop),\n caption: normalizeImageCaption(token.caption),\n });\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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"}