tiptap-markdown-react 0.8.10 → 0.8.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/labels.d.ts CHANGED
@@ -54,12 +54,27 @@ export interface ToolbarLabels {
54
54
  /** 导入下拉里的 Markdown 项。 */
55
55
  importMarkdown: string;
56
56
  tableInsert: string;
57
+ /** 网格选择器底部尺寸文案,如 "3 × 4"。参数顺序:列、行。 */
58
+ tableSizeSelected: (cols: number, rows: number) => string;
57
59
  tableAddColumnBefore: string;
58
60
  tableAddColumnAfter: string;
59
61
  tableDeleteColumn: string;
60
62
  tableAddRowBefore: string;
61
63
  tableAddRowAfter: string;
62
64
  tableDeleteRow: string;
65
+ /** 多格选区:在左侧插入 N 列。 */
66
+ tableAddColumnBeforeN: (n: number) => string;
67
+ /** 多格选区:在右侧插入 N 列。 */
68
+ tableAddColumnAfterN: (n: number) => string;
69
+ /** 多格选区:删除 N 列。 */
70
+ tableDeleteColumnN: (n: number) => string;
71
+ /** 多格选区:在上方插入 N 行。 */
72
+ tableAddRowBeforeN: (n: number) => string;
73
+ /** 多格选区:在下方插入 N 行。 */
74
+ tableAddRowAfterN: (n: number) => string;
75
+ /** 多格选区:删除 N 行。 */
76
+ tableDeleteRowN: (n: number) => string;
77
+ tableDeleteTable: string;
63
78
  inlineMath: string;
64
79
  blockMath: string;
65
80
  mathPlaceholder: string;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * 阅读页入口:只贴 SSR HTML + 脚注 click 委托。
3
+ *
4
+ * 不 import `./extensions`(TableKit / CellSelection)。主入口 `.` 与 `./server`
5
+ * 都会在模块加载时 `jsonID("cell")`;同一 JS realm 里两套一起加载会抛
6
+ * `Duplicate use of selection JSON ID cell`。阅读页用本入口即可与 `/server`
7
+ * 共存(表格已是静态 HTML,不必再注册选区)。
8
+ */
9
+ export { ReportContent, type ReportContentProps } from './ReportContent';
10
+ export { CitationInteractive, type CitationInteractiveProps, } from './components/CitationInteractive';
11
+ export { ReportContentInteractive, type ReportContentInteractiveProps, } from './components/ReportContentInteractive';
12
+ export type { CitationEnterContext, OnCitationEnter, OnCitationLeave, RenderCitation, RenderCitationContext, } from './citationTypes';
13
+ export { findCitationRefElement, readCitationAttrs, } from './citationDom';
14
+ export { applyCitationSources, enrichMarkdownCitations, type SourceRef, } from './citationUtils';
15
+ export { scrollToTocHeading } from './toc/scrollToTocHeading';
16
+ export type { TocItem } from './toc/extractToc';
@@ -0,0 +1 @@
1
+ export {};
package/dist/reader.js ADDED
@@ -0,0 +1,14 @@
1
+ "use client";
2
+ import { R, a, e } from "./ReportContent-c_UlJnVY.js";
3
+ import { C, R as R2, f, r, s } from "./scrollToTocHeading-CwapFaVP.js";
4
+ export {
5
+ C as CitationInteractive,
6
+ R as ReportContent,
7
+ R2 as ReportContentInteractive,
8
+ a as applyCitationSources,
9
+ e as enrichMarkdownCitations,
10
+ f as findCitationRefElement,
11
+ r as readCitationAttrs,
12
+ s as scrollToTocHeading
13
+ };
14
+ //# sourceMappingURL=reader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reader.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;"}
@@ -0,0 +1,126 @@
1
+ "use client";
2
+ import { jsxs, jsx } from "react/jsx-runtime";
3
+ import { useRef, useEffect } from "react";
4
+ import { R as ReportContent } from "./ReportContent-c_UlJnVY.js";
5
+ function findCitationRefElement(target, root) {
6
+ let node = null;
7
+ if (target instanceof Element) node = target;
8
+ else if (target instanceof Text) node = target.parentElement;
9
+ else if (target instanceof Node) node = target.parentElement;
10
+ if (!(node instanceof Element)) return null;
11
+ const el = node.closest(".citation-ref");
12
+ if (!(el instanceof HTMLElement)) return null;
13
+ if (!root.contains(el)) return null;
14
+ return el;
15
+ }
16
+ function readCitationAttrs(el) {
17
+ var _a;
18
+ return {
19
+ index: el.getAttribute("data-index") || ((_a = el.textContent) == null ? void 0 : _a.trim()) || "",
20
+ url: el.getAttribute("data-url"),
21
+ title: el.getAttribute("data-title")
22
+ };
23
+ }
24
+ function CitationInteractive({
25
+ containerRef,
26
+ onCitationEnter,
27
+ onCitationLeave
28
+ }) {
29
+ const enterRef = useRef(onCitationEnter);
30
+ const leaveRef = useRef(onCitationLeave);
31
+ enterRef.current = onCitationEnter;
32
+ leaveRef.current = onCitationLeave;
33
+ useEffect(() => {
34
+ const root = containerRef.current;
35
+ if (!root) return;
36
+ let currentEl = null;
37
+ const emitEnter = (el) => {
38
+ currentEl = el;
39
+ const attrs = readCitationAttrs(el);
40
+ enterRef.current({ index: attrs.index, attrs, anchorEl: el });
41
+ };
42
+ const emitLeave = () => {
43
+ if (!currentEl) return;
44
+ currentEl = null;
45
+ leaveRef.current();
46
+ };
47
+ const ACTIVE_ATTR = "data-tmr-citation-active";
48
+ const onClick = (e) => {
49
+ const el = findCitationRefElement(e.target, root);
50
+ if (el) {
51
+ e.preventDefault();
52
+ e.stopPropagation();
53
+ if (el === currentEl && el.hasAttribute(ACTIVE_ATTR)) {
54
+ emitLeave();
55
+ return;
56
+ }
57
+ emitEnter(el);
58
+ return;
59
+ }
60
+ emitLeave();
61
+ };
62
+ const onKeyDown = (e) => {
63
+ if (e.key === "Escape") emitLeave();
64
+ };
65
+ root.addEventListener("click", onClick, true);
66
+ document.addEventListener("keydown", onKeyDown);
67
+ return () => {
68
+ root.removeEventListener("click", onClick, true);
69
+ document.removeEventListener("keydown", onKeyDown);
70
+ };
71
+ }, [containerRef]);
72
+ return null;
73
+ }
74
+ function ReportContentInteractive({
75
+ onCitationEnter,
76
+ onCitationLeave,
77
+ className,
78
+ html,
79
+ ...rest
80
+ }) {
81
+ const ref = useRef(null);
82
+ return /* @__PURE__ */ jsxs("div", { ref, className, children: [
83
+ /* @__PURE__ */ jsx(ReportContent, { html, ...rest }),
84
+ /* @__PURE__ */ jsx(
85
+ CitationInteractive,
86
+ {
87
+ containerRef: ref,
88
+ onCitationEnter,
89
+ onCitationLeave
90
+ }
91
+ )
92
+ ] });
93
+ }
94
+ function easeOutCubic(t) {
95
+ return 1 - (1 - t) * (1 - t) * (1 - t);
96
+ }
97
+ function scrollToTocHeading(headingId, scrollContainer, options) {
98
+ const el = scrollContainer.querySelector(
99
+ `[data-toc-id="${CSS.escape(headingId)}"]`
100
+ );
101
+ if (!el) return;
102
+ const startTop = scrollContainer.scrollTop;
103
+ const rootRect = scrollContainer.getBoundingClientRect();
104
+ const elRect = el.getBoundingClientRect();
105
+ const targetTop = startTop + (elRect.top - rootRect.top);
106
+ if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
107
+ scrollContainer.scrollTop = targetTop;
108
+ return;
109
+ }
110
+ const duration = (options == null ? void 0 : options.duration) ?? 400;
111
+ const start = performance.now();
112
+ const animate = () => {
113
+ const t = Math.min((performance.now() - start) / duration, 1);
114
+ scrollContainer.scrollTop = startTop + (targetTop - startTop) * easeOutCubic(t);
115
+ if (t < 1) requestAnimationFrame(animate);
116
+ };
117
+ requestAnimationFrame(animate);
118
+ }
119
+ export {
120
+ CitationInteractive as C,
121
+ ReportContentInteractive as R,
122
+ findCitationRefElement as f,
123
+ readCitationAttrs as r,
124
+ scrollToTocHeading as s
125
+ };
126
+ //# sourceMappingURL=scrollToTocHeading-CwapFaVP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scrollToTocHeading-CwapFaVP.js","sources":["../src/citationDom.ts","../src/components/CitationInteractive.tsx","../src/components/ReportContentInteractive.tsx","../src/toc/scrollToTocHeading.ts"],"sourcesContent":["/**\n * 从事件目标向上找到 `.citation-ref`(且必须在 root 内)。\n * SSR 阅读页事件委托与单测共用。\n */\nexport function findCitationRefElement(\n target: EventTarget | null,\n root: ParentNode,\n): HTMLElement | null {\n let node: Node | null = null;\n if (target instanceof Element) node = target;\n else if (target instanceof Text) node = target.parentElement;\n else if (target instanceof Node) node = target.parentElement;\n if (!(node instanceof Element)) return null;\n const el = node.closest('.citation-ref');\n if (!(el instanceof HTMLElement)) return null;\n if (!root.contains(el)) return null;\n return el;\n}\n\n/** 从圆标 DOM 读取 attrs(与 CitationRef renderHTML 的 data-* 对齐)。 */\nexport function readCitationAttrs(el: HTMLElement): {\n index: string;\n url: string | null;\n title: string | null;\n} {\n return {\n index: el.getAttribute('data-index') || el.textContent?.trim() || '',\n url: el.getAttribute('data-url'),\n title: el.getAttribute('data-title'),\n };\n}\n","'use client';\n\nimport { useEffect, useRef, type RefObject } from 'react';\nimport {\n findCitationRefElement,\n readCitationAttrs,\n} from '../citationDom';\nimport type {\n OnCitationEnter,\n OnCitationLeave,\n} from '../citationTypes';\n\nexport interface CitationInteractiveProps {\n /**\n * 包含 `ReportContent` HTML 的根节点(其内应有 `.citation-ref`)。\n * 用同一 ref 包住 ReportContent 即可。\n */\n containerRef: RefObject<HTMLElement | null>;\n /** 点击圆标。宿主据此 setState 打开自己的 Popover。 */\n onCitationEnter: OnCitationEnter;\n /**\n * 点正文非圆标处 / 再点同一圆标 / Escape。\n * 库立即回调;浮层开关由宿主管。\n */\n onCitationLeave: OnCitationLeave;\n}\n\n/**\n * SSR 阅读页脚注增强:只做 **click** 事件委托,不持有 open 状态。\n *\n * ```tsx\n * <CitationInteractive\n * containerRef={ref}\n * onCitationEnter={setActive}\n * onCitationLeave={() => setActive(null)}\n * />\n * ```\n */\nexport function CitationInteractive({\n containerRef,\n onCitationEnter,\n onCitationLeave,\n}: CitationInteractiveProps) {\n const enterRef = useRef(onCitationEnter);\n const leaveRef = useRef(onCitationLeave);\n enterRef.current = onCitationEnter;\n leaveRef.current = onCitationLeave;\n\n useEffect(() => {\n const root = containerRef.current;\n if (!root) return;\n\n let currentEl: HTMLElement | null = null;\n\n const emitEnter = (el: HTMLElement) => {\n currentEl = el;\n const attrs = readCitationAttrs(el);\n enterRef.current({ index: attrs.index, attrs, anchorEl: el });\n };\n\n const emitLeave = () => {\n if (!currentEl) return;\n currentEl = null;\n leaveRef.current();\n };\n\n const ACTIVE_ATTR = 'data-tmr-citation-active';\n\n const onClick = (e: MouseEvent) => {\n const el = findCitationRefElement(e.target, root);\n if (el) {\n // 捕获阶段就取消默认,避免圆标内 <a> 跳转\n e.preventDefault();\n e.stopPropagation();\n // 仅当该圆标仍处于打开态时再点才关闭(宿主可能已外部关闭,attr 已清)\n if (el === currentEl && el.hasAttribute(ACTIVE_ATTR)) {\n emitLeave();\n return;\n }\n emitEnter(el);\n return;\n }\n emitLeave();\n };\n\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'Escape') emitLeave();\n };\n\n root.addEventListener('click', onClick, true);\n document.addEventListener('keydown', onKeyDown);\n\n return () => {\n root.removeEventListener('click', onClick, true);\n document.removeEventListener('keydown', onKeyDown);\n };\n }, [containerRef]);\n\n return null;\n}\n","'use client';\n\nimport { useRef, type ComponentProps } from 'react';\nimport type { OnCitationEnter, OnCitationLeave } from '../citationTypes';\nimport { ReportContent } from '../ReportContent';\nimport { CitationInteractive } from './CitationInteractive';\n\nexport interface ReportContentInteractiveProps\n extends Omit<ComponentProps<typeof ReportContent>, 'ref'> {\n /** 点击圆标:宿主 setState 打开自己的 Popover。 */\n onCitationEnter: OnCitationEnter;\n /** 点外部 / 再点圆标 / Escape:宿主关闭。 */\n onCitationLeave: OnCitationLeave;\n}\n\n/**\n * SSR HTML 阅读 + 脚注 click 委托的便捷封装。\n * 内部:`ReportContent` + `CitationInteractive`(只报事件,仅 click)。\n */\nexport function ReportContentInteractive({\n onCitationEnter,\n onCitationLeave,\n className,\n html,\n ...rest\n}: ReportContentInteractiveProps) {\n const ref = useRef<HTMLDivElement>(null);\n\n return (\n <div ref={ref} className={className}>\n <ReportContent html={html} {...rest} />\n <CitationInteractive\n containerRef={ref}\n onCitationEnter={onCitationEnter}\n onCitationLeave={onCitationLeave}\n />\n </div>\n );\n}\n","/**\n * 目录点击 → 平滑滚到对应标题。\n *\n * 不依赖浏览器 `scrollTo({behavior:'smooth'})` / `scrollIntoView({behavior:'smooth'})`——\n * 在 flex + overflow 嵌套布局下(父级 `overflow:hidden`、自身 `flex:1; overflow-y:auto`),\n * Chrome 的 compositor 驱动 smooth scroll 会静默失效。\n *\n * 改用 `requestAnimationFrame` + easeOutCubic 缓动,直接操作 `scrollTop`,\n * 主线程一次性 400ms 动画,点击触发的单次滚动无感知性能差异。\n *\n * 尊重 `prefers-reduced-motion: reduce`——此时瞬间跳转,无动画。\n */\n\n/** 缓动:easeOutCubic。t ∈ [0, 1]。 */\nfunction easeOutCubic(t: number): number {\n return 1 - (1 - t) * (1 - t) * (1 - t);\n}\n\n/**\n * 平滑滚动(或瞬间跳转)到指定 `data-toc-id` 的标题。\n *\n * @param headingId - TOC item 的 id,对应 DOM 上 `data-toc-id` 属性值\n * @param scrollContainer - 滚动容器(如 `.scrollArea`)\n * @param options - `duration`:动画时长 ms,默认 400\n */\nexport function scrollToTocHeading(\n headingId: string,\n scrollContainer: HTMLElement,\n options?: { duration?: number },\n): void {\n const el = scrollContainer.querySelector<HTMLElement>(\n `[data-toc-id=\"${CSS.escape(headingId)}\"]`,\n );\n if (!el) return;\n\n const startTop = scrollContainer.scrollTop;\n const rootRect = scrollContainer.getBoundingClientRect();\n const elRect = el.getBoundingClientRect();\n const targetTop = startTop + (elRect.top - rootRect.top);\n\n // prefers-reduced-motion:瞬间跳转\n if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {\n scrollContainer.scrollTop = targetTop;\n return;\n }\n\n // 手动平滑滚动\n const duration = options?.duration ?? 400;\n const start = performance.now();\n const animate = () => {\n const t = Math.min((performance.now() - start) / duration, 1);\n scrollContainer.scrollTop = startTop + (targetTop - startTop) * easeOutCubic(t);\n if (t < 1) requestAnimationFrame(animate);\n };\n requestAnimationFrame(animate);\n}\n"],"names":[],"mappings":";;;;AAIO,SAAS,uBACd,QACA,MACoB;AACpB,MAAI,OAAoB;AACxB,MAAI,kBAAkB,QAAS,QAAO;AAAA,WAC7B,kBAAkB,KAAM,QAAO,OAAO;AAAA,WACtC,kBAAkB,KAAM,QAAO,OAAO;AAC/C,MAAI,EAAE,gBAAgB,SAAU,QAAO;AACvC,QAAM,KAAK,KAAK,QAAQ,eAAe;AACvC,MAAI,EAAE,cAAc,aAAc,QAAO;AACzC,MAAI,CAAC,KAAK,SAAS,EAAE,EAAG,QAAO;AAC/B,SAAO;AACT;AAGO,SAAS,kBAAkB,IAIhC;;AACA,SAAO;AAAA,IACL,OAAO,GAAG,aAAa,YAAY,OAAK,QAAG,gBAAH,mBAAgB,WAAU;AAAA,IAClE,KAAK,GAAG,aAAa,UAAU;AAAA,IAC/B,OAAO,GAAG,aAAa,YAAY;AAAA,EAAA;AAEvC;ACQO,SAAS,oBAAoB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AACF,GAA6B;AAC3B,QAAM,WAAW,OAAO,eAAe;AACvC,QAAM,WAAW,OAAO,eAAe;AACvC,WAAS,UAAU;AACnB,WAAS,UAAU;AAEnB,YAAU,MAAM;AACd,UAAM,OAAO,aAAa;AAC1B,QAAI,CAAC,KAAM;AAEX,QAAI,YAAgC;AAEpC,UAAM,YAAY,CAAC,OAAoB;AACrC,kBAAY;AACZ,YAAM,QAAQ,kBAAkB,EAAE;AAClC,eAAS,QAAQ,EAAE,OAAO,MAAM,OAAO,OAAO,UAAU,IAAI;AAAA,IAC9D;AAEA,UAAM,YAAY,MAAM;AACtB,UAAI,CAAC,UAAW;AAChB,kBAAY;AACZ,eAAS,QAAA;AAAA,IACX;AAEA,UAAM,cAAc;AAEpB,UAAM,UAAU,CAAC,MAAkB;AACjC,YAAM,KAAK,uBAAuB,EAAE,QAAQ,IAAI;AAChD,UAAI,IAAI;AAEN,UAAE,eAAA;AACF,UAAE,gBAAA;AAEF,YAAI,OAAO,aAAa,GAAG,aAAa,WAAW,GAAG;AACpD,oBAAA;AACA;AAAA,QACF;AACA,kBAAU,EAAE;AACZ;AAAA,MACF;AACA,gBAAA;AAAA,IACF;AAEA,UAAM,YAAY,CAAC,MAAqB;AACtC,UAAI,EAAE,QAAQ,SAAU,WAAA;AAAA,IAC1B;AAEA,SAAK,iBAAiB,SAAS,SAAS,IAAI;AAC5C,aAAS,iBAAiB,WAAW,SAAS;AAE9C,WAAO,MAAM;AACX,WAAK,oBAAoB,SAAS,SAAS,IAAI;AAC/C,eAAS,oBAAoB,WAAW,SAAS;AAAA,IACnD;AAAA,EACF,GAAG,CAAC,YAAY,CAAC;AAEjB,SAAO;AACT;AChFO,SAAS,yBAAyB;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAkC;AAChC,QAAM,MAAM,OAAuB,IAAI;AAEvC,SACE,qBAAC,OAAA,EAAI,KAAU,WACb,UAAA;AAAA,IAAA,oBAAC,eAAA,EAAc,MAAa,GAAG,KAAA,CAAM;AAAA,IACrC;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,cAAc;AAAA,QACd;AAAA,QACA;AAAA,MAAA;AAAA,IAAA;AAAA,EACF,GACF;AAEJ;ACxBA,SAAS,aAAa,GAAmB;AACvC,SAAO,KAAK,IAAI,MAAM,IAAI,MAAM,IAAI;AACtC;AASO,SAAS,mBACd,WACA,iBACA,SACM;AACN,QAAM,KAAK,gBAAgB;AAAA,IACzB,iBAAiB,IAAI,OAAO,SAAS,CAAC;AAAA,EAAA;AAExC,MAAI,CAAC,GAAI;AAET,QAAM,WAAW,gBAAgB;AACjC,QAAM,WAAW,gBAAgB,sBAAA;AACjC,QAAM,SAAS,GAAG,sBAAA;AAClB,QAAM,YAAY,YAAY,OAAO,MAAM,SAAS;AAGpD,MAAI,OAAO,WAAW,kCAAkC,EAAE,SAAS;AACjE,oBAAgB,YAAY;AAC5B;AAAA,EACF;AAGA,QAAM,YAAW,mCAAS,aAAY;AACtC,QAAM,QAAQ,YAAY,IAAA;AAC1B,QAAM,UAAU,MAAM;AACpB,UAAM,IAAI,KAAK,KAAK,YAAY,QAAQ,SAAS,UAAU,CAAC;AAC5D,oBAAgB,YAAY,YAAY,YAAY,YAAY,aAAa,CAAC;AAC9E,QAAI,IAAI,EAAG,uBAAsB,OAAO;AAAA,EAC1C;AACA,wBAAsB,OAAO;AAC/B;"}
package/dist/server.js CHANGED
@@ -1,5 +1,7 @@
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-DiY1iu1J.js";
2
- import { c, R, i, e, l, a, m } from "./extractToc-DiY1iu1J.js";
1
+ import { s as stabilizeMarkdown, b as baseExtensions, p as pureCodeBlock, a as pureImage, C as CitationRef, m as makeTocGetId, r as renderMathElementHtml, e as extractToc } from "./extractToc-CecK9W_w.js";
2
+ import { c, d, l, f, g } from "./extractToc-CecK9W_w.js";
3
+ import { a as applyCitationSources } from "./ReportContent-c_UlJnVY.js";
4
+ import { R, e } from "./ReportContent-c_UlJnVY.js";
3
5
  import { TableOfContents, generateTocIds } from "@tiptap/extension-table-of-contents";
