tiptap-markdown-react 0.8.5 → 0.8.7

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.
@@ -0,0 +1 @@
1
+ export {};
@@ -3,5 +3,8 @@ import { SourceRef } from './citationUtils';
3
3
  /**
4
4
  * 在光标处插入 markdown 片段(封装 contentType: 'markdown')。
5
5
  * 传入 `sources` 时会先 enrich `[^n]` → 带 url 的 citation-ref,再插入。
6
+ *
7
+ * 整段过不了 schema(嵌套 bold、块级图进 listItem 等)时按块重试,
8
+ * 单块仍挂则剥标记当段落。某一处脏输出不至于整次插入失败。
6
9
  */
7
10
  export declare function insertMarkdown(editor: Editor, markdown: string, sources?: SourceRef[]): void;
@@ -0,0 +1 @@
1
+ export {};
package/dist/labels.d.ts CHANGED
@@ -28,6 +28,16 @@ export interface ToolbarLabels {
28
28
  linkPrompt: string;
29
29
  image: string;
30
30
  imageUploadFailed: string;
31
+ /** Markdown 文件导入失败(与图片上传共用 onError 回调时,便于宿主区分文案)。 */
32
+ importMarkdownFailed: string;
33
+ /** 主栏导入按钮。默认只吃 .md,宿主传 onImportDocument 后可扩展格式。 */
34
+ importDocument: string;
35
+ /** 导入进行中的按钮 title。 */
36
+ importDocumentBusy: string;
37
+ /** 导入进行中的取消按钮。 */
38
+ importDocumentCancel: string;
39
+ /** 非 Markdown 文件导入失败(onError 的 source === 'import')。 */
40
+ importDocumentFailed: string;
31
41
  blockquote: string;
32
42
  bulletList: string;
33
43
  orderedList: string;
@@ -35,6 +45,7 @@ export interface ToolbarLabels {
35
45
  more: string;
36
46
  codeBlock: string;
37
47
  hr: string;
48
+ /** @deprecated 导入入口已移到主栏,改用 `importDocument`。 */
38
49
  importMarkdown: string;
39
50
  tableInsert: string;
40
51
  tableAddColumnBefore: string;
package/dist/server.js CHANGED
@@ -1,5 +1,5 @@
1
- import { k as stabilizeMarkdown, h as applyCitationSources, b as baseExtensions, p as pureCodeBlock, f as pureImage, C as CitationRef, d as makeTocGetId, n as renderMathElementHtml, j as extractToc } from "./extractToc-bgvj5gGC.js";
2
- import { c, R, i, e, l, a, m } from "./extractToc-bgvj5gGC.js";
1
+ import { k as stabilizeMarkdown, h as applyCitationSources, b as baseExtensions, p as pureCodeBlock, f as pureImage, C as CitationRef, d as makeTocGetId, n as renderMathElementHtml, j as extractToc } from "./extractToc-Dft8DJ1g.js";
2
+ import { c, R, i, e, l, a, m } from "./extractToc-Dft8DJ1g.js";
3
3
  import { TableOfContents, generateTocIds } from "@tiptap/extension-table-of-contents";
4
4
  import { MarkdownManager } from "@tiptap/markdown";
5
5
  import { renderToHTMLString } from "@tiptap/static-renderer";
package/dist/style.css CHANGED
@@ -533,7 +533,7 @@
533
533
  color: #22863a;
534
534
  }
535
535
  /* 顶部操作栏:横跨宽度、内容居中、细线灰图标。配色映射到可覆盖的 --tmr-* 变量。 */
536
- ._toolbar_16b09_2 {
536
+ ._toolbar_1ybhg_2 {
537
537
  --sb-accent: var(--tmr-accent, #ff6719);
538
538
  --sb-text: var(--tmr-text, #363737);
539
539
  --sb-muted: var(--tmr-toolbar-muted, #6b6b6b);
@@ -546,7 +546,7 @@
546
546
  z-index: 8;
547
547
  }
548
548
 
549
- ._inner_16b09_15 {
549
+ ._inner_1ybhg_15 {
550
550
  display: flex;
551
551
  align-items: center;
552
552
  justify-content: safe center;
@@ -558,7 +558,7 @@
558
558
  overflow-x: auto;
559
559
  }
560
560
 
561
- ._btn_16b09_27 {
561
+ ._btn_1ybhg_27 {
562
562
  display: inline-flex;
563
563
  align-items: center;
564
564
  justify-content: center;
@@ -576,28 +576,47 @@
576
576
  transition: background 0.15s, color 0.15s;
577
577
  }
578
578
 
579
- ._btn_16b09_27:hover:not(:disabled) {
579
+ ._btn_1ybhg_27:hover:not(:disabled) {
580
580
  background: #f3f3f3;
581
581
  color: var(--sb-text);
582
582
  }
583
583
 
584
- ._btnActive_16b09_50 {
584
+ ._btnActive_1ybhg_50 {
585
585
  color: var(--sb-accent);
586
586
  background: #fff3ec;
587
587
  }
588
588
 
589
- ._btnActive_16b09_50:hover {
589
+ ._btnActive_1ybhg_50:hover {
590
590
  color: var(--sb-accent);
591
591
  background: #ffe8da;
592
592
  }
593
593
 
594
- ._btn_16b09_27:disabled {
594
+ ._btn_1ybhg_27:disabled {
595
595
  opacity: 0.3;
596
596
  cursor: not-allowed;
597
597
  }
598
598
 
599
+ /* 导入中的取消:紧跟导入按钮的小号文字按钮 */
600
+ ._importCancel_1ybhg_66 {
601
+ height: 34px;
602
+ padding: 0 6px;
603
+ border: none;
604
+ border-radius: 6px;
605
+ background: transparent;
606
+ color: var(--sb-accent);
607
+ font-family: 'SF Pro Display', system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
608
+ font-size: 13px;
609
+ line-height: 1;
610
+ cursor: pointer;
611
+ flex-shrink: 0;
612
+ }
613
+
614
+ ._importCancel_1ybhg_66:hover {
615
+ background: #f3f3f3;
616
+ }
617
+
599
618
  /* 表内浮动工具条(BubbleMenu portal 到 body,拿不到 .toolbar 变量,故写死配色) */
600
- ._tableBubble_16b09_66 {
619
+ ._tableBubble_1ybhg_85 {
601
620
  display: flex;
602
621
  align-items: center;
603
622
  gap: 2px;
@@ -609,7 +628,7 @@
609
628
  }
610
629
 
611
630
  /* 文字型图标(引用等) */
612
- ._txtIcon_16b09_78 {
631
+ ._txtIcon_1ybhg_97 {
613
632
  font-family: 'SF Pro Display', system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
614
633
  font-size: 16px;
615
634
  font-weight: 600;
@@ -617,7 +636,7 @@
617
636
  }
618
637
 
619
638
  /* 文字颜色 / 高亮图标:字母 + 底部当前色条 */
620
- ._colorIcon_16b09_86 {
639
+ ._colorIcon_1ybhg_105 {
621
640
  display: inline-flex;
622
641
  flex-direction: column;
623
642
  align-items: center;
@@ -625,20 +644,20 @@
625
644
  line-height: 1;
626
645
  }
627
646
 
628
- ._colorIconGlyph_16b09_94 {
647
+ ._colorIconGlyph_1ybhg_113 {
629
648
  font-family: 'SF Pro Display', system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
630
649
  font-size: 15px;
631
650
  font-weight: 600;
632
651
  }
633
652
 
634
- ._colorIconBar_16b09_100 {
653
+ ._colorIconBar_1ybhg_119 {
635
654
  width: 16px;
636
655
  height: 3px;
637
656
  border-radius: 1.5px;
638
657
  }
639
658
 
640
659
  /* A▾ 上标/下标下拉触发器 */
641
- ._scriptTrigger_16b09_107 {
660
+ ._scriptTrigger_1ybhg_126 {
642
661
  display: inline-flex;
643
662
  align-items: center;
644
663
  gap: 3px;
@@ -653,12 +672,12 @@
653
672
  transition: background 0.15s, color 0.15s;
654
673
  }
655
674
 
656
- ._scriptTrigger_16b09_107:hover {
675
+ ._scriptTrigger_1ybhg_126:hover {
657
676
  background: #f3f3f3;
658
677
  color: var(--sb-text);
659
678
  }
660
679
 
661
- ._scriptTriggerGlyph_16b09_127 {
680
+ ._scriptTriggerGlyph_1ybhg_146 {
662
681
  font-family: 'SF Pro Display', system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
663
682
  font-size: 16px;
664
683
  font-weight: 600;
@@ -666,7 +685,7 @@
666
685
  text-underline-offset: 2px;
667
686
  }
668
687
 
669
- ._divider_16b09_135 {
688
+ ._divider_1ybhg_154 {
670
689
  width: 1px;
671
690
  height: 20px;
672
691
  margin: 0 8px;
@@ -675,7 +694,7 @@
675
694
  }
676
695
 
677
696
  /* ── Style / Size / More 下拉触发器 ── */
678
- ._styleTrigger_16b09_144 {
697
+ ._styleTrigger_1ybhg_163 {
679
698
  display: inline-flex;
680
699
  align-items: center;
681
700
  justify-content: space-between;
@@ -695,16 +714,16 @@
695
714
  transition: background 0.15s;
696
715
  }
697
716
 
698
- ._styleTrigger_16b09_144:hover:not(:disabled) {
717
+ ._styleTrigger_1ybhg_163:hover:not(:disabled) {
699
718
  background: #f3f3f3;
700
719
  }
701
720
 
702
- ._styleTrigger_16b09_144:disabled {
721
+ ._styleTrigger_1ybhg_163:disabled {
703
722
  opacity: 0.3;
704
723
  cursor: not-allowed;
705
724
  }
706
725
 
707
- ._styleTriggerLabel_16b09_173 {
726
+ ._styleTriggerLabel_1ybhg_192 {
708
727
  flex: 1;
709
728
  text-align: left;
710
729
  overflow: hidden;
@@ -712,19 +731,19 @@
712
731
  text-overflow: ellipsis;
713
732
  }
714
733
 
715
- ._styleTriggerCaret_16b09_181 {
734
+ ._styleTriggerCaret_1ybhg_200 {
716
735
  color: var(--sb-muted);
717
736
  flex: none;
718
737
  }
719
738
 
720
739
  /* 下拉项:图标 + 文字 */
721
- ._styleItem_16b09_187 {
740
+ ._styleItem_1ybhg_206 {
722
741
  display: inline-flex;
723
742
  align-items: center;
724
743
  gap: 12px;
725
744
  }
726
745
 
727
- ._styleIcon_16b09_193 {
746
+ ._styleIcon_1ybhg_212 {
728
747
  display: inline-flex;
729
748
  justify-content: center;
730
749
  align-items: center;
@@ -734,19 +753,19 @@
734
753
  }
735
754
 
736
755
  /* 上标 / 下标图标 */
737
- ._scriptGlyph_16b09_203 {
756
+ ._scriptGlyph_1ybhg_222 {
738
757
  display: inline-block;
739
758
  text-align: center;
740
759
  font-family: 'SF Pro Display', system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
741
760
  }
742
761
 
743
- ._scriptGlyph_16b09_203 sup,
744
- ._scriptGlyph_16b09_203 sub {
762
+ ._scriptGlyph_1ybhg_222 sup,
763
+ ._scriptGlyph_1ybhg_222 sub {
745
764
  font-size: 0.7em;
746
765
  }
747
766
 
748
767
  /* ── Radix 下拉菜单面板(替代 antd .ant-dropdown-menu) ── */
749
- ._menu_16b09_215 {
768
+ ._menu_1ybhg_234 {
750
769
  min-width: 200px;
751
770
  padding: 6px;
752
771
  background: #fff;
@@ -757,7 +776,7 @@
757
776
  z-index: 1000;
758
777
  }
759
778
 
760
- ._menuItem_16b09_226 {
779
+ ._menuItem_1ybhg_245 {
761
780
  display: flex;
762
781
  align-items: center;
763
782
  width: 100%;
@@ -773,52 +792,52 @@
773
792
  outline: none;
774
793
  }
775
794
 
776
- ._menuItem_16b09_226:hover:not(:disabled) {
795
+ ._menuItem_1ybhg_245:hover:not(:disabled) {
777
796
  background: #f3f3f3;
778
797
  }
779
798
 
780
- ._menuItem_16b09_226:disabled {
799
+ ._menuItem_1ybhg_245:disabled {
781
800
  opacity: 0.35;
782
801
  cursor: not-allowed;
783
802
  }
784
803
 
785
804
  /* 选中态:品牌色高亮 */
786
- ._menuItemSelected_16b09_252 {
805
+ ._menuItemSelected_1ybhg_271 {
787
806
  background: #fff3ec;
788
807
  color: var(--sb-accent);
789
808
  }
790
809
 
791
- ._menuItemSelected_16b09_252 ._styleIcon_16b09_193 {
810
+ ._menuItemSelected_1ybhg_271 ._styleIcon_1ybhg_212 {
792
811
  color: var(--sb-accent);
793
812
  }
794
813
 
795
- ._menuItemSelected_16b09_252:hover:not(:disabled) {
814
+ ._menuItemSelected_1ybhg_271:hover:not(:disabled) {
796
815
  background: #ffe8da;
797
816
  }
798
817
 
799
818
  /* Radix Popover 内容(承载 ColorPalette),面板自带阴影,这里只兜底 z-index */
800
- ._popoverContent_16b09_266 {
819
+ ._popoverContent_1ybhg_285 {
801
820
  z-index: 1000;
802
821
  }
803
822
 
804
- ._mathLayer_16b09_270 {
823
+ ._mathLayer_1ybhg_289 {
805
824
  position: absolute;
806
825
  z-index: 6;
807
826
  }
808
827
 
809
- ._mathStack_16b09_275 {
828
+ ._mathStack_1ybhg_294 {
810
829
  display: flex;
811
830
  flex-direction: column;
812
831
  align-items: flex-start;
813
832
  gap: 8px;
814
833
  }
815
834
 
816
- ._mathStackBlock_16b09_282 {
835
+ ._mathStackBlock_1ybhg_301 {
817
836
  align-items: stretch;
818
837
  width: 100%;
819
838
  }
820
839
 
821
- ._mathHint_16b09_287 {
840
+ ._mathHint_1ybhg_306 {
822
841
  display: inline-flex;
823
842
  align-items: center;
824
843
  gap: 6px;
@@ -830,19 +849,19 @@
830
849
  line-height: 1.4;
831
850
  }
832
851
 
833
- ._mathHintBlock_16b09_299 {
852
+ ._mathHintBlock_1ybhg_318 {
834
853
  width: 100%;
835
854
  padding: 10px 14px;
836
855
  border-radius: 8px;
837
856
  }
838
857
 
839
- ._mathHintIcon_16b09_305 {
858
+ ._mathHintIcon_1ybhg_324 {
840
859
  font-family: ui-serif, Georgia, 'Times New Roman', serif;
841
860
  font-size: 12px;
842
861
  opacity: 0.85;
843
862
  }
844
863
 
845
- ._mathPreviewInline_16b09_311 {
864
+ ._mathPreviewInline_1ybhg_330 {
846
865
  display: inline-flex;
847
866
  max-width: min(440px, calc(100vw - 24px));
848
867
  padding: 4px 10px;
@@ -852,7 +871,7 @@
852
871
  color: var(--tmr-text, #363737);
853
872
  }
854
873
 
855
- ._mathPreviewBlock_16b09_321 {
874
+ ._mathPreviewBlock_1ybhg_340 {
856
875
  width: 100%;
857
876
  min-height: 48px;
858
877
  padding: 14px 16px;
@@ -863,11 +882,11 @@
863
882
  color: var(--tmr-text, #363737);
864
883
  }
865
884
 
866
- ._mathPreviewBlock_16b09_321 .katex-display {
885
+ ._mathPreviewBlock_1ybhg_340 .katex-display {
867
886
  margin: 0;
868
887
  }
869
888
 
870
- ._mathField_16b09_336 {
889
+ ._mathField_1ybhg_355 {
871
890
  display: flex;
872
891
  align-items: center;
873
892
  gap: 8px;
@@ -879,14 +898,14 @@
879
898
  box-shadow: 0 4px 16px rgb(0 0 0 / 12%);
880
899
  }
881
900
 
882
- ._mathFieldBlock_16b09_348 {
901
+ ._mathFieldBlock_1ybhg_367 {
883
902
  align-items: flex-start;
884
903
  width: 100%;
885
904
  border-radius: 12px;
886
905
  padding: 10px 10px 10px 14px;
887
906
  }
888
907
 
889
- ._mathInput_16b09_355 {
908
+ ._mathInput_1ybhg_374 {
890
909
  flex: 1;
891
910
  min-width: 0;
892
911
  margin: 0;
@@ -900,11 +919,11 @@
900
919
  resize: none;
901
920
  }
902
921
 
903
- ._mathInput_16b09_355:focus {
922
+ ._mathInput_1ybhg_374:focus {
904
923
  outline: none;
905
924
  }
906
925
 
907
- ._mathDone_16b09_373 {
926
+ ._mathDone_1ybhg_392 {
908
927
  display: inline-flex;
909
928
  align-items: center;
910
929
  gap: 4px;
@@ -920,16 +939,16 @@
920
939
  cursor: pointer;
921
940
  }
922
941
 
923
- ._mathDone_16b09_373:hover:not(:disabled) {
942
+ ._mathDone_1ybhg_392:hover:not(:disabled) {
924
943
  filter: brightness(0.95);
925
944
  }
926
945
 
927
- ._mathDone_16b09_373:disabled {
946
+ ._mathDone_1ybhg_392:disabled {
928
947
  opacity: 0.35;
929
948
  cursor: not-allowed;
930
949
  }
931
950
 
932
- ._mathDoneKbd_16b09_398 {
951
+ ._mathDoneKbd_1ybhg_417 {
933
952
  font-size: 11px;
934
953
  font-weight: 500;
935
954
  opacity: 0.85;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tiptap-markdown-react",
3
- "version": "0.8.5",
3
+ "version": "0.8.7",
4
4
  "description": "A batteries-included, self-styled Markdown WYSIWYG editor and reader suite built on Tiptap v3. Markdown in/out, SSR/static rendering, table of contents, themeable via CSS variables, zero Ant Design.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -64,6 +64,7 @@
64
64
  "@radix-ui/react-dropdown-menu": "^2.1.16",
65
65
  "@radix-ui/react-popover": "^1.1.15",
66
66
  "@tiptap/core": "3.27.1",
67
+ "@tiptap/extension-code": "^3.27.1",
67
68
  "@tiptap/extension-code-block-lowlight": "3.27.1",
68
69
  "@tiptap/extension-file-handler": "^3.27.1",
69
70
  "@tiptap/extension-highlight": "3.27.1",
@@ -1 +0,0 @@
1
- {"version":3,"file":"extractToc-bgvj5gGC.js","sources":["../src/citationUtils.ts","../src/CitationRef.ts","../src/commentAnchor/CommentMark.ts","../src/commentAnchor/commentMapper.ts","../src/math.ts","../src/extensions.ts","../src/toc/tocSlug.ts","../src/ReportContent.tsx","../src/stabilizeMarkdown.ts","../src/toc/extractToc.ts"],"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","import { mergeAttributes, Node } from '@tiptap/core';\n\nexport interface CitationRefOptions {\n HTMLAttributes: Record<string, unknown>;\n}\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n citationRef: {\n insertCitationRef: (attrs: {\n index: string;\n url?: string | null;\n title?: string | null;\n }) => ReturnType;\n };\n }\n}\n\n/**\n * 脚注/引文引用——inline atom 节点。\n *\n * - 输入:`[^n]`(markdownTokenizer)或 `<span class=\"citation-ref\">`(parseHTML)\n * - 输出 HTML:居中圆形 pill(CSS `.citation-ref`),**不是**上下标\n * - 输出 markdown:`[^n]`\n *\n * Popover /「原文片段」由消费方通过 {@link createCitationRef} 的 `renderCitation`\n * 插槽完成;本节点只负责标记与默认圆标样式。\n */\nexport const CitationRef = Node.create<CitationRefOptions>({\n name: 'citationRef',\n\n group: 'inline',\n\n inline: true,\n\n atom: true,\n\n selectable: true,\n\n // 高于普通链接,确保 `[^1]` 不被拆成残缺 link\n priority: 1000,\n\n addOptions() {\n return { HTMLAttributes: {} };\n },\n\n addAttributes() {\n return {\n index: {\n default: null,\n parseHTML: (element) => element.getAttribute('data-index'),\n renderHTML: (attributes) => {\n if (!attributes.index) return {};\n return { 'data-index': attributes.index };\n },\n },\n url: {\n default: null,\n parseHTML: (element) => element.getAttribute('data-url'),\n renderHTML: (attributes) => {\n if (!attributes.url) return {};\n return { 'data-url': attributes.url };\n },\n },\n title: {\n default: null,\n parseHTML: (element) => element.getAttribute('data-title'),\n renderHTML: (attributes) => {\n if (!attributes.title) return {};\n return { 'data-title': attributes.title };\n },\n },\n };\n },\n\n parseHTML() {\n return [{ tag: 'span.citation-ref' }];\n },\n\n renderHTML({ node, HTMLAttributes }) {\n const index = String(node.attrs.index ?? '');\n const url = node.attrs.url as string | null;\n const title = (node.attrs.title as string | null) || '';\n const attrs = mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {\n class: 'citation-ref',\n });\n\n if (url) {\n return [\n 'span',\n attrs,\n [\n 'a',\n {\n href: url,\n title,\n target: '_blank',\n rel: 'noopener noreferrer',\n },\n index,\n ],\n ];\n }\n\n return ['span', attrs, index];\n },\n\n renderMarkdown(node) {\n return `[^${node?.attrs?.index ?? ''}]`;\n },\n\n parseMarkdown: (token, helpers) => {\n return helpers.createNode('citationRef', {\n index: String(token.index ?? ''),\n url: token.url ?? null,\n title: token.title ?? null,\n });\n },\n\n markdownTokenizer: {\n name: 'citationRef',\n level: 'inline' as const,\n start(src: string) {\n const match = /\\[\\^\\d+\\]/.exec(src);\n return match?.index ?? -1;\n },\n tokenize(src) {\n const match = /^\\[\\^(\\d+)\\]/.exec(src);\n if (!match) return undefined;\n return {\n type: 'citationRef',\n raw: match[0],\n index: match[1],\n };\n },\n },\n\n addCommands() {\n return {\n insertCitationRef:\n (attrs) =>\n ({ commands }) =>\n commands.insertContent({\n type: this.name,\n attrs: {\n index: attrs.index,\n url: attrs.url ?? null,\n title: attrs.title ?? null,\n },\n }),\n };\n },\n});\n","import { Mark, mergeAttributes } from '@tiptap/core';\n\n/**\n * 评论锚定 mark —— 仅存在于编辑会话内,导出 markdown 时剥离。\n *\n * - attrs.commentIds:string[]。ProseMirror 同一文本节点不能挂两个同名\n * mark,重叠评论由 commentMapper 合并成「区间 + commentIds[]」后一次性铺入。\n * - renderMarkdown 返回空串 → getMarkdown() 输出纯文本,评论数据不落库。\n * - 粘贴/拖拽拦截与 gutter / active 装饰见 commentAnchorPlugin(客户端)。\n * 本模块保持纯 schema,server 入口可安全导入。\n */\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n commentAnchor: {\n /** 按互不相交的区间 + commentIds 数组铺评论 mark(区间由 mergeCommentIntervals 产出)。 */\n applyCommentAnchors: (intervals: CommentInterval[]) => ReturnType;\n /** 清空全文档评论 mark(comments 刷新 / 退出编辑态前调用)。 */\n clearCommentAnchors: () => ReturnType;\n };\n }\n}\n\nexport interface CommentInterval {\n from: number;\n to: number;\n commentIds: string[];\n}\n\nexport interface CommentMarkOptions {\n HTMLAttributes: Record<string, unknown>;\n}\n\nexport const CommentMark = Mark.create<CommentMarkOptions>({\n name: 'commentAnchor',\n\n // 评论是「引用快照」:光标在区间边界输入不应自动扩展高亮。\n inclusive: false,\n // 与加粗/斜体等格式可共存。\n excludes: '',\n\n addOptions() {\n return { HTMLAttributes: {} };\n },\n\n addAttributes() {\n return {\n commentIds: {\n default: [] as string[],\n parseHTML: (element) =>\n (element.getAttribute('data-comment-ids') ?? '')\n .split(' ')\n .filter(Boolean),\n renderHTML: (attributes) => {\n const ids = attributes.commentIds as string[];\n return ids?.length\n ? { 'data-comment-ids': Array.from(new Set(ids)).join(' ') }\n : {};\n },\n },\n };\n },\n\n parseHTML() {\n return [{ tag: 'mark[data-comment-ids]' }];\n },\n\n renderHTML({ HTMLAttributes }) {\n return [\n 'mark',\n mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {\n class: 'tmr-comment',\n }),\n 0,\n ];\n },\n\n // 关键契约:markdown 输出剥离评论 mark,落库内容永远干净。\n // 注意:@tiptap/markdown 的 registerExtension 以扩展 name 注册 renderMarkdown,\n // 无需(也不支持)markdownName 字段。\n renderMarkdown() {\n return '';\n },\n\n addCommands() {\n return {\n applyCommentAnchors:\n (intervals) =>\n ({ tr, dispatch }) => {\n const intervals_ = (intervals ?? []).filter(\n (iv) => iv.from < iv.to && iv.commentIds.length > 0,\n );\n if (intervals_.length === 0) return false;\n let next = tr;\n for (const iv of intervals_) {\n next = next.addMark(\n iv.from,\n iv.to,\n this.type.create({ commentIds: iv.commentIds }),\n );\n }\n // 锚定是会话级 UI,不是用户内容:不进 undo 历史,避免 Ctrl+Z 把\n // 高亮全部撤掉。真编辑(改字)仍可正常 undo。\n next.setMeta('addToHistory', false);\n if (dispatch) dispatch(next);\n return true;\n },\n clearCommentAnchors:\n () =>\n ({ tr, dispatch }) => {\n const markType = this.type;\n const ranges: Array<{ from: number; to: number }> = [];\n tr.doc.descendants((node, pos) => {\n if (node.isText && node.marks.some((m) => m.type === markType)) {\n ranges.push({ from: pos, to: pos + node.nodeSize });\n }\n return true;\n });\n // 无 mark 时视为成功(no-op),保证 chain 后续命令继续执行。\n if (ranges.length === 0) return true;\n let next = tr;\n for (const r of ranges) next = next.removeMark(r.from, r.to, markType);\n next.setMeta('addToHistory', false);\n if (dispatch) dispatch(next);\n return true;\n },\n };\n },\n});\n","import type { Node } from '@tiptap/pm/model';\nimport type { CommentInterval } from './CommentMark';\n\n/**\n * SPIKE 原型:把评论的 ref_content segments 映射到 ProseMirror doc 上的文本区间。\n *\n * 与旧 DOM textContent 匹配的区别:\n * - 工作在文档模型上(无表格 wrapper 空白 / text node 碎片问题);\n * - 按顶层 block 作用域搜索,避免跨 block 误命中;\n * - fallback 链与既有 anchorCommentsInDom 一致:hash → context → exact → prefix → outdated。\n */\n\nexport interface CommentSegment {\n /** block 文本哈希(可选快路径;哈希算法见 blockTextHash,需与捕获端一致)。 */\n blockHash?: string;\n prefix?: string;\n exact: string;\n suffix?: string;\n}\n\nexport interface CommentAnchorInput {\n commentId: string;\n segments: CommentSegment[];\n}\n\nexport type CommentAnchorStatus = 'matched' | 'ambiguous' | 'partial' | 'outdated';\n\nexport interface CommentRange {\n from: number;\n to: number;\n}\n\nexport interface CommentAnchorResult {\n commentId: string;\n ranges: CommentRange[];\n status: CommentAnchorStatus;\n}\n\ninterface TextSlice {\n pos: number;\n text: string;\n}\n\ninterface TextIndex {\n slices: TextSlice[];\n offsets: number[];\n fullText: string;\n}\n\nconst PREFIX_FALLBACK_LEN = 20;\n\n/** 收集 [start, end) 内所有 text 节点 + 位置,拼成可搜索的 flat text。 */\nfunction collectText(doc: Node, start = 0, end = doc.content.size): TextIndex {\n const slices: TextSlice[] = [];\n const offsets: number[] = [];\n let cursor = 0;\n doc.nodesBetween(start, end, (node, pos) => {\n if (node.isText && node.text) {\n slices.push({ pos, text: node.text });\n offsets.push(cursor);\n cursor += node.text.length;\n }\n return true;\n });\n offsets.push(cursor);\n return {\n slices,\n offsets,\n fullText: slices.map((s) => s.text).join(''),\n };\n}\n\nfunction findAll(fullText: string, needle: string): number[] {\n if (!needle) return [];\n const hits: number[] = [];\n let from = 0;\n while (from <= fullText.length - needle.length) {\n const idx = fullText.indexOf(needle, from);\n if (idx < 0) break;\n hits.push(idx);\n from = idx + needle.length;\n }\n return hits;\n}\n\n/** 把 fullText 上的 [start, start+len) 映射回 doc 绝对位置。 */\nfunction rangeAt(index: TextIndex, start: number, len: number): CommentRange | null {\n if (len <= 0) return null;\n let startIdx = -1;\n let endIdx = -1;\n for (let i = 0; i < index.slices.length; i++) {\n if (index.offsets[i] <= start && start < index.offsets[i + 1]) startIdx = i;\n const end = start + len;\n if (index.offsets[i] < end && end <= index.offsets[i + 1]) endIdx = i;\n if (startIdx >= 0 && endIdx >= 0) break;\n }\n if (startIdx < 0 || endIdx < 0) return null;\n return {\n from: index.slices[startIdx].pos + (start - index.offsets[startIdx]),\n to: index.slices[endIdx].pos + (start + len - index.offsets[endIdx]),\n };\n}\n\n/** 顶层 block 边界(doc 直接子节点)。 */\nfunction blockBounds(doc: Node): Array<{ start: number; end: number }> {\n const bounds: Array<{ start: number; end: number }> = [];\n doc.forEach((child, offset) => {\n bounds.push({ start: offset, end: offset + child.nodeSize });\n });\n return bounds;\n}\n\n/** djb2 哈希(捕获端与消费端必须用同一个实现;invret 接入时对齐)。 */\nexport function blockTextHash(text: string): string {\n const normalized = text.replace(/\\s+/g, '');\n let hash = 5381;\n for (let i = 0; i < normalized.length; i++) {\n hash = (hash * 33) ^ normalized.charCodeAt(i);\n }\n return String(hash >>> 0);\n}\n\nfunction blockText(doc: Node, bound: { start: number; end: number }): string {\n return collectText(doc, bound.start, bound.end).fullText;\n}\n\n/** 在若干 block 内找 needle,返回命中区间;命中 >1 处标记 ambiguous。 */\nfunction matchInBlocks(\n doc: Node,\n bounds: Array<{ start: number; end: number }>,\n needle: string,\n markLen: number,\n markOffset: number,\n): { ranges: CommentRange[]; ambiguous: boolean } {\n const ranges: CommentRange[] = [];\n let hits = 0;\n for (const bound of bounds) {\n const index = collectText(doc, bound.start, bound.end);\n for (const start of findAll(index.fullText, needle)) {\n const range = rangeAt(index, start + markOffset, markLen);\n if (range) {\n ranges.push(range);\n hits += 1;\n }\n }\n }\n return { ranges, ambiguous: hits > 1 };\n}\n\n/** 单个 segment → doc 区间(fallback 链)。 */\nfunction matchSegment(doc: Node, seg: CommentSegment): {\n ranges: CommentRange[];\n ambiguous: boolean;\n} {\n const bounds = blockBounds(doc);\n\n // S1: blockHash 快路径\n if (seg.blockHash) {\n const ranges: CommentRange[] = [];\n let hits = 0;\n for (const bound of bounds) {\n if (blockTextHash(blockText(doc, bound)) !== seg.blockHash) continue;\n const index = collectText(doc, bound.start, bound.end);\n for (const start of findAll(index.fullText, seg.exact)) {\n const range = rangeAt(index, start, seg.exact.length);\n if (range) {\n ranges.push(range);\n hits += 1;\n }\n }\n }\n if (ranges.length > 0) return { ranges, ambiguous: hits > 1 };\n }\n\n // S2: 上下文(prefix+exact+suffix)定位\n if (seg.prefix || seg.suffix) {\n const composite = (seg.prefix ?? '') + seg.exact + (seg.suffix ?? '');\n const found = matchInBlocks(\n doc,\n bounds,\n composite,\n seg.exact.length,\n seg.prefix?.length ?? 0,\n );\n if (found.ranges.length > 0) return found;\n }\n\n // S3: block 内 exact\n const exact = matchInBlocks(doc, bounds, seg.exact, seg.exact.length, 0);\n if (exact.ranges.length > 0) return exact;\n\n // S4: 前缀降级(前 20 字)\n const prefix = seg.exact.slice(0, PREFIX_FALLBACK_LEN);\n if (prefix.length >= 4 && prefix !== seg.exact) {\n const fallback = matchInBlocks(doc, bounds, prefix, prefix.length, 0);\n if (fallback.ranges.length > 0) return fallback;\n }\n\n return { ranges: [], ambiguous: false };\n}\n\n/** 主入口:评论列表 → 每条的 doc 区间 + 状态。 */\nexport function mapCommentAnchors(\n doc: Node,\n comments: CommentAnchorInput[],\n): CommentAnchorResult[] {\n return comments.map((c) => {\n let matchedSegments = 0;\n let ambiguousSegments = 0;\n const ranges: CommentRange[] = [];\n for (const seg of c.segments) {\n const { ranges: segRanges, ambiguous } = matchSegment(doc, seg);\n if (segRanges.length > 0) {\n matchedSegments += 1;\n if (ambiguous) ambiguousSegments += 1;\n ranges.push(...segRanges);\n }\n }\n const status: CommentAnchorStatus =\n matchedSegments === 0\n ? 'outdated'\n : ambiguousSegments > 0\n ? 'ambiguous'\n : matchedSegments < c.segments.length\n ? 'partial'\n : 'matched';\n return { commentId: c.commentId, ranges, status };\n });\n}\n\n/** 把每条评论的区间合并成互不相交的 CommentInterval(重叠区间合并 commentIds)。 */\nexport function mergeCommentIntervals(\n entries: Array<{ commentId: string; ranges: CommentRange[] }>,\n): CommentInterval[] {\n interface Event {\n pos: number;\n delta: 1 | -1;\n id: string;\n }\n const events: Event[] = [];\n for (const e of entries) {\n // 先按评论合并相邻/接触区间(同一评论的多段 [0,2)+[2,4) 应视为连续覆盖),\n // 否则 sweep 会在接缝处错误地先关后开,产出多余分段。\n const sorted = e.ranges\n .filter((r) => r.from < r.to)\n .sort((a, b) => a.from - b.from || a.to - b.to);\n const merged: CommentRange[] = [];\n for (const r of sorted) {\n const last = merged[merged.length - 1];\n if (last && r.from <= last.to) {\n if (r.to > last.to) last.to = r.to;\n } else {\n merged.push({ from: r.from, to: r.to });\n }\n }\n for (const r of merged) {\n events.push({ pos: r.from, delta: 1, id: e.commentId });\n events.push({ pos: r.to, delta: -1, id: e.commentId });\n }\n }\n if (events.length === 0) return [];\n events.sort((a, b) => a.pos - b.pos || a.delta - b.delta);\n\n const intervals: CommentInterval[] = [];\n const active = new Set<string>();\n let prevPos = events[0].pos;\n for (const ev of events) {\n if (ev.pos > prevPos && active.size > 0) {\n intervals.push({\n from: prevPos,\n to: ev.pos,\n commentIds: Array.from(active),\n });\n }\n if (ev.delta === 1) active.add(ev.id);\n else active.delete(ev.id);\n prevPos = ev.pos;\n }\n return intervals;\n}\n","import { BlockMath, InlineMath } from '@tiptap/extension-mathematics';\nimport type { Node as PMNode } from '@tiptap/pm/model';\nimport katex from 'katex';\n\nconst KATEX_OPTIONS = { throwOnError: false as const };\n\nexport type MathKind = 'inline' | 'block';\n\nexport interface MathClickPayload {\n kind: MathKind;\n pos: number;\n latex: string;\n}\n\ntype MathClickListener = (payload: MathClickPayload) => void;\n\nlet mathClickListener: MathClickListener | null = null;\n\n/** 编辑器工具栏注册点击公式回调;未注册时点击无操作(预览 / SSR)。 */\nexport function subscribeMathClick(listener: MathClickListener): () => void {\n mathClickListener = listener;\n return () => {\n if (mathClickListener === listener) mathClickListener = null;\n };\n}\n\nfunction emitClick(kind: MathKind, node: PMNode, pos: number) {\n mathClickListener?.({\n kind,\n pos,\n latex: String(node.attrs.latex ?? ''),\n });\n}\n\nfunction escapeHtml(value: string): string {\n return value\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;');\n}\n\n/** KaTeX HTML 片段(SSR / 预览弹层)。失败时回退为转义原文。 */\nexport function renderMathHtml(latex: string, displayMode: boolean): string {\n try {\n return katex.renderToString(latex ?? '', {\n ...KATEX_OPTIONS,\n displayMode,\n });\n } catch {\n return escapeHtml(latex ?? '');\n }\n}\n\n/** 把公式画进已有节点(编辑态原地预览,避免再叠一份 overlay)。 */\nexport function renderMathInto(\n el: HTMLElement,\n latex: string,\n displayMode: boolean,\n): void {\n try {\n katex.render(latex ?? '', el, { ...KATEX_OPTIONS, displayMode });\n } catch {\n el.textContent = latex ?? '';\n }\n}\n\nexport function renderMathElementHtml(\n latex: string,\n displayMode: boolean,\n): string {\n const inner = renderMathHtml(latex, displayMode);\n const attr = escapeHtml(latex ?? '');\n if (displayMode) {\n return `<div data-type=\"block-math\" data-latex=\"${attr}\" class=\"tiptap-mathematics-render\">${inner}</div>`;\n }\n return `<span data-type=\"inline-math\" data-latex=\"${attr}\" class=\"tiptap-mathematics-render\">${inner}</span>`;\n}\n\n/**\n * 行内公式:markdown 为 `$$latex$$`(单行、无换行)。\n * 关掉官方 `$…$` tokenizer 与 `$$` input rule——手打 `$` / `$$` 当字。\n */\nexport const reportInlineMath = InlineMath.extend({\n addInputRules() {\n return [];\n },\n\n renderMarkdown(node: { attrs?: { latex?: string } }) {\n return `$$${node.attrs?.latex || ''}$$`;\n },\n\n markdownTokenizer: {\n name: 'inlineMath',\n level: 'inline' as const,\n start(src: string) {\n return src.indexOf('$$');\n },\n tokenize(src: string) {\n const match = /^\\$\\$([^$\\n]+?)\\$\\$/.exec(src);\n if (!match) return undefined;\n return {\n type: 'inlineMath',\n raw: match[0],\n latex: match[1].trim(),\n };\n },\n },\n}).configure({\n katexOptions: { ...KATEX_OPTIONS, displayMode: false },\n onClick: (node, pos) => emitClick('inline', node, pos),\n});\n\n/**\n * 块级公式:markdown 为带换行的 `$$\\nlatex\\n$$`。\n * 单行 `$$…$$` 不在这里匹配,交给行内。无 input rule。\n */\nexport const reportBlockMath = BlockMath.extend({\n addInputRules() {\n return [];\n },\n\n renderMarkdown(node: { attrs?: { latex?: string } }) {\n const latex = node.attrs?.latex || '';\n return `$$\\n${latex}\\n$$`;\n },\n\n markdownTokenizer: {\n name: 'blockMath',\n level: 'block' as const,\n start(src: string) {\n const match = /\\$\\$\\r?\\n/.exec(src);\n return match ? match.index : -1;\n },\n tokenize(src: string) {\n const match = /^\\$\\$\\r?\\n([\\s\\S]+?)\\r?\\n\\$\\$(?:\\r?\\n|$)/.exec(src);\n if (!match) return undefined;\n return {\n type: 'blockMath',\n raw: match[0],\n latex: match[1].trim(),\n };\n },\n },\n}).configure({\n katexOptions: { ...KATEX_OPTIONS, displayMode: true },\n onClick: (node, pos) => emitClick('block', node, pos),\n});\n","import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight';\nimport Highlight from '@tiptap/extension-highlight';\nimport Image from '@tiptap/extension-image';\nimport Subscript from '@tiptap/extension-subscript';\nimport Superscript from '@tiptap/extension-superscript';\nimport { TableKit } from '@tiptap/extension-table';\nimport TaskItem from '@tiptap/extension-task-item';\nimport TaskList from '@tiptap/extension-task-list';\nimport {\n Color,\n FontSize,\n TextStyle,\n} from '@tiptap/extension-text-style';\n// AnyExtension 从 @tiptap/react 取(它 re-export 自 core),避免新增 @tiptap/core 直接依赖\nimport type { AnyExtension } from '@tiptap/react';\nimport StarterKit from '@tiptap/starter-kit';\nimport { common, createLowlight } from 'lowlight';\nimport type { JSONContent } from '@tiptap/core';\nimport { reportBlockMath, reportInlineMath } from './math';\n\n/**\n * 编辑器的「内容 schema 级」扩展集——纯节点/标记定义,不含任何 React NodeView\n * 或键盘交互绑定。三处共用:\n * 1. 编辑器(MarkdownWysiwygEditor):base + 交互增强版 CodeBlock/Image\n * 2. 只读预览(MarkdownPreview)\n * 3. server 端 markdown→HTML 渲染(renderReportHtml)\n *\n * 分层动机:交互增强依赖 @tiptap/react / 浏览器,会污染 server bundle(RSC)。\n * base 保持纯 schema,server import 它只引入节点定义,无 React 代码。\n *\n * 关键边界:CodeBlock / Image / CitationRef「会被客户端增强」,**不放进 base**——\n * 否则编辑器追加增强版会与 base 的纯版同名冲突(Tiptap 报 \"Duplicate extension names\")。\n * 改为:base 不含这三类,由调用方按需注入:\n * - 编辑器 / 预览注入「增强版」(React NodeView)\n * - server 注入「纯版」(pureCodeBlock / pureImage / CitationRef)\n */\n\n// lowlight 实例:代码块语法高亮。导出供编辑器增强版与 server 纯版共用同一实例。\nexport const lowlight = createLowlight(common);\n\n/**\n * 颜色 / 字号的 markdown 往返:markdown 没有原生颜色语法,序列化为行内 HTML\n * `<span style=\"...\">`(marked 支持行内 HTML,Tiptap 解析端经 parseHTML 还原成\n * TextStyle/Color/FontSize mark)。否则 getMarkdown() 会把这些 mark 静默剥掉,\n * 保存后再预览颜色丢失。\n *\n * 注意:Color / FontSize 通过 `types: [TextStyle.name]` 把 color / fontSize\n * 属性挂在 **textStyle** mark 的 attrs 上,文档里出现的 mark 类型名是\n * `textStyle`——所以 renderMarkdown 必须扩展在 TextStyle 上,序列化器才能按\n * mark 类型名查到 handler。\n */\nconst MarkdownTextStyle = TextStyle.extend({\n renderMarkdown(\n node: { attrs?: Record<string, unknown> },\n helpers: {\n renderChildren: (\n nodes: JSONContent | JSONContent[],\n separator?: string,\n ) => string;\n },\n ) {\n const { color, fontSize } = (node.attrs ?? {}) as {\n color?: string;\n fontSize?: string;\n };\n const styles: string[] = [];\n if (color) styles.push(`color:${color}`);\n if (fontSize) styles.push(`font-size:${fontSize}`);\n if (styles.length === 0) return '';\n // getMarkOpening 用占位符调用 renderChildren()(无需传节点),类型按签名兜底转换。\n const children = (helpers.renderChildren as () => string)();\n return `<span style=\"${styles.join(';')}\">${children}</span>`;\n },\n});\n\n/** 纯版代码块(无 React 视图):server / 预览用,产出带高亮 class 的静态结构。 */\nexport const pureCodeBlock = CodeBlockLowlight.configure({ lowlight });\n\n/**\n * 纯版块级图片(无删除快捷键):server / 预览用。\n * inline:false(官方默认)——研报里图片均为大图独占一行,块级图片可直接作为 doc\n * 顶层子节点,避免「inline image 裸挂 doc 顶层」被 generateTocIds 严格校验判非法。\n */\nexport const pureImage = Image.configure({ inline: false });\n\n/**\n * 三处共用的纯 schema 扩展——**不含 CodeBlock / Image / CitationRef**(见上方边界说明)。\n * 编辑器:`[...baseExtensions, 增强CodeBlock, 增强Image, createCitationRef(), Markdown]`\n * server/预览:`[...baseExtensions, pureCodeBlock, pureImage, CitationRef, …]`\n */\nexport const baseExtensions: AnyExtension[] = [\n // 禁用 StarterKit 内置 codeBlock,统一改用带 lowlight 的代码块(由调用方注入)\n StarterKit.configure({ codeBlock: false }),\n MarkdownTextStyle,\n Color.configure({ types: [TextStyle.name] }),\n // 正文字号:官方 FontSize(v3 起并入 text-style 包),以内联样式挂在 TextStyle\n // 的 <span> 上(如 style=\"font-size: 16px\"),与 Color 同基座,三端共用。\n FontSize.configure({ types: [TextStyle.name] }),\n Highlight.configure({ multicolor: true }),\n Superscript,\n Subscript,\n TableKit.configure({\n table: {\n resizable: true,\n // SSR/renderHTML 默认不包 wrapper;阅读页无 NodeView 时宽表会被挤扁。\n // 与编辑器 TableView 的 `.tableWrapper` 对齐,静态 HTML 也可 overflow-x。\n renderWrapper: true,\n },\n }),\n TaskList,\n TaskItem.configure({ nested: true }),\n reportInlineMath,\n reportBlockMath,\n];\n","/**\n * 目录锚点 id 生成——阅读页(SSR)与编辑页(editor)共用的**单一真源**,\n * 保证「创作预览的锚点 = 发布后阅读页的锚点」。\n *\n * 用 slug 而非官方默认 UUID:slug 跨 SSR / ISR revalidate 稳定(UUID 每次都变、\n * 分享的 #hash 会失效),且 `#introduction` 比 `#d4590f81-…` 可读、对 SEO 友好。\n *\n * slug 会重名(两个「细节」都 slug 成 `xi-jie`),故用闭包计数器去重:\n * 同名追加 -2 / -3(已实测:details / details-2 / details-3,HTML id 全唯一)。\n */\n\n/** 单个标题文本 → 基础 slug(保留中文,去标点,空白转连字符)。 */\nfunction baseSlug(text: string): string {\n return (\n text\n .toLowerCase()\n .trim()\n .replace(/\\s+/g, '-')\n // 保留字母/数字/下划线/中文/连字符,其余剔除\n .replace(/[^\\w一-龥-]/g, '')\n .replace(/^-+|-+$/g, '') || 'section'\n );\n}\n\n/**\n * 造一个带去重的 `getId`。每次生成新实例(闭包各自计数),传给\n * `TableOfContents.configure({ getId })` 或 `generateTocIds` 的扩展配置。\n */\nexport function makeTocGetId(): (content: string) => string {\n const seen = new Map<string, number>();\n return (content: string) => {\n const base = baseSlug(content);\n const n = (seen.get(base) ?? 0) + 1;\n seen.set(base, n);\n return n === 1 ? base : `${base}-${n}`;\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","/**\n * 给流式半成品 markdown 补闭合围栏,避免未结束的 ``` 把后文吞进 code。\n * 完整稿不要调用(ISR / 已落库卡)。\n */\nexport function stabilizeMarkdown(markdown: string): string {\n if (!markdown) return markdown;\n\n let inFence = false;\n let fenceChar = '`';\n let fenceLen = 3;\n\n for (const line of markdown.split('\\n')) {\n const match = line.match(/^(`{3,}|~{3,})/);\n if (!match) continue;\n const marker = match[1];\n const char = marker[0];\n if (!inFence) {\n inFence = true;\n fenceChar = char;\n fenceLen = marker.length;\n continue;\n }\n if (char === fenceChar && marker.length >= fenceLen && /^[`~]+\\s*$/.test(line)) {\n inFence = false;\n }\n }\n\n if (!inFence) return markdown;\n const closer = fenceChar.repeat(fenceLen);\n return markdown.endsWith('\\n') ? `${markdown}${closer}` : `${markdown}\\n${closer}`;\n}\n","/**\n * 最小 Tiptap JSON 节点结构(只取本模块用到的字段)。\n * 不从 `@tiptap/core` 引 `JSONContent`——core 是嵌套依赖、非直接依赖,\n * 直接 import 类型解析不到(与仓库既有做法一致,见 extensions.ts)。\n */\ninterface TocDocNode {\n type?: string;\n text?: string;\n attrs?: Record<string, unknown> | null;\n content?: TocDocNode[];\n}\n\n/** 目录项。 */\nexport interface TocItem {\n /** 锚点 id(= heading 的 data-toc-id / id,由 generateTocIds 注入)。 */\n id: string;\n /** 标题层级(1-6)。 */\n level: number;\n /** 标题纯文本。 */\n text: string;\n /** 付费墙锁住的章节(置灰不可点)。 */\n locked?: boolean;\n}\n\n/** 目录纳入的最大层级(h1-h6,全层级入目录)。 */\nconst MAX_LEVEL = 6;\n\n/** 取 heading 节点的纯文本(拼接其 text 子节点)。 */\nfunction headingText(node: TocDocNode): string {\n if (!node.content) return '';\n return node.content\n .map((c) => (c.type === 'text' ? c.text ?? '' : ''))\n .join('')\n .trim();\n}\n\n/**\n * 从已注入锚点 id 的 Tiptap JSON doc 提取 h1-h6 目录。\n *\n * 必须在 `generateTocIds` 之后调用(heading 的 attrs 里才有 `data-toc-id`)。\n * `lockedTitles`:被截断(锁住)章节标题,命中则标 `locked`——\n * 目录列全部章节,锁住项置灰不可点(付费墙钩子)。用 Set 便于去重命中。\n */\nexport function extractToc(\n doc: TocDocNode,\n lockedTitles: string[] = [],\n): TocItem[] {\n const locked = new Set(lockedTitles.map((t) => t.trim()));\n const items: TocItem[] = [];\n\n const walk = (node: TocDocNode) => {\n const level = node.attrs?.level;\n if (\n node.type === 'heading' &&\n typeof level === 'number' &&\n level <= MAX_LEVEL\n ) {\n const text = headingText(node);\n const id = (node.attrs?.['data-toc-id'] ?? node.attrs?.id) as\n | string\n | undefined;\n // 有文本 + 有锚点 id 才入目录(空标题 / 未注入 id 的跳过)。\n if (text && id) {\n items.push({\n id,\n level,\n text,\n locked: locked.has(text) || undefined,\n });\n }\n }\n node.content?.forEach(walk);\n };\n\n walk(doc);\n return items;\n}\n"],"names":["styles"],"mappings":"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"}