@docen/docx 0.3.2 → 0.3.4

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 (83) hide show
  1. package/dist/blockquote-DY80QC06.d.mts +21 -0
  2. package/dist/bullet-list-DF60pnSL.d.mts +12 -0
  3. package/dist/converters/docx.d.mts +25 -4
  4. package/dist/converters/docx.mjs +217 -41
  5. package/dist/converters/html.d.mts +1 -1
  6. package/dist/converters/html.mjs +1 -1
  7. package/dist/converters/markdown.d.mts +1 -1
  8. package/dist/converters/markdown.mjs +1 -1
  9. package/dist/converters/patch.d.mts +1 -1
  10. package/dist/converters/prepare.d.mts +1 -1
  11. package/dist/converters/prepare.mjs +6 -9
  12. package/dist/converters/styles.d.mts +7 -1
  13. package/dist/converters/styles.mjs +31 -8
  14. package/dist/{core-DT9IrTzN.mjs → core-BnF8XhVE.mjs} +229 -33
  15. package/dist/core-DC0_-WcE.d.mts +387 -0
  16. package/dist/core.d.mts +1 -1
  17. package/dist/core.mjs +1 -1
  18. package/dist/details-Dd5MqqmR.d.mts +17 -0
  19. package/dist/document-Cws7XTYL.d.mts +28 -0
  20. package/dist/editor.d.mts +1 -1
  21. package/dist/editor.mjs +1 -1
  22. package/dist/extensions/blockquote.d.mts +2 -22
  23. package/dist/extensions/bullet-list.d.mts +2 -0
  24. package/dist/extensions/bullet-list.mjs +21 -0
  25. package/dist/extensions/code-block.d.mts +1 -18
  26. package/dist/extensions/column-break.d.mts +1 -30
  27. package/dist/extensions/column-break.mjs +1 -1
  28. package/dist/extensions/details.d.mts +2 -18
  29. package/dist/extensions/document.d.mts +1 -1
  30. package/dist/extensions/document.mjs +3 -6
  31. package/dist/extensions/extensions.d.mts +10 -6
  32. package/dist/extensions/extensions.mjs +9 -4
  33. package/dist/extensions/formatting-marks.d.mts +1 -37
  34. package/dist/extensions/formatting-marks.mjs +1 -1
  35. package/dist/extensions/heading.mjs +31 -9
  36. package/dist/extensions/image.d.mts +1 -1
  37. package/dist/extensions/image.mjs +20 -4
  38. package/dist/extensions/index.d.mts +17 -4
  39. package/dist/extensions/index.mjs +18 -3
  40. package/dist/extensions/link.d.mts +2 -0
  41. package/dist/extensions/link.mjs +56 -0
  42. package/dist/extensions/mention.d.mts +2 -24
  43. package/dist/extensions/ordered-list.d.mts +1 -24
  44. package/dist/extensions/ordered-list.mjs +10 -1
  45. package/dist/extensions/page-break.d.mts +1 -1
  46. package/dist/extensions/page-break.mjs +1 -1
  47. package/dist/extensions/paragraph.mjs +14 -6
  48. package/dist/extensions/passthrough.d.mts +2 -2
  49. package/dist/extensions/passthrough.mjs +2 -2
  50. package/dist/extensions/scroll.d.mts +2 -0
  51. package/dist/extensions/scroll.mjs +33 -0
  52. package/dist/extensions/section-break.d.mts +1 -43
  53. package/dist/extensions/section-break.mjs +1 -1
  54. package/dist/extensions/strike.mjs +1 -5
  55. package/dist/extensions/tab.d.mts +2 -0
  56. package/dist/extensions/tab.mjs +2 -0
  57. package/dist/extensions/table-cell.mjs +3 -8
  58. package/dist/extensions/table-header.mjs +1 -6
  59. package/dist/extensions/table-row.mjs +1 -6
  60. package/dist/extensions/table.mjs +1 -6
  61. package/dist/extensions/task-item.d.mts +2 -26
  62. package/dist/extensions/text-style.mjs +1 -6
  63. package/dist/extensions/tiptap.d.mts +1 -1
  64. package/dist/extensions/toc-field.d.mts +2 -0
  65. package/dist/extensions/toc-field.mjs +2 -0
  66. package/dist/extensions/utils.d.mts +2 -2
  67. package/dist/extensions/utils.mjs +10 -1
  68. package/dist/extensions/wpg-group.d.mts +2 -2
  69. package/dist/extensions/wpg-group.mjs +2 -2
  70. package/dist/extensions/wps-shape.d.mts +1 -1
  71. package/dist/extensions/wps-shape.mjs +1 -1
  72. package/dist/index.d.mts +48 -11
  73. package/dist/index.mjs +19 -4
  74. package/dist/link-BawPjQZR.d.mts +6 -0
  75. package/dist/mention-BGLzLVYw.d.mts +23 -0
  76. package/dist/ordered-list-DFAe-YEV.d.mts +25 -0
  77. package/dist/scroll-ZNeThJsJ.d.mts +18 -0
  78. package/dist/task-item-B0ntvQ1Y.d.mts +25 -0
  79. package/dist/{tiptap-pZsNPsvV.d.mts → tiptap-BKqn41uT.d.mts} +2 -2
  80. package/dist/{utils-D87vukzp.d.mts → utils-BJwDQts7.d.mts} +10 -1
  81. package/package.json +3 -3
  82. package/dist/core-C9VunJ8o.d.mts +0 -174
  83. package/dist/document-BH1y4qHM.d.mts +0 -6
