tiptap-markdown-react 0.17.1 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +171 -17
  2. package/THIRD_PARTY_LICENSES.md +45 -1
  3. package/dist/{ReportContent-MgbebxPV.js → ReportContent-B8jIzhy3.js} +8 -8
  4. package/dist/ReportContent-B8jIzhy3.js.map +1 -0
  5. package/dist/chart/columnMatching.d.ts +1 -1
  6. package/dist/chart/types.d.ts +1 -1
  7. package/dist/commentAnchor/commentMapper.d.ts +1 -1
  8. package/dist/components/EditorToolbar.d.ts +13 -2
  9. package/dist/components/EditorToolbar.dom.test.d.ts +1 -0
  10. package/dist/components/FindReplaceBar.d.ts +0 -7
  11. package/dist/components/ImageAlignTools.d.ts +19 -0
  12. package/dist/components/ImageUploadView.d.ts +10 -0
  13. package/dist/components/LinkPopover.d.ts +28 -0
  14. package/dist/components/LinkPopover.dom.test.d.ts +1 -0
  15. package/dist/components/MarkdownWysiwygEditor.d.ts +59 -1
  16. package/dist/components/TableHandles.d.ts +19 -0
  17. package/dist/components/TableHandles.dom.test.d.ts +1 -0
  18. package/dist/components/findEntry.dom.test.d.ts +1 -0
  19. package/dist/components/imageUpload.dom.test.d.ts +1 -0
  20. package/dist/extensions.d.ts +12 -0
  21. package/dist/{extractToc-BaR6LP1f.js → extractToc-CGrBMGvM.js} +433 -9
  22. package/dist/extractToc-CGrBMGvM.js.map +1 -0
  23. package/dist/findReplace.d.ts +18 -4
  24. package/dist/icons/index.d.ts +15 -0
  25. package/dist/imageAlign.d.ts +29 -0
  26. package/dist/imageAlign.dom.test.d.ts +1 -0
  27. package/dist/imageCaption.d.ts +12 -0
  28. package/dist/imageMarkdown.d.ts +86 -0
  29. package/dist/imageResize.dom.test.d.ts +1 -0
  30. package/dist/imageUpload.d.ts +63 -0
  31. package/dist/index.d.ts +11 -2
  32. package/dist/index.js +2354 -537
  33. package/dist/index.js.map +1 -1
  34. package/dist/labels.d.ts +93 -1
  35. package/dist/linkEditing.d.ts +45 -0
  36. package/dist/linkEditing.test.d.ts +1 -0
  37. package/dist/paragraphMarkdown.d.ts +19 -0
  38. package/dist/paragraphStyles.d.ts +38 -0
  39. package/dist/paragraphStyles.dom.test.d.ts +1 -0
  40. package/dist/reader.js +2 -2
  41. package/dist/resetImageSize.d.ts +12 -0
  42. package/dist/{scrollToTocHeading-BTQWajGD.js → scrollToTocHeading-B2QmL4wi.js} +2 -2
  43. package/dist/{scrollToTocHeading-BTQWajGD.js.map → scrollToTocHeading-B2QmL4wi.js.map} +1 -1
  44. package/dist/server.d.ts +1 -1
  45. package/dist/server.js +4 -4
  46. package/dist/style-inline.css +932 -180
  47. package/dist/style.css +932 -180
  48. package/dist/tableSelection.d.ts +25 -5
  49. package/package.json +5 -2
  50. package/dist/ReportContent-MgbebxPV.js.map +0 -1
  51. package/dist/extractToc-BaR6LP1f.js.map +0 -1
@@ -1,11 +1,14 @@
1
1
  import { AnyExtension, Editor } from '@tiptap/react';
2
+ import { ResizableNodeViewDirection } from '@tiptap/core';
2
3
  import { SourceRef } from '../citationUtils';
3
4
  import { RenderCitation } from '../citationTypes';
4
5
  import { CommentClickPayload, CommentRef } from '../commentAnchor/commentTypes';
6
+ import { ImageUploadConfig } from '../imageUpload';
5
7
  import { CodeBlockLabels, FindLabels, ShortcutLabels, SlashMenuLabels } from '../labels';
6
8
  import { PendingAnchor } from '../pendingAnchor';
7
9
  import { SelectionKind } from '../selectionKind';
8
10
  import { TocItem } from '../toc/extractToc';
11
+ export declare const ImageWithConfirmDelete: import('@tiptap/core').Node<import('@tiptap/extension-image').ImageOptions, any>;
9
12
  /** 浮动条相对其定位上下文的偏移(数字按 px)。内部路径的上下文是编辑器,portal 路径是宿主容器。 */