4
6
  import { MarkdownManager } from "@tiptap/markdown";
5
7
  import { renderToHTMLString } from "@tiptap/static-renderer";
@@ -116,13 +118,13 @@ export {
116
118
  R as ReportContent,
117
119
  applyCitationSources,
118
120
  baseExtensions,
119
- i as blockTextHash,
121
+ d as blockTextHash,
120
122
  e as enrichMarkdownCitations,
121
123
  extractToc,
122
124
  l as lowlight,
123
125
  makeTocGetId,
124
- a as mapCommentAnchors,
125
- m as mergeCommentIntervals,
126
+ f as mapCommentAnchors,
127
+ g as mergeCommentIntervals,
126
128
  pureCodeBlock,
127
129
  pureImage,
128
130
  renderReportHtml,
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sources":["../src/renderReportHtml.ts"],"sourcesContent":["import {\n generateTocIds,\n TableOfContents,\n} from '@tiptap/extension-table-of-contents';\nimport type { JSONContent } from '@tiptap/core';\nimport { MarkdownManager } from '@tiptap/markdown';\nimport { renderToHTMLString } from '@tiptap/static-renderer';\nimport {\n applyCitationSources,\n type SourceRef,\n} from './citationUtils';\nimport { CitationRef } from './CitationRef';\nimport { baseExtensions, pureCodeBlock, pureImage } from './extensions';\nimport { renderMathElementHtml } from './math';\nimport { stabilizeMarkdown } from './stabilizeMarkdown';\nimport { extractToc, type TocItem } from './toc/extractToc';\nimport { makeTocGetId } from './toc/tocSlug';\n\n/**\n * server 端把 markdown 渲成 HTML 字符串 + 目录(纯函数,无 editor / 无 window)。\n *\n * 走官方管线 `MarkdownManager.parse → generateTocIds → renderToHTMLString`,可在\n * Server Component / ISR 里直接跑,正文 HTML + 锚点 id 进静态产物,兑现 SEO / 首屏。\n *\n * 扩展集与编辑器同源(base + 纯版 CodeBlock/Image)+ TableOfContents。TOC 扩展在 v3\n * 会扩展 Heading schema 使 id 渲染进 HTML;`generateTocIds` 是官方纯函数(无 editor /\n * 无 window,专为「renderToHTMLString 前在 server 注入锚点」设计)。\n * 锚点用 slug + 去重(makeTocGetId),跨 SSR / ISR 稳定、可分享。\n *\n * `[^n]` 由 CitationRef tokenizer 解析;`sources` 经 {@link applyCitationSources}\n * 补 url/title(不依赖 DOMParser,避免 server 内联 HTML 失效)。\n *\n * 已知边界:官方包解析内联 HTML 依赖 window.DOMParser,server 端无 window 会把\n * 内联 HTML(如 <u>)转义为字面文本。\n */\nexport interface RenderedReport {\n html: string;\n toc: TocItem[];\n /**\n * 管线是否成功。空 markdown 也是 ok。\n * parse / render 抛错时为 false;调用方可保留上一帧 HTML。\n */\n ok: boolean;\n}\n\nexport interface RenderReportHtmlOptions {\n /** 被付费墙锁住的章节标题(TOC 标 locked)。 */\n lockedTitles?: string[];\n /** 脚注来源,按 index 对齐 `[^n]`。 */\n sources?: SourceRef[];\n /**\n * 是否注入标题锚点并返回 toc。默认 true(`/p` 阅读页)。\n * 卡片预览传 false,跳过 generateTocIds。\n */\n includeToc?: boolean;\n /**\n * 流式半成品:补未闭合代码围栏。默认 false。\n * 完整落库稿不要开,以免改已发布 HTML。\n */\n stabilize?: boolean;\n}\n\n// [perf] 模块级复用单个 MarkdownManager。每次 `new MarkdownManager` 会让底层 marked\n// 全局累积扩展,parse 逐次变慢。parse 只用 schema、与 TOC 的 getId 无关,可跨调用\n// 安全复用;getId 相关的锚点注入放在 generateTocIds 阶段、用每篇新建的 extensions 处理。\nconst sharedManager = new MarkdownManager({\n extensions: [\n ...baseExtensions,\n pureCodeBlock,\n pureImage,\n CitationRef,\n TableOfContents,\n ],\n});\n\nfunction htmlFromJson(json: JSONContent): string {\n const extensions = [\n ...baseExtensions,\n pureCodeBlock,\n pureImage,\n CitationRef,\n TableOfContents.configure({ getId: makeTocGetId() }),\n ];\n return renderToHTMLString({\n extensions,\n content: json,\n options: {\n nodeMapping: {\n inlineMath: ({ node }) =>\n renderMathElementHtml(String(node.attrs?.latex ?? ''), false),\n blockMath: ({ node }) =>\n renderMathElementHtml(String(node.attrs?.latex ?? ''), true),\n },\n },\n });\n}\n\nexport function renderReportHtml(\n markdown: string,\n lockedTitlesOrOptions: string[] | RenderReportHtmlOptions = [],\n): RenderedReport {\n if (!markdown?.trim()) return { html: '', toc: [], ok: true };\n\n const options: RenderReportHtmlOptions = Array.isArray(lockedTitlesOrOptions)\n ? { lockedTitles: lockedTitlesOrOptions }\n : lockedTitlesOrOptions;\n const lockedTitles = options.lockedTitles ?? [];\n const sources = options.sources ?? [];\n const includeToc = options.includeToc !== false;\n const input = options.stabilize ? stabilizeMarkdown(markdown) : markdown;\n\n let json: JSONContent;\n try {\n json = applyCitationSources(\n sharedManager.parse(input) as JSONContent,\n sources,\n );\n } catch (e) {\n console.error(\n '[renderReportHtml] markdown parse failed:',\n (e as Error).message,\n );\n return { html: '', toc: [], ok: false };\n }\n\n if (!includeToc) {\n try {\n return { html: htmlFromJson(json), toc: [], ok: true };\n } catch (e) {\n console.error(\n '[renderReportHtml] render failed (no toc):',\n (e as Error).message,\n );\n return { html: '', toc: [], ok: false };\n }\n }\n\n // getId 是有状态去重闭包,每篇必须重置,否则锚点 id 跨调用串号。这份 extensions\n // 只喂 generateTocIds / renderToHTMLString——schema 与 sharedManager 一致(仅 getId\n // 配置不同,不影响 schema),故对 sharedManager.parse 出的 json 兼容。\n const extensions = [\n ...baseExtensions,\n pureCodeBlock,\n pureImage,\n CitationRef,\n TableOfContents.configure({ getId: makeTocGetId() }),\n ];\n try {\n const jsonWithIds = generateTocIds(json, extensions);\n const html = renderToHTMLString({\n extensions,\n content: jsonWithIds,\n options: {\n nodeMapping: {\n inlineMath: ({ node }) =>\n renderMathElementHtml(String(node.attrs?.latex ?? ''), false),\n blockMath: ({ node }) =>\n renderMathElementHtml(String(node.attrs?.latex ?? ''), true),\n },\n },\n });\n return { html, toc: extractToc(jsonWithIds, lockedTitles), ok: true };\n } catch (e) {\n // 某些 markdown 经 parse 产出的 JSON 可能在 heading 内嵌套 block 级子节点,\n // 导致 generateTocIds 的 setNodeMarkup 校验失败。降级为无 TOC 仍产出正文。\n console.error(\n '[renderReportHtml] TOC pipeline failed, rendering without heading ids:',\n (e as Error).message,\n );\n try {\n return { html: htmlFromJson(json), toc: [], ok: true };\n } catch (e2) {\n console.error(\n '[renderReportHtml] Tiptap pipeline failed:',\n (e2 as Error).message,\n );\n return { html: '', toc: [], ok: false };\n }\n }\n}\n"],"names":["e","e2"],"mappings":"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"}
1
+ {"version":3,"file":"server.js","sources":["../src/renderReportHtml.ts"],"sourcesContent":["import {\n generateTocIds,\n TableOfContents,\n} from '@tiptap/extension-table-of-contents';\nimport type { JSONContent } from '@tiptap/core';\nimport { MarkdownManager } from '@tiptap/markdown';\nimport { renderToHTMLString } from '@tiptap/static-renderer';\nimport {\n applyCitationSources,\n type SourceRef,\n} from './citationUtils';\nimport { CitationRef } from './CitationRef';\nimport { baseExtensions, pureCodeBlock, pureImage } from './extensions';\nimport { renderMathElementHtml } from './math';\nimport { stabilizeMarkdown } from './stabilizeMarkdown';\nimport { extractToc, type TocItem } from './toc/extractToc';\nimport { makeTocGetId } from './toc/tocSlug';\n\n/**\n * server 端把 markdown 渲成 HTML 字符串 + 目录(纯函数,无 editor / 无 window)。\n *\n * 走官方管线 `MarkdownManager.parse → generateTocIds → renderToHTMLString`,可在\n * Server Component / ISR 里直接跑,正文 HTML + 锚点 id 进静态产物,兑现 SEO / 首屏。\n *\n * 扩展集与编辑器同源(base + 纯版 CodeBlock/Image)+ TableOfContents。TOC 扩展在 v3\n * 会扩展 Heading schema 使 id 渲染进 HTML;`generateTocIds` 是官方纯函数(无 editor /\n * 无 window,专为「renderToHTMLString 前在 server 注入锚点」设计)。\n * 锚点用 slug + 去重(makeTocGetId),跨 SSR / ISR 稳定、可分享。\n *\n * `[^n]` 由 CitationRef tokenizer 解析;`sources` 经 {@link applyCitationSources}\n * 补 url/title(不依赖 DOMParser,避免 server 内联 HTML 失效)。\n *\n * 已知边界:官方包解析内联 HTML 依赖 window.DOMParser,server 端无 window 会把\n * 内联 HTML(如 <u>)转义为字面文本。\n */\nexport interface RenderedReport {\n html: string;\n toc: TocItem[];\n /**\n * 管线是否成功。空 markdown 也是 ok。\n * parse / render 抛错时为 false;调用方可保留上一帧 HTML。\n */\n ok: boolean;\n}\n\nexport interface RenderReportHtmlOptions {\n /** 被付费墙锁住的章节标题(TOC 标 locked)。 */\n lockedTitles?: string[];\n /** 脚注来源,按 index 对齐 `[^n]`。 */\n sources?: SourceRef[];\n /**\n * 是否注入标题锚点并返回 toc。默认 true(`/p` 阅读页)。\n * 卡片预览传 false,跳过 generateTocIds。\n */\n includeToc?: boolean;\n /**\n * 流式半成品:补未闭合代码围栏。默认 false。\n * 完整落库稿不要开,以免改已发布 HTML。\n */\n stabilize?: boolean;\n}\n\n// [perf] 模块级复用单个 MarkdownManager。每次 `new MarkdownManager` 会让底层 marked\n// 全局累积扩展,parse 逐次变慢。parse 只用 schema、与 TOC 的 getId 无关,可跨调用\n// 安全复用;getId 相关的锚点注入放在 generateTocIds 阶段、用每篇新建的 extensions 处理。\nconst sharedManager = new MarkdownManager({\n extensions: [\n ...baseExtensions,\n pureCodeBlock,\n pureImage,\n CitationRef,\n TableOfContents,\n ],\n});\n\nfunction htmlFromJson(json: JSONContent): string {\n const extensions = [\n ...baseExtensions,\n pureCodeBlock,\n pureImage,\n CitationRef,\n TableOfContents.configure({ getId: makeTocGetId() }),\n ];\n return renderToHTMLString({\n extensions,\n content: json,\n options: {\n nodeMapping: {\n inlineMath: ({ node }) =>\n renderMathElementHtml(String(node.attrs?.latex ?? ''), false),\n blockMath: ({ node }) =>\n renderMathElementHtml(String(node.attrs?.latex ?? ''), true),\n },\n },\n });\n}\n\nexport function renderReportHtml(\n markdown: string,\n lockedTitlesOrOptions: string[] | RenderReportHtmlOptions = [],\n): RenderedReport {\n if (!markdown?.trim()) return { html: '', toc: [], ok: true };\n\n const options: RenderReportHtmlOptions = Array.isArray(lockedTitlesOrOptions)\n ? { lockedTitles: lockedTitlesOrOptions }\n : lockedTitlesOrOptions;\n const lockedTitles = options.lockedTitles ?? [];\n const sources = options.sources ?? [];\n const includeToc = options.includeToc !== false;\n const input = options.stabilize ? stabilizeMarkdown(markdown) : markdown;\n\n let json: JSONContent;\n try {\n json = applyCitationSources(\n sharedManager.parse(input) as JSONContent,\n sources,\n );\n } catch (e) {\n console.error(\n '[renderReportHtml] markdown parse failed:',\n (e as Error).message,\n );\n return { html: '', toc: [], ok: false };\n }\n\n if (!includeToc) {\n try {\n return { html: htmlFromJson(json), toc: [], ok: true };\n } catch (e) {\n console.error(\n '[renderReportHtml] render failed (no toc):',\n (e as Error).message,\n );\n return { html: '', toc: [], ok: false };\n }\n }\n\n // getId 是有状态去重闭包,每篇必须重置,否则锚点 id 跨调用串号。这份 extensions\n // 只喂 generateTocIds / renderToHTMLString——schema 与 sharedManager 一致(仅 getId\n // 配置不同,不影响 schema),故对 sharedManager.parse 出的 json 兼容。\n const extensions = [\n ...baseExtensions,\n pureCodeBlock,\n pureImage,\n CitationRef,\n TableOfContents.configure({ getId: makeTocGetId() }),\n ];\n try {\n const jsonWithIds = generateTocIds(json, extensions);\n const html = renderToHTMLString({\n extensions,\n content: jsonWithIds,\n options: {\n nodeMapping: {\n inlineMath: ({ node }) =>\n renderMathElementHtml(String(node.attrs?.latex ?? ''), false),\n blockMath: ({ node }) =>\n renderMathElementHtml(String(node.attrs?.latex ?? ''), true),\n },\n },\n });\n return { html, toc: extractToc(jsonWithIds, lockedTitles), ok: true };\n } catch (e) {\n // 某些 markdown 经 parse 产出的 JSON 可能在 heading 内嵌套 block 级子节点,\n // 导致 generateTocIds 的 setNodeMarkup 校验失败。降级为无 TOC 仍产出正文。\n console.error(\n '[renderReportHtml] TOC pipeline failed, rendering without heading ids:',\n (e as Error).message,\n );\n try {\n return { html: htmlFromJson(json), toc: [], ok: true };\n } catch (e2) {\n console.error(\n '[renderReportHtml] Tiptap pipeline failed:',\n (e2 as Error).message,\n );\n return { html: '', toc: [], ok: false };\n }\n }\n}\n"],"names":["e","e2"],"mappings":"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"}