@@ -0,0 +1,21 @@
1
+ //#region src/extensions/blockquote.d.ts
2
+ /**
3
+ * Blockquote extension — owns the DOCX expression of a blockquote.
4
+ *
5
+ * DOCX has no blockquote element; the convention is a left indent plus a left
6
+ * border on each contained paragraph. This module owns that signature; the
7
+ * DocxManager walks the blockquote's child paragraphs and applies it to each.
8
+ */
9
+ /** Left indent (twips, ~0.5 inch) marking a blockquote. */
10
+ declare const BLOCKQUOTE_INDENT_LEFT = 720;
11
+ /** Left border marking a blockquote. */
12
+ declare const BLOCKQUOTE_BORDER: {
13
+ readonly style: "single";
14
+ readonly size: 18;
15
+ readonly space: 12;
16
+ readonly color: "CCCCCC";
17
+ };
18
+ /** Apply the blockquote signature (left indent + left border) to paragraph opts. */
19
+ declare function applyBlockquoteStyle(paraObj: Record<string, unknown>): void;
20
+ //#endregion
21
+ export { BLOCKQUOTE_INDENT_LEFT as n, applyBlockquoteStyle as r, BLOCKQUOTE_BORDER as t };
@@ -0,0 +1,12 @@
1
+ //#region src/extensions/bullet-list.d.ts
2
+ /**
3
+ * BulletList — carries the source DOCX abstractNum reference (when the list came
4
+ * from parseDOCX) so the round-trip reuses the original numbering definition
5
+ * (custom bullet glyph/font/indent, e.g. a Wingdings marker) instead of
6
+ * regenerating the default. The `numbering` attr is DOCX-only (not rendered
7
+ * to HTML); lists created in the editor carry null and compile to the default
8
+ * bullet.
9
+ */
10
+ declare const BulletList: import("@tiptap/core").Node<import("@tiptap/extension-bullet-list").BulletListOptions, any>;
11
+ //#endregion
12
+ export { BulletList as t };
@@ -1,4 +1,4 @@
1
- import { a as JSONContent } from "../core-C9VunJ8o.mjs";
1
+ import { a as JSONContent } from "../core-DC0_-WcE.mjs";
2
2
  import { PrepareStep } from "./prepare.mjs";
3
3
  import { DocumentOptions, OutputByType, OutputType, PackerOptions, parseDocument } from "@office-open/docx";
4
4
 