10
13
  export interface FindBarOffset {
11
14
  top?: number | string;
@@ -32,6 +35,37 @@ export interface MarkdownWysiwygEditorHandle {
32
35
  openFind: () => void;
33
36
  /** 收起自带浮动条,清空高亮并把焦点还给编辑器。findBar={false} 时不生效。 */
34
37
  closeFind: () => void;
38
+ /**
39
+ * 反转自带浮动条:关着就开、开着就关。工具栏那个带 active 态的放大镜接它
40
+ * (`onSearch={() => handle.current?.toggleFind()}`)。findBar={false} 时不生效。
41
+ */
42
+ toggleFind: () => void;
43
+ }
44
+ /** 图片缩放的透传选项(官方 `@tiptap/extension-image` 的 resize 配置)。 */
45
+ export interface ImageResizeOptions {
46
+ /**
47
+ * 手柄方向,默认左右两条竖条(对齐官方 image-upload-node demo 的形态)。
48
+ * 例:`['top-right', 'bottom-right']` 只留右侧两角;八个方向全给则四角 + 四边。
49
+ */
50
+ directions?: ResizableNodeViewDirection[];
51
+ /** 最小宽(px),默认 80。 */
52
+ minWidth?: number;
53
+ /** 最小高(px),默认 80。 */
54
+ minHeight?: number;
55
+ /**
56
+ * 最大宽(px)。**默认跟随编辑器内容区宽度**(拖不出布局,窗口变化时在下次拖拽前刷新
57
+ * 上限);显式传值则以此为准。官方 `Image` 的 resize 没有 max 入口,由本库注入
58
+ * `ResizableNodeView.maxSize`(该字段官方是公开的,拖拽约束每次都会读)。
59
+ */
60
+ maxWidth?: number;
61
+ /** 最大高(px)。默认不单独限制(等比缩放时高度随宽度走)。 */
62
+ maxHeight?: number;
63
+ /**
64
+ * 是否始终锁定宽高比,默认 true(本库默认)。等比下"编辑态精确尺寸"与只读渲染的
65
+ * 按比例自适应(CSS `height: auto`)数学等价;显式关掉后,非等比拖出的图在只读
66
+ * 渲染里会按原图比例回弹——markdown 里存的仍是精确值。
67
+ */
68
+ alwaysPreserveAspectRatio?: boolean;
35
69
  }
36
70
  export interface MarkdownWysiwygEditorProps {
37
71
  /** 初始 markdown 内容。 */
@@ -124,7 +158,7 @@ export interface MarkdownWysiwygEditorProps {
124
158
  */
125
159
  findBar?: boolean;
126
160
  /**
127
- * 把自带的浮动条挂到宿主的容器里(antd `getPopupContainer` 那一套):给元素或返回元素的函数
161
+ * 把自带的浮动条挂到宿主的容器里(popup container 模式):给元素或返回元素的函数
128
162
  * 即可,开合状态与 Cmd/Ctrl+F 仍归库——只换挂载点。适合条子被 `overflow: hidden` 祖先裁掉、
129
163
  * 或想让它落在自己的头部 / 侧栏里。挂进去后**定位由宿主负责**(库不再加绝对定位),
130
164
  * 且容器若在主题子树之外,记得把 `--tmr-*` 变量也带到那里。
@@ -145,6 +179,13 @@ export interface MarkdownWysiwygEditorProps {
145
179
  findShortcut?: boolean;
146
180
  /** 查找替换浮动条的本地化文案。 */
147
181
  findLabels?: Partial<FindLabels>;
182
+ /**
183
+ * 浮动条开合状态变化的通知(条子开着时 `handle.openFind()`、Esc / × 关掉都会触发)。
184
+ * 典型用法是驱动别处的入口按钮:`<EditorToolbar onSearch={open} searchActive={isOpen} />`。
185
+ * 只在真正变化时回调,挂载时不会先报一次 `false`;报的是「条子真的在屏幕上」——
186
+ * `findBar={false}`(宿主自己摆条子)时恒为 `false`,不会因为 `openFind()` 空转就亮起入口。
187
+ */
188
+ onFindOpenChange?: (open: boolean) => void;
148
189
  /**
149
190
  * 启用快捷键抽屉:编辑器右下角出现键盘悬浮按钮,点开为
150
191
  * 「格式 / 快捷键 / Markdown」三列对照抽屉(默认 true,只读态隐藏)。
@@ -168,6 +209,23 @@ export interface MarkdownWysiwygEditorProps {
168
209
  slashMenu?: boolean;
169
210
  /** 斜杠菜单的本地化文案。 */
170
211
  slashMenuLabels?: Partial<SlashMenuLabels>;
212
+ /**
213
+ * 图片上传(对齐官方 ImageUploadNode 的交互):配置后工具栏的图片按钮变成
214
+ * 「插入上传块」——文档里出现拖拽 / 点击的占位块,上传进度就地显示,全部成功后就地
215
+ * 替换为图片。`upload` 是唯一必填项(网络 I/O 归宿主),签名 `(file, onProgress,
216
+ * signal) => Promise<url>`;收 `signal` 后宿主应在移除 / 清空 / 占位块被删时中断请求。
217
+ *
218
+ * 挂在这里而不是工具栏的 `onImageUpload`:按钮的行为由「编辑器里有没有注册
219
+ * imageUpload 扩展」决定,两处都传会以本配置为准。未配置时工具栏走旧的直传路径
220
+ * (点按钮直接开文件框 → 上传 → 插图片)。仅初始化时生效。
221
+ */
222
+ imageUpload?: ImageUploadConfig;
223
+ /**
224
+ * 图片缩放(默认开):拖手柄改尺寸,松手把整数像素写进节点 attrs;markdown 里带尺寸的
225
+ * 图以 `<img src alt width height>` 保真(没拖过的图保持标准 `![alt](url)`,见
226
+ * imageMarkdown.ts)。传 `false` 关闭;传对象透传官方 resize 配置。仅初始化时生效。
227
+ */
228
+ imageResize?: boolean | ImageResizeOptions;
171
229
  }
172
230
  /**
173
231
  * 所见即所得 Markdown 编辑器(基于 Tiptap v3 + 官方 @tiptap/markdown)。
@@ -0,0 +1,19 @@
1
+ import { Editor } from '@tiptap/core';
2
+ import { ToolbarLabels } from '../labels';
3
+ export interface TableHandlesProps {
4
+ /** 编辑器实例。只读态不出手柄。 */
5
+ editor: Editor;
6
+ /** 已合并默认值的工具栏文案(行列菜单项与两个手柄的 aria-label 都取自它)。 */
7
+ labels: ToolbarLabels;
8
+ }
9
+ /**
10
+ * 表格的悬停手柄(Notion 式):鼠标落在单元格上时,行左缘出一条竖胶囊(⋮)、表格上方出一条
11
+ * 横胶囊(⋯),点开就是该行 / 该列的操作菜单。
12
+ *
13
+ * 这是原来「表内右键出菜单」的替代——右键只保留**多格选区**的批量增删(手柄只覆盖单行单列,
14
+ * 那个场景手柄表达不了)。命令全部复用 `tableSelection.ts`,与工具栏的「插入表格」共用一套。
15
+ *
16
+ * 手柄是 fixed 定位、渲染在 body 的 portal 里:它要贴着单元格边缘,而工具栏并不在编辑器 DOM
17
+ * 内。滚动 / 缩放时重新量一遍悬停的那颗单元格,让手柄跟着走。
18
+ */
19
+ export declare function TableHandles({ editor, labels }: TableHandlesProps): import('react').ReactPortal | null;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -38,9 +38,21 @@ export declare const pureCodeBlock: import('@tiptap/core').Node<import('@tiptap/
38
38
  * 纯版块级图片(无删除快捷键):server / 预览用。
39
39
  * inline:false(官方默认)——研报里图片均为大图独占一行,块级图片可直接作为 doc
40
40
  * 顶层子节点,避免「inline image 裸挂 doc 顶层」被 generateTocIds 严格校验判非法。
41
+ *
42
+ * markdown 往返接 imageMarkdown:带尺寸 / 对齐 / 描述(图注)的图序列化为
43
+ * `<img …>` / `<figure>…</figure>`、解析时自建 tokenizer 认领——官方解析内联 HTML
44
+ * 依赖 window.DOMParser,server 端没有 window,不认领会把它转义成字面文本
45
+ * (见 imageMarkdown.ts 头注)。
41
46
  */
42
47
  export declare const pureImage: import('@tiptap/core').Node<import('@tiptap/extension-image').ImageOptions, any>;
43
48
  export { pureChart, reportChart } from './chart/ChartExtension';
49
+ /**
50
+ * 段落 / 标题的 markdown 版本:带段落级样式(对齐 / 缩进 / 行高)时序列化为
51
+ * `<p style="…">` / `<hN style="…">`,无样式保持原生形式(见 paragraphMarkdown.ts)。
52
+ * StarterKit 里这两个节点要关掉,改用下面这份(同 CodeBlock / Image 的分层做法)。
53
+ */
54
+ export declare const MarkdownParagraph: import('@tiptap/core').Node<import('@tiptap/extension-paragraph').ParagraphOptions, any>;
55
+ export declare const MarkdownHeading: import('@tiptap/core').Node<import('@tiptap/extension-heading').HeadingOptions, any>;
44
56
  /**
45
57
  * 三处共用的纯 schema 扩展——**不含 CodeBlock / Image / CitationRef / Chart**(见上方边界说明)。
46
58
  * 编辑器:`[...baseExtensions, 增强CodeBlock, 增强Image, createCitationRef(), createChart(), Markdown]`
@@ -1,18 +1,21 @@
1
- import { Node, mergeAttributes, Mark } from "@tiptap/core";
1
+ import { Node, mergeAttributes, Mark, Extension } from "@tiptap/core";
2
2
  import Code from "@tiptap/extension-code";
3
3
  import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight";
4
+ import Heading from "@tiptap/extension-heading";
4
5
  import Highlight from "@tiptap/extension-highlight";
5
6
  import Image from "@tiptap/extension-image";
7
+ import Paragraph from "@tiptap/extension-paragraph";
6
8
  import Subscript from "@tiptap/extension-subscript";
7
9
  import Superscript from "@tiptap/extension-superscript";
8
10
  import { TableKit } from "@tiptap/extension-table";
9
11
  import TaskItem from "@tiptap/extension-task-item";
10
12
  import TaskList from "@tiptap/extension-task-list";
13
+ import TextAlign from "@tiptap/extension-text-align";
11
14
  import { Color, TextStyle, FontSize } from "@tiptap/extension-text-style";
12
15
  import StarterKit from "@tiptap/starter-kit";
13
16
  import { createLowlight, common } from "lowlight";
14
17
  import { Plugin, PluginKey } from "@tiptap/pm/state";
15
- import { p as parseChartFenceBody, s as serializeChartPayload, n as normalizeChartMarkdown } from "./ReportContent-MgbebxPV.js";
18
+ import { p as parseChartFenceBody, s as serializeChartPayload, n as normalizeChartMarkdown } from "./ReportContent-B8jIzhy3.js";
16
19
  import { InlineMath, BlockMath } from "@tiptap/extension-mathematics";
17
20
  import katex from "katex";
18
21
  const CitationRef = Node.create({
@@ -580,6 +583,176 @@ function parseCodeTokenAsChartOrCodeBlock(token, helpers, codeBlockName = "codeB
580
583
  token.text ? [helpers.createTextNode(token.text)] : []
581
584
  );
582
585
  }
586
+ const IMAGE_ALIGN_VALUES = /* @__PURE__ */ new Set(["left", "center", "right"]);
587
+ function normalizeImageAlign(value) {
588
+ return typeof value === "string" && IMAGE_ALIGN_VALUES.has(value) ? value : null;
589
+ }
590
+ const imageAlignAttribute = {
591
+ default: null,
592
+ parseHTML: (element) => normalizeImageAlign(element.getAttribute("data-align")),
593
+ renderHTML: (attributes) => {
594
+ const align = normalizeImageAlign(attributes.align);
595
+ return align ? { "data-align": align } : {};
596
+ }
597
+ };
598
+ const imageCaptionAttribute = {
599
+ default: null,
600
+ parseHTML: (element) => {
601
+ var _a;
602
+ const figure = element.closest("figure");
603
+ return normalizeImageCaption(
604
+ ((_a = figure == null ? void 0 : figure.querySelector("figcaption")) == null ? void 0 : _a.textContent) ?? null
605
+ );
606
+ },
607
+ renderHTML: () => ({})
608
+ };
609
+ function imageNodeHtml(output) {
610
+ const caption = normalizeImageCaption(output.attrs.caption);
611
+ if (!caption) return ["img", output.htmlAttributes];
612
+ return [
613
+ "figure",
614
+ { class: "tmr-image-figure" },
615
+ ["img", output.htmlAttributes],
616
+ ["figcaption", { class: "tmr-image-caption" }, caption]
617
+ ];
618
+ }
619
+ function imageFigureParseRule() {
620
+ return {
621
+ tag: "figure",
622
+ getAttrs: (element) => {
623
+ const img = element.querySelector("img");
624
+ if (!img) return false;
625
+ const caption = element.querySelector("figcaption");
626
+ return {
627
+ src: img.getAttribute("src"),
628
+ alt: img.getAttribute("alt"),
629
+ title: img.getAttribute("title"),
630
+ width: toPixel(img.getAttribute("width")),
631
+ height: toPixel(img.getAttribute("height")),
632
+ align: normalizeImageAlign(img.getAttribute("data-align")),
633
+ caption: normalizeImageCaption((caption == null ? void 0 : caption.textContent) ?? null)
634
+ };
635
+ }
636
+ };
637
+ }
638
+ function attrValue(attrs, name) {
639
+ const pattern = new RegExp(
640
+ `(?:^|\\s)${name}\\s*=\\s*(?:"([^"]*)"|'([^']*)'|([^\\s"'=<>]+))`,
641
+ "i"
642
+ );
643
+ const match = pattern.exec(attrs);
644
+ if (!match) return null;
645
+ return match[1] ?? match[2] ?? match[3] ?? null;
646
+ }
647
+ const ENTITIES = {
648
+ "&amp;": "&",
649
+ "&lt;": "<",
650
+ "&gt;": ">",
651
+ "&quot;": '"',
652
+ "&#39;": "'"
653
+ };
654
+ function decodeEntities(text) {
655
+ return text.replace(/&(?:amp|lt|gt|quot|#39);/g, (m) => ENTITIES[m] ?? m);
656
+ }
657
+ function encodeAttr$1(value) {
658
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
659
+ }
660
+ function encodeText(value) {
661
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
662
+ }
663
+ function normalizeImageCaption(value) {
664
+ if (typeof value !== "string") return null;
665
+ const text = value.trim();
666
+ return text === "" ? null : text;
667
+ }
668
+ function toPixel(value) {
669
+ if (value == null) return null;
670
+ const n = Number(value);
671
+ return Number.isFinite(n) && n > 0 ? Math.round(n) : null;
672
+ }
673
+ function imageRenderMarkdown(node) {
674
+ const attrs = node.attrs ?? {};
675
+ const src = typeof attrs.src === "string" ? attrs.src : "";
676
+ const alt = typeof attrs.alt === "string" ? attrs.alt : "";
677
+ const title = typeof attrs.title === "string" ? attrs.title : "";
678
+ const width = toPixel(attrs.width == null ? null : String(attrs.width));
679
+ const height = toPixel(attrs.height == null ? null : String(attrs.height));
680
+ const align = normalizeImageAlign(attrs.align);
681
+ const caption = normalizeImageCaption(attrs.caption);
682
+ if (width == null && height == null && !align && !caption) {
683
+ return title ? `![${alt}](${src} "${title}")` : `![${alt}](${src})`;
684
+ }
685
+ const parts = [`src="${encodeAttr$1(src)}"`];
686
+ if (alt) parts.push(`alt="${encodeAttr$1(alt)}"`);
687
+ if (title) parts.push(`title="${encodeAttr$1(title)}"`);
688
+ if (width != null) parts.push(`width="${width}"`);
689
+ if (height != null) parts.push(`height="${height}"`);
690
+ if (align) parts.push(`data-align="${align}"`);
691
+ const imgTag = `<img ${parts.join(" ")}>`;
692
+ if (!caption) return imgTag;
693
+ return `<figure>
694
+ ${imgTag}
695
+ <figcaption>${encodeText(caption)}</figcaption>
696
+ </figure>`;
697
+ }
698
+ function imageTokenFromImgTag(raw, attrsString, caption = null) {
699
+ const srcValue = attrValue(attrsString, "src");
700
+ if (!srcValue) return void 0;
701
+ const alt = attrValue(attrsString, "alt");
702
+ const title = attrValue(attrsString, "title");
703
+ return {
704
+ type: "image",
705
+ raw,
706
+ src: decodeEntities(srcValue),
707
+ alt: alt == null ? null : decodeEntities(alt),
708
+ title: title == null ? null : decodeEntities(title),
709
+ width: toPixel(attrValue(attrsString, "width")),
710
+ height: toPixel(attrValue(attrsString, "height")),
711
+ align: normalizeImageAlign(attrValue(attrsString, "data-align")),
712
+ caption
713
+ };
714
+ }
715
+ const imageMarkdownTokenizer = {
716
+ name: "image",
717
+ level: "block",
718
+ start(src) {
719
+ const figure = /<figure[\s>]/i.exec(src);
720
+ const img = /<img[\s>]/i.exec(src);
721
+ const indexes = [figure == null ? void 0 : figure.index, img == null ? void 0 : img.index].filter(
722
+ (index) => index != null
723
+ );
724
+ return indexes.length ? Math.min(...indexes) : -1;
725
+ },
726
+ tokenize(src) {
727
+ const figure = /^<figure\s*>([\s\S]*?)<\/figure\s*>/i.exec(src);
728
+ if (figure) {
729
+ const inner = figure[1] ?? "";
730
+ const img = /<img\s+([^>]*?)\s*\/?>/.exec(inner);
731
+ if (!img) return void 0;
732
+ const cap = /<figcaption[^>]*>([\s\S]*?)<\/figcaption\s*>/i.exec(inner);
733
+ const caption = cap ? normalizeImageCaption(
734
+ decodeEntities((cap[1] ?? "").replace(/<[^>]*>/g, ""))
735
+ ) : null;
736
+ return imageTokenFromImgTag(figure[0], img[1] ?? "", caption);
737
+ }
738
+ const match = /^<img\s+([^>]*?)\s*\/?>/.exec(src);
739
+ if (!match) return void 0;
740
+ return imageTokenFromImgTag(match[0], match[1] ?? "");
741
+ }
742
+ };
743
+ function imageParseMarkdown(token, helpers) {
744
+ const src = typeof token.src === "string" ? token.src : token.href ?? null;
745
+ const alt = typeof token.alt === "string" ? token.alt : token.text ?? null;
746
+ return helpers.createNode("image", {
747
+ src,
748
+ alt,
749
+ title: token.title ?? null,
750
+ width: token.width ?? null,
751
+ height: token.height ?? null,
752
+ align: token.align ?? null,
753
+ caption: normalizeImageCaption(token.caption)
754
+ });
755
+ }
583
756
  const KATEX_OPTIONS = { throwOnError: false };
584
757
  let mathClickListener = null;
585
758
  function subscribeMathClick(listener) {
@@ -683,6 +856,196 @@ $$`;
683
856
  katexOptions: { ...KATEX_OPTIONS, displayMode: true },
684
857
  onClick: (node, pos) => emitClick("block", node, pos)
685
858
  });
859
+ const INDENT_STEP_EM = 2;
860
+ const TEXT_ALIGN_VALUES = /* @__PURE__ */ new Set(["left", "center", "right", "justify"]);
861
+ function encodeAttr(value) {
862
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
863
+ }
864
+ const KNOWN_STYLE_KEYS = ["text-align", "margin-left", "line-height"];
865
+ function paragraphStyleString(attrs) {
866
+ const parts = [];
867
+ const align = typeof attrs.textAlign === "string" ? attrs.textAlign : null;
868
+ if (align && TEXT_ALIGN_VALUES.has(align)) {
869
+ parts.push(`text-align: ${align}`);
870
+ }
871
+ const indent = Number(attrs.indent);
872
+ if (Number.isFinite(indent) && indent > 0) {
873
+ parts.push(`margin-left: ${indent * INDENT_STEP_EM}em`);
874
+ }
875
+ const lineHeight = Number(attrs.lineHeight);
876
+ if (Number.isFinite(lineHeight) && lineHeight > 0) {
877
+ parts.push(`line-height: ${lineHeight}`);
878
+ }
879
+ return parts.join("; ");
880
+ }
881
+ function parseParagraphStyleAttrs(style) {
882
+ const attrs = {};
883
+ for (const entry of style.split(";")) {
884
+ const index = entry.indexOf(":");
885
+ if (index < 0) continue;
886
+ const key = entry.slice(0, index).trim().toLowerCase();
887
+ const value = entry.slice(index + 1).trim();
888
+ if (!value) continue;
889
+ if (key === "text-align" && TEXT_ALIGN_VALUES.has(value)) {
890
+ attrs.textAlign = value;
891
+ } else if (key === "margin-left") {
892
+ const match = /^([\d.]+)em$/.exec(value);
893
+ const em = match ? Number.parseFloat(match[1]) : NaN;
894
+ const level = Number.isFinite(em) && em > 0 ? Math.round(em / INDENT_STEP_EM) : 0;
895
+ if (level > 0) attrs.indent = level;
896
+ } else if (key === "line-height") {
897
+ const parsed = Number.parseFloat(value);
898
+ if (Number.isFinite(parsed) && parsed > 0) attrs.lineHeight = parsed;
899
+ }
900
+ }
901
+ return attrs;
902
+ }
903
+ function hasKnownStyle(style) {
904
+ const lowered = style.toLowerCase();
905
+ return KNOWN_STYLE_KEYS.some((key) => lowered.includes(`${key}:`));
906
+ }
907
+ function paragraphRenderMarkdown(node, helpers) {
908
+ const children = helpers.renderChildren(node.content ?? []);
909
+ const style = paragraphStyleString(
910
+ node.attrs ?? {}
911
+ );
912
+ if (!style) return children;
913
+ return `<p style="${encodeAttr(style)}">${children}</p>`;
914
+ }
915
+ function headingRenderMarkdown(node, helpers) {
916
+ var _a;
917
+ const children = helpers.renderChildren(node.content ?? []);
918
+ const level = Math.min(
919
+ Math.max(Number((_a = node.attrs) == null ? void 0 : _a.level) || 1, 1),
920
+ 6
921
+ );
922
+ const style = paragraphStyleString(
923
+ node.attrs ?? {}
924
+ );
925
+ if (!style) return `${"#".repeat(level)} ${children}`;
926
+ return `<h${level} style="${encodeAttr(style)}">${children}</h${level}>`;
927
+ }
928
+ const paragraphMarkdownTokenizer = {
929
+ name: "paragraph",
930
+ level: "block",
931
+ start(src) {
932
+ const match = /<(?:p|h[1-6])\s+style=/i.exec(src);
933
+ return match ? match.index : -1;
934
+ },
935
+ tokenize(src, _tokens, lexer) {
936
+ const match = /^<(p|h([1-6]))\s+style="([^"]*)"\s*>([\s\S]*?)<\/\1\s*>/i.exec(
937
+ src
938
+ );
939
+ if (!match) return void 0;
940
+ const [, tag, headingLevel, style, inner] = match;
941
+ if (!hasKnownStyle(style)) return void 0;
942
+ return {
943
+ type: "paragraph",
944
+ raw: match[0],
945
+ tag,
946
+ hLevel: headingLevel ? Number(headingLevel) : null,
947
+ style,
948
+ // 行内内容交给 marked 的 lexer 解析:bold / link 等照常还原(与 sup/sub 同一手法)
949
+ tokens: lexer.inlineTokens(inner ?? "")
950
+ };
951
+ }
952
+ };
953
+ const ROW_BLOCK_TYPES = /* @__PURE__ */ new Set(["image", "chart"]);
954
+ function paragraphParseMarkdown(token, helpers) {
955
+ const attrs = parseParagraphStyleAttrs(String(token.style ?? ""));
956
+ const content = helpers.parseInline(token.tokens ?? []);
957
+ const blockOnly = content.length > 0 && Object.keys(attrs).length === 0 && content.every((child) => ROW_BLOCK_TYPES.has(String(child.type)));
958
+ if (blockOnly) return content;
959
+ if (token.hLevel) {
960
+ return helpers.createNode("heading", { level: token.hLevel, ...attrs }, content);
961
+ }
962
+ return helpers.createNode("paragraph", attrs, content);
963
+ }
964
+ function parseIndentValue(value, stepEm) {
965
+ if (!value) return null;
966
+ const match = /^([\d.]+)em$/.exec(value.trim());
967
+ if (!match) return null;
968
+ const em = Number.parseFloat(match[1]);
969
+ if (!Number.isFinite(em) || em <= 0) return null;
970
+ const level = Math.round(em / stepEm);
971
+ return level > 0 ? level : null;
972
+ }
973
+ function parseLineHeightValue(value) {
974
+ if (!value) return null;
975
+ const parsed = Number.parseFloat(value);
976
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
977
+ }
978
+ function clampIndent(level, max) {
979
+ if (!Number.isFinite(level) || level <= 0) return 0;
980
+ return Math.min(Math.round(level), max);
981
+ }
982
+ const ParagraphStyles = Extension.create({
983
+ name: "paragraphStyles",
984
+ addOptions() {
985
+ return {
986
+ types: [],
987
+ indentStepEm: 2,
988
+ maxIndent: 8
989
+ };
990
+ },
991
+ addGlobalAttributes() {
992
+ return [
993
+ {
994
+ types: this.options.types,
995
+ attributes: {
996
+ indent: {
997
+ default: null,
998
+ parseHTML: (element) => parseIndentValue(element.style.marginLeft, this.options.indentStepEm),
999
+ renderHTML: (attributes) => {
1000
+ const level = clampIndent(
1001
+ Number(attributes.indent),
1002
+ this.options.maxIndent
1003
+ );
1004
+ if (level === 0) return {};
1005
+ return {
1006
+ style: `margin-left: ${level * this.options.indentStepEm}em`
1007
+ };
1008
+ }
1009
+ },
1010
+ lineHeight: {
1011
+ default: null,
1012
+ parseHTML: (element) => parseLineHeightValue(element.style.lineHeight),
1013
+ renderHTML: (attributes) => {
1014
+ const value = Number(attributes.lineHeight);
1015
+ if (!Number.isFinite(value) || value <= 0) return {};
1016
+ return { style: `line-height: ${value}` };
1017
+ }
1018
+ }
1019
+ }
1020
+ }
1021
+ ];
1022
+ },
1023
+ addCommands() {
1024
+ const runOnTypes = (attrs) => (props) => this.options.types.map((type) => props.commands.updateAttributes(type, attrs)).some((response) => response);
1025
+ const currentIndent = ({ editor }) => {
1026
+ for (const type of this.options.types) {
1027
+ const value = editor.getAttributes(type).indent;
1028
+ if (value != null) return Number(value) || 0;
1029
+ }
1030
+ return 0;
1031
+ };
1032
+ return {
1033
+ setIndent: (level) => (props) => {
1034
+ const next = clampIndent(Number(level), this.options.maxIndent);
1035
+ return runOnTypes({ indent: next || null })(props);
1036
+ },
1037
+ increaseIndent: () => (props) => runOnTypes({
1038
+ indent: clampIndent(currentIndent(props) + 1, this.options.maxIndent) || null
1039
+ })(props),
1040
+ decreaseIndent: () => (props) => runOnTypes({
1041
+ indent: clampIndent(currentIndent(props) - 1, this.options.maxIndent) || null
1042
+ })(props),
1043
+ setLineHeight: (value) => (props) => runOnTypes({
1044
+ lineHeight: value == null ? null : parseLineHeightValue(String(value))
1045
+ })(props)
1046
+ };
1047
+ }
1048
+ });
686
1049
  const InlineCode = Code.extend({
687
1050
  excludes: ""
688
1051
  });
@@ -739,10 +1102,61 @@ const MarkdownSubscript = Subscript.extend({
739
1102
  const pureCodeBlock = CodeBlockLowlight.extend({
740
1103
  parseMarkdown: (token, helpers) => parseCodeTokenAsChartOrCodeBlock(token, helpers, "codeBlock")
741
1104
  }).configure({ lowlight });
742
- const pureImage = Image.configure({ inline: false });
1105
+ const pureImage = Image.extend({
1106
+ addAttributes() {
1107
+ var _a;
1108
+ return {
1109
+ ...((_a = Image.config.addAttributes) == null ? void 0 : _a.call(this)) ?? {},
1110
+ align: imageAlignAttribute,
1111
+ caption: imageCaptionAttribute
1112
+ };
1113
+ },
1114
+ parseHTML() {
1115
+ return [
1116
+ imageFigureParseRule(),
1117
+ {
1118
+ tag: this.options.allowBase64 ? "img[src]" : 'img[src]:not([src^="data:"])'
1119
+ }
1120
+ ];
1121
+ },
1122
+ renderHTML({ node, HTMLAttributes }) {
1123
+ return imageNodeHtml({
1124
+ attrs: node.attrs,
1125
+ htmlAttributes: mergeAttributes(
1126
+ this.options.HTMLAttributes,
1127
+ HTMLAttributes
1128
+ )
1129
+ });
1130
+ },
1131
+ renderMarkdown: imageRenderMarkdown,
1132
+ parseMarkdown: imageParseMarkdown,
1133
+ markdownTokenizer: imageMarkdownTokenizer
1134
+ }).configure({ inline: false });
1135
+ const MarkdownParagraph = Paragraph.extend({
1136
+ renderMarkdown: paragraphRenderMarkdown,
1137
+ markdownTokenizer: paragraphMarkdownTokenizer,
1138
+ parseMarkdown: paragraphParseMarkdown
1139
+ });
1140
+ const MarkdownHeading = Heading.extend({
1141
+ renderMarkdown: headingRenderMarkdown
1142
+ });
743
1143
  const baseExtensions = [
744
- // 禁用 StarterKit 内置 codeBlock / code,统一改用 lowlight 代码块 + 可共存的 InlineCode
745
- StarterKit.configure({ codeBlock: false, code: false }),
1144
+ // 禁用 StarterKit 内置 codeBlock / code(改用 lowlight 代码块 + 可共存的 InlineCode)
1145
+ // 与 paragraph / heading(改用带 markdown 样式往返的版本,见上)
1146
+ StarterKit.configure({
1147
+ codeBlock: false,
1148
+ code: false,
1149
+ paragraph: false,
1150
+ heading: false
1151
+ }),
1152
+ MarkdownParagraph,
1153
+ MarkdownHeading,
1154
+ // 段落级样式:文字对齐用官方扩展(免费);缩进 / 行高自研(官方无此扩展)。
1155
+ // 三者都往节点 style 上写片段,tiptap 的 mergeAttributes 对 style 做拼接合并。
1156
+ TextAlign.configure({ types: [MarkdownParagraph.name, MarkdownHeading.name] }),
1157
+ ParagraphStyles.configure({
1158
+ types: [MarkdownParagraph.name, MarkdownHeading.name]
1159
+ }),
746
1160
  InlineCode,
747
1161
  MarkdownTextStyle,
748
1162
  Color.configure({ types: [TextStyle.name] }),
@@ -840,7 +1254,9 @@ function extractToc(doc, lockedTitles = []) {
840
1254
  return items;
841
1255
  }
842
1256
  export {
1257
+ subscribeMathClick as A,
843
1258
  CitationRef as C,
1259
+ ParagraphStyles as P,
844
1260
  pureCodeBlock as a,
845
1261
  baseExtensions as b,
846
1262
  pureImage as c,
@@ -854,10 +1270,18 @@ export {
854
1270
  parseCodeTokenAsChartOrCodeBlock as k,
855
1271
  lowlight as l,
856
1272
  makeTocGetId as m,
857
- renderMathInto as n,
858
- subscribeMathClick as o,
1273
+ normalizeImageAlign as n,
1274
+ imageMarkdownTokenizer as o,
859
1275
  prepareChartMarkdown as p,
1276
+ imageParseMarkdown as q,
860
1277
  renderMathElementHtml as r,
861
- stabilizeMarkdown as s
1278
+ stabilizeMarkdown as s,
1279
+ imageRenderMarkdown as t,
1280
+ imageNodeHtml as u,
1281
+ imageFigureParseRule as v,
1282
+ imageCaptionAttribute as w,
1283
+ imageAlignAttribute as x,
1284
+ normalizeImageCaption as y,
1285
+ renderMathInto as z
862
1286
  };
863
- //# sourceMappingURL=extractToc-BaR6LP1f.js.map
1287
+ //# sourceMappingURL=extractToc-CGrBMGvM.js.map