@@ -68,15 +68,36 @@ declare class DocxManager {
68
68
  private resolveSectionChild;
69
69
  /** Wrap an opaque SectionChild in a passthrough atom (attrs.data = JSON). */
70
70
  private resolvePassthrough;
71
+ /**
72
+ * Resolve a table of contents into an editable `tableOfContents` container:
73
+ * `attrs.options` carries the field switches, `content` is the entry
74
+ * paragraphs. Each entry's inner HYPERLINK field has content-less runs that
75
+ * office-open parses as `null`; resolving the entries through
76
+ * `resolveSectionChild` → `resolveParagraphChildren` drops those nulls, so the
77
+ * TOC no longer reaches the generate path as an opaque blob of nulls (the
78
+ * `stringifyRunInline(null).break` crash). When `entries` is absent/empty
79
+ * (a fresh, unrendered TOC), keep the node valid for `content: "block+"` with
80
+ * a placeholder empty paragraph.
81
+ */
82
+ private resolveToc;
71
83
  /**
72
84
  * Resolve a details group-SDT: the summary-style paragraph becomes
73
85
  * detailsSummary, the remaining blocks fold into detailsContent.
74
86
  */
75
87
  private resolveDetailsSdt;
76
88
  private resolveParagraph;
77
- /** Look up a paragraph style's NAME from its styleId, via the styles table
78
- * carried on the current resolve(). Returns undefined outside resolve. */
79
- private styleNameOf;
89
+ /** Heading level (1-9) for a paragraph, or undefined when it isn't a heading.
90
+ * DOCX marks a heading several ways, checked in priority order:
91
+ * 1. office-open lifts a HeadingLevel pStyle ("Heading1".."Title") into `heading`.
92
+ * 2. An explicit `outlineLevel` (0-8 → 1-9) — Word's outline/TOC key off this
93
+ * even without a heading pStyle; the Heading1-9 styles carry outlineLvl 0-8.
94
+ * 3. A pStyle that names a heading style: directly ("Heading7", which stays on
95
+ * `style` because office-open's HeadingLevel type caps at 6), by localized
96
+ * NAME ("heading 1"/"标题 1"), or via the `basedOn` chain (a custom style
97
+ * "MyTitle" basedOn="Heading1"). `heading` and `style` carry the same pStyle.
98
+ * `outlineLevel` is read loosely — office-open's public type omits the field
99
+ * even though it round-trips (w:outlineLvl) at runtime. */
100
+ private detectHeadingLevel;
80
101
  /** reference → level-0 format/start, for classifying numbering paragraphs. */
81
102
  private buildNumberingLookup;
82
103
  /**
@@ -14,6 +14,7 @@ import { parseDocx as parseDocx$7, renderDocx as renderDocx$8 } from "../extensi
14
14
  import { createTaskCheckbox, isTaskCheckbox, readCheckboxState } from "../extensions/task-item.mjs";
15
15
  import { parseDocx as parseDocx$8, renderDocx as renderDocx$9 } from "../extensions/text-style.mjs";
16
16
  import { prepareDocument } from "./prepare.mjs";
17
+ import { indexParagraphStyles } from "./styles.mjs";
17
18
  import { emojis, shortcodeToEmoji } from "@tiptap/extension-emoji";
18
19
  import { generateDocument, generateDocumentStream, generateDocumentSync, parseDocument } from "@office-open/docx";
19
20
  import { encodeBase64 } from "@office-open/core";
@@ -119,13 +120,16 @@ var DocxManager = class {
119
120
  const core = docAttrs.core ?? void 0;
120
121
  const background = docAttrs.background ?? void 0;
121
122
  const documentExtras = docAttrs.documentExtras ?? void 0;
123
+ const origNumberingConfig = docAttrs.numbering?.config ?? [];
124
+ const regeneratedRefs = new Set(this.numberingConfigs.map((c) => c.reference));
125
+ const numberingConfig = [...origNumberingConfig.filter((c) => !regeneratedRefs.has(c.reference)), ...this.numberingConfigs];
122
126
  return {
123
127
  sections,
124
128
  ...styles ? { styles } : {},
125
129
  ...core,
126
130
  ...background ? { background } : {},
127
131
  ...documentExtras,
128
- ...this.numberingConfigs.length > 0 ? { numbering: { config: this.numberingConfigs } } : {}
132
+ ...numberingConfig.length > 0 ? { numbering: { config: numberingConfig } } : {}
129
133
  };
130
134
  }
131
135
  /** Assemble a SectionOptions from compiled children + optional layout/headers/footers. */
@@ -217,6 +221,7 @@ var DocxManager = class {
217
221
  const attrs = {};
218
222
  if (docOpts.styles) attrs.styles = docOpts.styles;
219
223
  if (docOpts.background) attrs.background = docOpts.background;
224
+ if (docOpts.numbering) attrs.numbering = docOpts.numbering;
220
225
  const core = extractCoreProperties(docOpts);
221
226
  if (core) attrs.core = core;
222
227
  const lastSection = sections[lastIndex];
@@ -266,6 +271,20 @@ var DocxManager = class {
266
271
  case "orderedList":
267
272
  case "taskList": return this.compileListFromNode(node, 0);
268
273
  case "details": return this.compileDetailsNode(node);
274
+ case "tocField": {
275
+ const options = node.attrs?.options ?? {};
276
+ const entries = [];
277
+ for (const child of node.content ?? []) {
278
+ const compiled = this.compileSectionChild(child);
279
+ if (!compiled) continue;
280
+ if (Array.isArray(compiled)) entries.push(...compiled);
281
+ else entries.push(compiled);
282
+ }
283
+ return { toc: {
284
+ ...options,
285
+ entries
286
+ } };
287
+ }
269
288
  case "passthrough": {
270
289
  const data = node.attrs?.data ?? "{}";
271
290
  try {
@@ -443,15 +462,20 @@ var DocxManager = class {
443
462
  const items = [];
444
463
  const isOrdered = node.type === "orderedList";
445
464
  const isTask = node.type === "taskList";
465
+ const numbering = node.attrs?.numbering;
446
466
  let ordered;
447
- if (isOrdered) ordered = this.registerOrderedNumbering(node);
467
+ if (isOrdered && !numbering) ordered = this.registerOrderedNumbering(node);
448
468
  for (const listItem of node.content ?? []) {
449
469
  if (listItem.type !== "listItem" && listItem.type !== "taskItem") continue;
450
470
  const checked = Boolean(listItem.attrs?.checked);
451
471
  for (const child of listItem.content ?? []) if (child.type === "paragraph" || child.type === "heading") {
452
472
  const para = child.type === "heading" ? this.compileHeadingNode(child) : this.compileParagraphNode(child);
453
473
  const paraObj = typeof para === "string" ? { text: para } : para;
454
- if (ordered) paraObj.numbering = {
474
+ if (numbering) paraObj.numbering = {
475
+ reference: numbering,
476
+ level
477
+ };
478
+ else if (ordered) paraObj.numbering = {
455
479
  reference: ordered.reference,
456
480
  instance: ordered.instance,
457
481
  level
@@ -544,6 +568,17 @@ var DocxManager = class {
544
568
  case "columnBreak":
545
569
  children.push({ columnBreak: true });
546
570
  break;
571
+ case "tab":
572
+ children.push({ tab: true });
573
+ break;
574
+ case "inlinePassthrough": {
575
+ const data = node.attrs?.data ?? "{}";
576
+ try {
577
+ const parsed = JSON.parse(data);
578
+ if (parsed) children.push(parsed);
579
+ } catch {}
580
+ break;
581
+ }
547
582
  case "image": {
548
583
  const imageRun = renderDocx$2(node);
549
584
  if (imageRun) children.push(imageRun);
@@ -555,8 +590,18 @@ var DocxManager = class {
555
590
  break;
556
591
  }
557
592
  case "wpsShape": {
558
- const wpsShape = node.attrs?.wpsShape;
559
- if (wpsShape) children.push({ wpsShape });
593
+ const geometry = node.attrs?.wpsShape ?? {};
594
+ const body = [];
595
+ for (const child of node.content ?? []) {
596
+ const compiled = this.compileSectionChild(child);
597
+ if (!compiled) continue;
598
+ const items = Array.isArray(compiled) ? compiled : [compiled];
599
+ for (const it of items) if (it && typeof it === "object" && "paragraph" in it) body.push(it.paragraph);
600
+ }
601
+ children.push({ wpsShape: {
602
+ ...geometry,
603
+ children: body
604
+ } });
560
605
  break;
561
606
  }
562
607
  case "mention":
@@ -645,6 +690,7 @@ var DocxManager = class {
645
690
  if (sdt.properties?.tag === "docen-details") return this.resolveDetailsSdt(sdt);
646
691
  return this.resolvePassthrough(child);
647
692
  }
693
+ if ("toc" in child) return this.resolveToc(child.toc);
648
694
  return this.resolvePassthrough(child);
649
695
  }
650
696
  /** Wrap an opaque SectionChild in a passthrough atom (attrs.data = JSON). */
@@ -655,6 +701,35 @@ var DocxManager = class {
655
701
  };
656
702
  }
657
703
  /**
704
+ * Resolve a table of contents into an editable `tableOfContents` container:
705
+ * `attrs.options` carries the field switches, `content` is the entry
706
+ * paragraphs. Each entry's inner HYPERLINK field has content-less runs that
707
+ * office-open parses as `null`; resolving the entries through
708
+ * `resolveSectionChild` → `resolveParagraphChildren` drops those nulls, so the
709
+ * TOC no longer reaches the generate path as an opaque blob of nulls (the
710
+ * `stringifyRunInline(null).break` crash). When `entries` is absent/empty
711
+ * (a fresh, unrendered TOC), keep the node valid for `content: "block+"` with
712
+ * a placeholder empty paragraph.
713
+ */
714
+ resolveToc(toc) {
715
+ const { entries, ...options } = toc;
716
+ const content = [];
717
+ for (const entry of entries ?? []) {
718
+ const node = this.resolveSectionChild(entry);
719
+ if (!node) continue;
720
+ if (Array.isArray(node)) content.push(...node);
721
+ else content.push(node);
722
+ }
723
+ if (content.length === 0) content.push({ type: "paragraph" });
724
+ const node = {
725
+ type: "tocField",
726
+ content
727
+ };
728
+ const cleanOptions = cleanAttrs(options);
729
+ if (Object.keys(cleanOptions).length > 0) node.attrs = { options: cleanOptions };
730
+ return node;
731
+ }
732
+ /**
658
733
  * Resolve a details group-SDT: the summary-style paragraph becomes
659
734
  * detailsSummary, the remaining blocks fold into detailsContent.
660
735
  */
@@ -692,20 +767,10 @@ var DocxManager = class {
692
767
  const resolved = typeof opts === "string" ? { text: opts } : opts;
693
768
  if (resolved.thematicBreak) return { type: "horizontalRule" };
694
769
  if (resolved.style === "Code") return this.resolveCodeBlock(resolved);
695
- let headingLevel = resolved.heading ? HEADING_LEVEL_MAP[resolved.heading] : void 0;
696
- if (!headingLevel && resolved.style) {
697
- const name = this.styleNameOf(resolved.style);
698
- if (name) {
699
- const m = /^heading\s+(\d)$/i.exec(name);
700
- if (m) {
701
- const lvl = Number(m[1]);
702
- if (lvl >= 1 && lvl <= 6) headingLevel = lvl;
703
- } else if (/^title$/i.test(name)) headingLevel = 1;
704
- }
705
- }
706
- if (headingLevel && !resolved.heading) resolved.heading = `Heading${headingLevel}`;
770
+ const headingLevel = this.detectHeadingLevel(resolved);
707
771
  const nodeType = headingLevel ? "heading" : "paragraph";
708
772
  const attrs = headingLevel ? parseDocx(resolved) : parseDocx$2(resolved);
773
+ if (headingLevel && attrs.level == null) attrs.level = headingLevel;
709
774
  const content = this.resolveInlineContent(resolved);
710
775
  const cleanAttrsObj = cleanAttrs(attrs);
711
776
  const node = { type: nodeType };
@@ -713,10 +778,38 @@ var DocxManager = class {
713
778
  if (content.length > 0) node.content = content;
714
779
  return node;
715
780
  }
716
- /** Look up a paragraph style's NAME from its styleId, via the styles table
717
- * carried on the current resolve(). Returns undefined outside resolve. */
718
- styleNameOf(styleId) {
719
- return this.resolveStyles?.paragraphStyles?.find((p) => p.id === styleId)?.name;
781
+ /** Heading level (1-9) for a paragraph, or undefined when it isn't a heading.
782
+ * DOCX marks a heading several ways, checked in priority order:
783
+ * 1. office-open lifts a HeadingLevel pStyle ("Heading1".."Title") into `heading`.
784
+ * 2. An explicit `outlineLevel` (0-8 → 1-9) Word's outline/TOC key off this
785
+ * even without a heading pStyle; the Heading1-9 styles carry outlineLvl 0-8.
786
+ * 3. A pStyle that names a heading style: directly ("Heading7", which stays on
787
+ * `style` because office-open's HeadingLevel type caps at 6), by localized
788
+ * NAME ("heading 1"/"标题 1"), or via the `basedOn` chain (a custom style
789
+ * "MyTitle" basedOn="Heading1"). `heading` and `style` carry the same pStyle.
790
+ * `outlineLevel` is read loosely — office-open's public type omits the field
791
+ * even though it round-trips (w:outlineLvl) at runtime. */
792
+ detectHeadingLevel(resolved) {
793
+ if (resolved.heading) {
794
+ const lvl = HEADING_LEVEL_MAP[resolved.heading];
795
+ if (lvl) return lvl;
796
+ }
797
+ const outline = resolved.outlineLevel;
798
+ if (typeof outline === "number" && outline >= 0 && outline <= 8) return outline + 1;
799
+ const styleId = resolved.style;
800
+ if (!styleId || !this.resolveStyles) return void 0;
801
+ const byId = indexParagraphStyles(this.resolveStyles);
802
+ const visited = /* @__PURE__ */ new Set();
803
+ let curId = styleId;
804
+ while (curId && !visited.has(curId)) {
805
+ visited.add(curId);
806
+ if (HEADING_LEVEL_MAP[curId]) return HEADING_LEVEL_MAP[curId];
807
+ const style = byId.get(curId);
808
+ if (!style) break;
809
+ const lvl = headingLevelFromName(style.name);
810
+ if (lvl) return lvl;
811
+ curId = style.basedOn ?? void 0;
812
+ }
720
813
  }
721
814
  /** reference → level-0 format/start, for classifying numbering paragraphs. */
722
815
  buildNumberingLookup(docOpts) {
@@ -795,10 +888,7 @@ var DocxManager = class {
795
888
  if (cfg && cfg.format && cfg.format !== "bullet") {
796
889
  kind = "ordered";
797
890
  start = cfg.start;
798
- } else {
799
- kind = "bullet";
800
- reference = void 0;
801
- }
891
+ } else kind = "bullet";
802
892
  } else if (bullet) {
803
893
  kind = "bullet";
804
894
  level = bullet.level ?? 0;
@@ -847,7 +937,10 @@ var DocxManager = class {
847
937
  type: listType,
848
938
  content: [newItem]
849
939
  };
850
- if (listType === "orderedList" && info.level === 0 && typeof info.start === "number" && info.start !== 1) newList.attrs = { start: info.start };
940
+ const listAttrs = {};
941
+ if (listType === "orderedList" && info.level === 0 && typeof info.start === "number" && info.start !== 1) listAttrs.start = info.start;
942
+ if (info.reference) listAttrs.numbering = info.reference;
943
+ if (Object.keys(listAttrs).length > 0) newList.attrs = listAttrs;
851
944
  if (top) top.currentItem.content.push(newList);
852
945
  else topLevel.push(newList);
853
946
  stack.push({
@@ -909,9 +1002,10 @@ var DocxManager = class {
909
1002
  */
910
1003
  resolveListItemParagraph(para, info) {
911
1004
  const resolved = typeof para === "string" ? { text: para } : para;
912
- const headingLevel = resolved.heading ? HEADING_LEVEL_MAP[resolved.heading] : void 0;
1005
+ const headingLevel = this.detectHeadingLevel(resolved);
913
1006
  const nodeType = headingLevel ? "heading" : "paragraph";
914
1007
  const attrs = headingLevel ? parseDocx(resolved) : parseDocx$2(resolved);
1008
+ if (headingLevel && attrs.level == null) attrs.level = headingLevel;
915
1009
  const stripped = info.kind === "task" ? this.stripTaskCheckbox(resolved) : resolved;
916
1010
  const content = this.resolveInlineContent(stripped);
917
1011
  const node = { type: nodeType };
@@ -1072,21 +1166,55 @@ var DocxManager = class {
1072
1166
  return nodes;
1073
1167
  }
1074
1168
  resolveParagraphChild(child) {
1169
+ if ("tab" in child) return { type: "tab" };
1075
1170
  if ("text" in child || "children" in child || "break" in child) return this.resolveRun(child);
1076
1171
  if ("image" in child) return this.resolveImage(child.image);
1077
1172
  if ("wpgGroup" in child) return {
1078
1173
  type: "wpgGroup",
1079
1174
  attrs: { wpgGroup: child.wpgGroup }
1080
1175
  };
1081
- if ("wpsShape" in child) return {
1082
- type: "wpsShape",
1083
- attrs: { wpsShape: child.wpsShape }
1084
- };
1176
+ if ("wpsShape" in child) {
1177
+ const ws = child.wpsShape;
1178
+ const content = [];
1179
+ if (ws?.children) for (const para of ws.children) {
1180
+ if (typeof para !== "object" || para === null) {
1181
+ const node = this.resolveParagraph(para);
1182
+ if (node) content.push(node);
1183
+ continue;
1184
+ }
1185
+ const defRPr = para.run ?? {};
1186
+ const children = Array.isArray(para.children) ? para.children.map((c) => typeof c !== "object" || c === null ? {
1187
+ ...defRPr,
1188
+ text: c
1189
+ } : {
1190
+ ...defRPr,
1191
+ ...c
1192
+ }) : void 0;
1193
+ const node = this.resolveParagraph({
1194
+ ...para,
1195
+ run: void 0,
1196
+ ...children ? { children } : {}
1197
+ });
1198
+ if (node) content.push(node);
1199
+ }
1200
+ if (content.length === 0) content.push({ type: "paragraph" });
1201
+ const { children: _omit, ...geometry } = ws ?? {};
1202
+ const node = {
1203
+ type: "wpsShape",
1204
+ content
1205
+ };
1206
+ const cleanGeometry = cleanAttrs(geometry);
1207
+ if (Object.keys(cleanGeometry).length > 0) node.attrs = { wpsShape: cleanGeometry };
1208
+ return node;
1209
+ }
1085
1210
  if ("sdt" in child) return this.resolveInlineSdt(child);
1086
1211
  if ("hyperlink" in child) return this.resolveHyperlink(child.hyperlink);
1087
1212
  if ("pageBreak" in child) return { type: "pageBreak" };
1088
1213
  if ("columnBreak" in child) return { type: "columnBreak" };
1089
- return null;
1214
+ return {
1215
+ type: "inlinePassthrough",
1216
+ attrs: { data: JSON.stringify(child) }
1217
+ };
1090
1218
  }
1091
1219
  /** Resolve an inline SDT (mention carrier; other inline SDTs unsupported). */
1092
1220
  resolveInlineSdt(child) {
@@ -1106,15 +1234,48 @@ var DocxManager = class {
1106
1234
  if (opts.break && opts.text === void 0 && !opts.children) return { type: "hardBreak" };
1107
1235
  const text = opts.text;
1108
1236
  if (text === void 0 && !opts.children) return null;
1109
- if (opts.children && !text) {
1110
- const parts = [];
1111
- for (const c of opts.children) if (typeof c === "string") parts.push(c);
1112
- if (parts.length === 0) return null;
1113
- return {
1114
- type: "text",
1115
- text: parts.join(""),
1116
- marks: this.resolveMarks(opts)
1237
+ if (opts.children) {
1238
+ const marks = this.resolveMarks(opts);
1239
+ const nodes = [];
1240
+ let parts = [];
1241
+ const flushText = () => {
1242
+ if (parts.length > 0) {
1243
+ const node = {
1244
+ type: "text",
1245
+ text: parts.join("")
1246
+ };
1247
+ if (marks) node.marks = marks;
1248
+ nodes.push(node);
1249
+ parts = [];
1250
+ }
1117
1251
  };
1252
+ for (const c of opts.children) if (typeof c === "string") parts.push(c);
1253
+ else if (c && typeof c === "object") {
1254
+ if ("pageBreak" in c) {
1255
+ flushText();
1256
+ nodes.push({ type: "pageBreak" });
1257
+ } else if ("columnBreak" in c) {
1258
+ flushText();
1259
+ nodes.push({ type: "columnBreak" });
1260
+ } else if ("break" in c) {
1261
+ flushText();
1262
+ nodes.push({ type: "hardBreak" });
1263
+ } else if ("tab" in c) {
1264
+ flushText();
1265
+ nodes.push({ type: "tab" });
1266
+ }
1267
+ }
1268
+ flushText();
1269
+ if (text !== void 0) {
1270
+ const node = {
1271
+ type: "text",
1272
+ text,
1273
+ marks
1274
+ };
1275
+ nodes.push(node);
1276
+ }
1277
+ if (nodes.length === 0) return null;
1278
+ return nodes.length === 1 ? nodes[0] : nodes;
1118
1279
  }
1119
1280
  return {
1120
1281
  type: "text",
@@ -1172,7 +1333,7 @@ var DocxManager = class {
1172
1333
  type: "link",
1173
1334
  attrs: {
1174
1335
  href,
1175
- target: "_blank",
1336
+ target: href.startsWith("#") ? null : "_blank",
1176
1337
  rel: "noopener noreferrer nofollow",
1177
1338
  class: null,
1178
1339
  title: hyperlink.tooltip ?? null
@@ -1190,8 +1351,23 @@ const HEADING_LEVEL_MAP = {
1190
1351
  Heading4: 4,
1191
1352
  Heading5: 5,
1192
1353
  Heading6: 6,
1354
+ Heading7: 7,
1355
+ Heading8: 8,
1356
+ Heading9: 9,
1193
1357
  Title: 1
1194
1358
  };
1359
+ /** Heading level (1-9) from a localized style NAME: "heading 1"/"标题 1" → 1,
1360
+ * "title" → 1. office-open's built-in names are English ("heading 1"), but
1361
+ * zh-CN Word labels the same styles "标题 1"; both map to the same level. */
1362
+ function headingLevelFromName(name) {
1363
+ if (!name) return void 0;
1364
+ const m = /^heading\s+(\d)$/i.exec(name) ?? /^标题\s*(\d)$/.exec(name);
1365
+ if (m) {
1366
+ const lvl = Number(m[1]);
1367
+ if (lvl >= 1 && lvl <= 9) return lvl;
1368
+ }
1369
+ return /^title$/i.test(name) ? 1 : void 0;
1370
+ }
1195
1371
  const defaultManager = new DocxManager();
1196
1372
  /**
1197
1373
  * Parse a DOCX file into Tiptap JSON (runtime model).
@@ -1,4 +1,4 @@
1
- import { a as JSONContent, i as Extensions } from "../core-C9VunJ8o.mjs";
1
+ import { a as JSONContent, i as Extensions } from "../core-DC0_-WcE.mjs";
2
2
  import { ParseOptions } from "@tiptap/pm/model";
3
3
 
4
4
  //#region src/converters/html.d.ts
@@ -1,4 +1,4 @@
1
- import { s as docxExtensions } from "../core-DT9IrTzN.mjs";
1
+ import { o as docxExtensions } from "../core-BnF8XhVE.mjs";
2
2
  import { sectionLinePitchCss, sectionMarginCss } from "../extensions/utils.mjs";
3
3
  import { getSchema } from "@tiptap/core";
4
4
  import { encodeBase64 } from "@office-open/core";
@@ -1,4 +1,4 @@
1
- import { a as JSONContent } from "../core-C9VunJ8o.mjs";
1
+ import { a as JSONContent } from "../core-DC0_-WcE.mjs";
2
2
 
3
3
  //#region src/converters/markdown.d.ts
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { s as docxExtensions } from "../core-DT9IrTzN.mjs";
1
+ import { o as docxExtensions } from "../core-BnF8XhVE.mjs";
2
2
  import { Markdown, MarkdownManager } from "@tiptap/markdown";
3
3
  //#region src/converters/markdown.ts
4
4
  const markdownManager = new MarkdownManager({ extensions: [...docxExtensions, Markdown] });
@@ -1,4 +1,4 @@
1
- import { a as JSONContent } from "../core-C9VunJ8o.mjs";
1
+ import { a as JSONContent } from "../core-DC0_-WcE.mjs";
2
2
  import { PrepareStep } from "./prepare.mjs";
3
3
  import { OutputByType, OutputType, patchDocument } from "@office-open/docx";
4
4
 
@@ -1,4 +1,4 @@
1
- import { a as JSONContent } from "../core-C9VunJ8o.mjs";
1
+ import { a as JSONContent } from "../core-DC0_-WcE.mjs";
2
2
 
3
3
  //#region src/converters/prepare.d.ts
4
4
  /**
@@ -67,15 +67,12 @@ async function prepareDocument(json, steps = DEFAULT_STEPS) {
67
67
  }
68
68
  async function toDataUrl(src, handler) {
69
69
  const data = await handler(src);
70
- return `data:${{
71
- jpg: "image/jpeg",
72
- jpeg: "image/jpeg",
73
- png: "image/png",
74
- gif: "image/gif",
75
- bmp: "image/bmp",
76
- svg: "image/svg+xml",
77
- webp: "image/webp"
78
- }[src.split(".").pop()?.toLowerCase() ?? "png"] ?? "image/png"};base64,${encodeBase64(data)}`;
70
+ let mime = "image/png";
71
+ try {
72
+ const type = imageMeta(data).type;
73
+ if (type) mime = type === "jpg" ? "image/jpeg" : `image/${type}`;
74
+ } catch {}
75
+ return `data:${mime};base64,${encodeBase64(data)}`;
79
76
  }
80
77
  async function walkImages(node, handler) {
81
78
  if (node.type === "image" && node.attrs) {
@@ -2,6 +2,11 @@ import { JSONContent } from "@tiptap/core";
2
2
  import { StylesOptions } from "@office-open/docx";
3
3
 
4
4
  //#region src/converters/styles.d.ts
5
+ /** A named style entry as office-open models it: BaseParagraphStyleOptions or
6
+ * BaseCharacterStyleOptions (both extend the internal StyleOptions, carrying
7
+ * name/uiPriority/quickFormat). Derived from the public StylesOptions — not
8
+ * imported — because StyleOptions is not a public export of @office-open/docx. */
9
+ type StyleEntry = NonNullable<StylesOptions["paragraphStyles"]>[number] | NonNullable<StylesOptions["characterStyles"]>[number];
5
10
  /** The styleId of the document's default paragraph style (`w:default="1"`
6
11
  * type="paragraph") — the implicit style applied to every paragraph WITHOUT an
7
12
  * explicit pStyle. OOXML renders a pStyle-less paragraph as this style (usually
@@ -29,6 +34,7 @@ declare function defaultParagraphStyleId(styles: StylesOptions | null | undefine
29
34
  * carries its full ancestor chain rather than relying on source order.
30
35
  */
31
36
  declare function stylesToCss(styles: StylesOptions | null | undefined, scope: string): string;
37
+ declare function indexParagraphStyles(styles: StylesOptions): Map<string, StyleEntry>;
32
38
  /** A gallery-ready paragraph-style entry for the Styles combobox. */
33
39
  interface QuickStyleEntry {
34
40
  id: string;
@@ -82,4 +88,4 @@ declare function effectiveRunProps(styles: StylesOptions | null | undefined, sty
82
88
  */
83
89
  declare function inlineStyles(json: JSONContent, styles?: StylesOptions | null): JSONContent;
84
90
  //#endregion
85
- export { QuickStyleEntry, type StylesOptions, defaultParagraphStyleId, effectiveRunProps, inlineStyles, quickStyles, stylesToCss };
91
+ export { QuickStyleEntry, StyleEntry, type StylesOptions, defaultParagraphStyleId, effectiveRunProps, indexParagraphStyles, inlineStyles, quickStyles, stylesToCss };
@@ -85,7 +85,10 @@ function stylesToCss(styles, scope) {
85
85
  * (key → pStyle id via pStyleIdFromKey). `document` is docDefaults, not a
86
86
  * named style, so it is excluded. A built-in that also appears in
87
87
  * paragraphStyles is deduped by id (paragraphStyles wins on insertion order). */
88
+ const styleIndexCache = /* @__PURE__ */ new WeakMap();
88
89
  function indexParagraphStyles(styles) {
90
+ const cached = styleIndexCache.get(styles);
91
+ if (cached) return cached;
89
92
  const byId = /* @__PURE__ */ new Map();
90
93
  for (const ps of styles.paragraphStyles ?? []) byId.set(ps.id, ps);
91
94
  const defaults = styles.default;
@@ -93,14 +96,34 @@ function indexParagraphStyles(styles) {
93
96
  if (key === "document" || !style) continue;
94
97
  byId.set(pStyleIdFromKey(key), style);
95
98
  }
99
+ styleIndexCache.set(styles, byId);
96
100
  return byId;
97
101
  }
102
+ /** Whether `v` is a plain object — an OOXML property group (spacing/indent/
103
+ * border/shading/font) that merges key by key — as opposed to an array
104
+ * (tabStops) or scalar, which replace. */
105
+ function isPlainObject(v) {
106
+ return typeof v === "object" && v !== null && !Array.isArray(v);
107
+ }
108
+ /** Deep-merge `source` into `target` (mutates target) per the OOXML `basedOn`
109
+ * model: nested property groups merge key by key (a child's spacing.before
110
+ * merges with, not replaces, the parent's spacing.line); arrays and scalars
111
+ * replace. Nullish source values are skipped so an unset child key doesn't
112
+ * clobber an inherited value. */
113
+ function deepMergeInto(target, source) {
114
+ for (const [key, srcVal] of Object.entries(source)) {
115
+ if (srcVal === null || srcVal === void 0) continue;
116
+ const tgtVal = target[key];
117
+ target[key] = isPlainObject(srcVal) && isPlainObject(tgtVal) ? deepMergeInto({ ...tgtVal }, srcVal) : isPlainObject(srcVal) ? { ...srcVal } : srcVal;
118
+ }
119
+ return target;
120
+ }
98
121
  /** Merge a paragraph style's run/paragraph properties with its `basedOn` chain
99
- * (root first, child overrides per-property) — the OOXML inheritance model. A
100
- * flat property-level merge: a child property overrides the parent's; an unset
101
- * property is inherited. Shared by stylesToCss (rendering) and
102
- * effectiveRunProps (the caret resolver) so the gallery box and the rendered
103
- * page resolve identical values. */
122
+ * (root first, child overrides per-property) — the OOXML inheritance model.
123
+ * Nested property groups (spacing/indent/border/font) merge key by key; arrays
124
+ * and scalars replace. Shared by stylesToCss (rendering) and effectiveRunProps
125
+ * (the caret resolver) so the gallery box and the rendered page resolve
126
+ * identical values. */
104
127
  function mergeStyleChain(byId, styleId) {
105
128
  const chain = [];
106
129
  const visited = /* @__PURE__ */ new Set();
@@ -116,8 +139,8 @@ function mergeStyleChain(byId, styleId) {
116
139
  const paragraph = {};
117
140
  for (const style of chain) {
118
141
  const s = style;
119
- if (s.run) Object.assign(run, s.run);
120
- if (s.paragraph) Object.assign(paragraph, s.paragraph);
142
+ if (s.run) deepMergeInto(run, s.run);
143
+ if (s.paragraph) deepMergeInto(paragraph, s.paragraph);
121
144
  }
122
145
  return {
123
146
  run,
@@ -282,4 +305,4 @@ function inlineStyles(json, styles) {
282
305
  return resolveNode(json, indexParagraphStyles(docStyles), indexCharacterRunStyles(docStyles));
283
306
  }
284
307
  //#endregion
285
- export { defaultParagraphStyleId, effectiveRunProps, inlineStyles, quickStyles, stylesToCss };
308
+ export { defaultParagraphStyleId, effectiveRunProps, indexParagraphStyles, inlineStyles, quickStyles, stylesToCss };