@stll/folio-core 0.66.0 → 0.66.2

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 (86) hide show
  1. package/dist/ai-edits/apply.d.ts +13 -1
  2. package/dist/ai-edits/apply.js +533 -136
  3. package/dist/ai-edits/batch-claims.d.ts +26 -0
  4. package/dist/ai-edits/batch-claims.js +30 -5
  5. package/dist/ai-edits/headless.d.ts +6 -0
  6. package/dist/ai-edits/headless.js +25 -3
  7. package/dist/ai-edits/newListNumbering.d.ts +4 -1
  8. package/dist/ai-edits/newListNumbering.js +4 -2
  9. package/dist/ai-edits/types.d.ts +1 -1
  10. package/dist/document-operations.d.ts +13 -1
  11. package/dist/document-operations.js +11 -4
  12. package/dist/docx/listNumberingInstances.d.ts +6 -4
  13. package/dist/docx/listNumberingInstances.js +12 -11
  14. package/dist/docx/numberingIds.d.ts +10 -0
  15. package/dist/docx/numberingIds.js +5 -1
  16. package/dist/docx/paragraphParser.js +16 -3
  17. package/dist/docx/paragraphProperties.js +2 -1
  18. package/dist/docx/serializer/paragraphSerializer.js +86 -66
  19. package/dist/fonts/bundledFonts.d.ts +54 -0
  20. package/dist/fonts/bundledFonts.js +45 -0
  21. package/dist/fonts/fontsourceFaces.d.ts +9 -14
  22. package/dist/fonts/fontsourceFaces.js +10 -22
  23. package/dist/i18n/messages/ar.gen.js +1 -0
  24. package/dist/i18n/messages/catalogs.gen.d.ts +17 -0
  25. package/dist/i18n/messages/catalogs.gen.js +17 -0
  26. package/dist/i18n/messages/cs.gen.js +1 -0
  27. package/dist/i18n/messages/de.gen.js +1 -0
  28. package/dist/i18n/messages/en.gen.js +1 -0
  29. package/dist/i18n/messages/es.gen.js +1 -0
  30. package/dist/i18n/messages/et.gen.js +1 -0
  31. package/dist/i18n/messages/fr.gen.js +1 -0
  32. package/dist/i18n/messages/he.gen.js +1 -0
  33. package/dist/i18n/messages/hi.gen.js +1 -0
  34. package/dist/i18n/messages/hu.gen.js +1 -0
  35. package/dist/i18n/messages/lt.gen.js +1 -0
  36. package/dist/i18n/messages/lv.gen.js +1 -0
  37. package/dist/i18n/messages/messages.gen.d.ts +1 -0
  38. package/dist/i18n/messages/pl.gen.js +1 -0
  39. package/dist/i18n/messages/pt-BR.gen.js +1 -0
  40. package/dist/i18n/messages/sk.gen.js +1 -0
  41. package/dist/i18n/messages/tr.gen.js +1 -0
  42. package/dist/i18n/messages/zh-CN.gen.js +1 -0
  43. package/dist/internal/wholeStoryRevisionResolution.d.ts +1 -1
  44. package/dist/internal/wholeStoryRevisionResolution.js +24 -9
  45. package/dist/layout-bridge/convert/runMarkFormatting.js +1 -0
  46. package/dist/markdown/renderRuns.js +68 -12
  47. package/dist/prosemirror/commands/comments.js +2 -0
  48. package/dist/prosemirror/commands/resolveParagraphProperties.js +15 -2
  49. package/dist/prosemirror/conversion/fromProseDoc.js +55 -18
  50. package/dist/prosemirror/conversion/hyphenTextCarriers.d.ts +23 -1
  51. package/dist/prosemirror/conversion/hyphenTextCarriers.js +46 -1
  52. package/dist/prosemirror/conversion/toProseDoc.js +15 -9
  53. package/dist/prosemirror/extensions/StarterKit.js +1 -1
  54. package/dist/prosemirror/extensions/features/pasteCleanup.js +6 -0
  55. package/dist/prosemirror/extensions/features/pastedHtmlLists.js +2 -1
  56. package/dist/prosemirror/extensions/markRegistry.d.ts +3 -1
  57. package/dist/prosemirror/extensions/markRegistry.js +3 -0
  58. package/dist/prosemirror/extensions/marks/HyphenElementExtension.d.ts +6 -0
  59. package/dist/prosemirror/extensions/marks/HyphenElementExtension.js +54 -0
  60. package/dist/prosemirror/fieldRepresentation.js +2 -1
  61. package/dist/prosemirror/hyphenElementAttrs.d.ts +10 -0
  62. package/dist/prosemirror/hyphenElementAttrs.js +32 -0
  63. package/dist/prosemirror/keptRangeBoundaries.d.ts +2 -0
  64. package/dist/prosemirror/keptRangeBoundaries.js +1 -1
  65. package/dist/prosemirror/listNumbering.js +3 -2
  66. package/dist/prosemirror/paragraphIndentation.js +2 -1
  67. package/dist/prosemirror/paragraphPropertyCarry.d.ts +1 -14
  68. package/dist/prosemirror/paragraphPropertyCarry.js +4 -28
  69. package/dist/prosemirror/plugins/suggestionMode.js +19 -6
  70. package/dist/prosemirror/replacedAnnotations.d.ts +1 -0
  71. package/dist/prosemirror/replacedAnnotations.js +1 -0
  72. package/dist/prosemirror/sectionCarrier.d.ts +8 -0
  73. package/dist/prosemirror/sectionCarrier.js +29 -1
  74. package/dist/prosemirror/styles/paragraphStyleCascade.d.ts +10 -0
  75. package/dist/prosemirror/styles/paragraphStyleCascade.js +12 -2
  76. package/dist/prosemirror/tableRunIn.d.ts +24 -9
  77. package/dist/prosemirror/tableRunIn.js +107 -21
  78. package/dist/prosemirror/textInput.js +3 -1
  79. package/dist/prosemirror/validation.js +6 -0
  80. package/dist/utils/createDocument.js +11 -8
  81. package/dist/utils/fontLoader.d.ts +1 -1
  82. package/dist/utils/fontLoader.js +3 -11
  83. package/dist/utils/formatToStyle.js +2 -2
  84. package/dist/utils/units.d.ts +5 -0
  85. package/dist/utils/units.js +6 -1
  86. package/package.json +6 -4
@@ -59,6 +59,7 @@ import { expectTextBoxAnchorAttrs } from "../textBoxAnchorAttrs.js";
59
59
  import { textBoxHostParagraph } from "../textBoxHostParagraph.js";
60
60
  import { enclosingInsertionAncestors, inlineWrapperStackOf, trackedRevisionLayerOf } from "../trackedRevisionPath.js";
61
61
  import { assertValidProseMirrorDocument } from "../validation.js";
62
+ import { hyphenElementRunContent } from "./hyphenTextCarriers.js";
62
63
  import { hasSinkChildren } from "./preservedSinkCarriers.js";
63
64
  import { decodeSdtListItems, sdtPropertiesFromAttrs, sdtPropertiesMatchAttrs } from "./sdtAttrs.js";
64
65
  import { TaggedError, panic } from "better-result";
@@ -1659,13 +1660,13 @@ function extractParagraphContent(originalParagraph, _documentCounts, emptyHyperl
1659
1660
  }));
1660
1661
  const ownerId = runIdentityId(node);
1661
1662
  const currentOwnerId = currentRun ? sourceRunOwners.get(currentRun) : void 0;
1662
- const currentRunIsPlainText = currentRun?.content.every((runContent) => runContent.type === "text");
1663
+ const currentRunIsPlainText = currentRun?.content.every(isTextLeafContent);
1663
1664
  const joinsOwnedSourceRun = ownerId !== void 0 && ownerId === currentOwnerId && currentRun !== null && runsShareProperties(currentRun, run);
1664
1665
  const joinsOrdinaryText = coalescePlainText && currentRun !== null && runsShareProperties(currentRun, run) && currentRunIsPlainText === true && currentMarksKey === marksKey;
1665
1666
  if (currentRun && (joinsOwnedSourceRun || joinsOrdinaryText)) {
1666
- if (joinsOrdinaryText) {
1667
- for (const runContent of run.content) if (runContent.type === "text") appendTextToRun(currentRun, runContent.text);
1668
- } else currentRun.content.push(...run.content);
1667
+ if (joinsOrdinaryText) for (const runContent of run.content) if (runContent.type === "text") appendTextToRun(currentRun, runContent.text);
1668
+ else currentRun.content.push(runContent);
1669
+ else currentRun.content.push(...run.content);
1669
1670
  return;
1670
1671
  }
1671
1672
  if (currentRun) content.push(currentRun);
@@ -2266,10 +2267,7 @@ function createTrackedChangeRun({ baseParagraphFormatting, inheritedFormatting,
2266
2267
  const formatting = marksToTextFormatting(marks, formattingContext);
2267
2268
  run = {
2268
2269
  type: "run",
2269
- content: node.text ? [{
2270
- type: "text",
2271
- text: node.text
2272
- }] : [],
2270
+ content: node.text ? textLeafRunContent(node.text, marks) : [],
2273
2271
  ...Object.keys(formatting).length > 0 ? { formatting } : {}
2274
2272
  };
2275
2273
  restoreRunRecord(run, marks);
@@ -2355,7 +2353,7 @@ function getLinkKey(mark) {
2355
2353
  * Create a unique key for a set of marks (excluding hyperlink)
2356
2354
  */
2357
2355
  function getMarksKey(marks) {
2358
- const nonLinkMarks = marks.filter((m) => m.type.name !== "hyperlink");
2356
+ const nonLinkMarks = marks.filter((m) => m.type.name !== "hyperlink" && m.type.name !== "hyphenElement");
2359
2357
  if (nonLinkMarks.length === 0) return "";
2360
2358
  return nonLinkMarks.map((m) => `${m.type.name}:${JSON.stringify(m.attrs)}`).toSorted().join("|");
2361
2359
  }
@@ -2526,6 +2524,23 @@ function createNoteReferenceRun(noteRefMark, marks, formattingContext) {
2526
2524
  restoreRunRecord(run, marks);
2527
2525
  return run;
2528
2526
  }
2527
+ /**
2528
+ * The run content one ProseMirror text leaf writes.
2529
+ *
2530
+ * Text by default; a leaf carrying the hyphen-element mark writes its
2531
+ * carriers back as the elements they were read from.
2532
+ */
2533
+ function textLeafRunContent(text, marks) {
2534
+ if (marks.some(({ type }) => type.name === "hyphenElement")) return hyphenElementRunContent(text);
2535
+ return [{
2536
+ type: "text",
2537
+ text
2538
+ }];
2539
+ }
2540
+ /** Content an ordinary text run may hold and still join the next text leaf. */
2541
+ function isTextLeafContent(content) {
2542
+ return content.type === "text" || content.type === "softHyphen" || content.type === "noBreakHyphen";
2543
+ }
2529
2544
  function createRunFromText({ baseParagraphFormatting, inheritedFormatting, marks, paragraphMarkFormatting, paragraphMarkPrecedesStyle, styleResolver, text }) {
2530
2545
  const formatting = getRunFormattingFromMarks(marks, {
2531
2546
  baseParagraphFormatting,
@@ -2536,10 +2551,7 @@ function createRunFromText({ baseParagraphFormatting, inheritedFormatting, marks
2536
2551
  });
2537
2552
  const run = {
2538
2553
  type: "run",
2539
- content: [{
2540
- type: "text",
2541
- text
2542
- }]
2554
+ content: textLeafRunContent(text, marks)
2543
2555
  };
2544
2556
  if (formatting) run.formatting = formatting;
2545
2557
  restoreRunRecord(run, marks);
@@ -2876,11 +2888,37 @@ const reconcileEmptyFieldResultRuns = (runs, displayRun) => {
2876
2888
  }
2877
2889
  return reconciled;
2878
2890
  };
2879
- const synchronizeFieldDisplayText = (content, displayText, fallbackRun) => {
2880
- const currentText = getFindParagraphText({
2891
+ /**
2892
+ * A result without its hyphen elements, and without a run that held nothing
2893
+ * else. They are part of the text a new display text replaces.
2894
+ */
2895
+ const withoutHyphenElements = (items) => items.filter((item) => {
2896
+ switch (item.type) {
2897
+ case "run": {
2898
+ const kept = item.content.filter(({ type }) => type !== "softHyphen" && type !== "noBreakHyphen");
2899
+ if (kept.length === item.content.length) return true;
2900
+ item.content = kept;
2901
+ return kept.length > 0;
2902
+ }
2903
+ case "hyperlink":
2904
+ item.children = withoutHyphenElements(item.children);
2905
+ return true;
2906
+ case "inlineWrapper":
2907
+ case "insertion":
2908
+ case "deletion":
2909
+ case "moveFrom":
2910
+ case "moveTo":
2911
+ item.content = withoutHyphenElements(item.content);
2912
+ return true;
2913
+ default: return true;
2914
+ }
2915
+ });
2916
+ const synchronizeFieldDisplayText = (fieldContent, displayText, fallbackRun) => {
2917
+ if (getFindParagraphText({
2881
2918
  type: "paragraph",
2882
- content
2883
- });
2919
+ content: fieldContent
2920
+ }) === displayText) return fieldContent;
2921
+ const content = withoutHyphenElements(fieldContent);
2884
2922
  const visitRunsIn = (items, visit) => {
2885
2923
  for (const child of items) {
2886
2924
  if (child.type === "run") {
@@ -2893,7 +2931,6 @@ const synchronizeFieldDisplayText = (content, displayText, fallbackRun) => {
2893
2931
  const visitRuns = (visit) => {
2894
2932
  visitRunsIn(content, visit);
2895
2933
  };
2896
- if (currentText === displayText) return content;
2897
2934
  let replacedText = false;
2898
2935
  visitRuns((run) => {
2899
2936
  for (const runContent of run.content) {
@@ -1,5 +1,27 @@
1
+ import { document_d_exports } from "../../types/document.js";
1
2
  /** These source units render as native text while keeping one logical atom each. */
2
3
  export declare const HYPHEN_TEXT_CARRIERS: {
3
4
  readonly softHyphen: "­";
4
5
  readonly noBreakHyphen: "‑";
5
- };
6
+ };
7
+ /**
8
+ * The mark that tells a carrier written for `w:softHyphen` or
9
+ * `w:noBreakHyphen` apart from the same character authored inside `w:t`.
10
+ *
11
+ * Both render identically, so the text alone cannot say which one the source
12
+ * held; the save leg reads this mark to write each back as it was read.
13
+ */
14
+ export declare const HYPHEN_ELEMENT_MARK_NAME = "hyphenElement";
15
+ type HyphenElementRunContent = document_d_exports.TextContent | document_d_exports.SoftHyphenContent | document_d_exports.NoBreakHyphenContent;
16
+ /**
17
+ * The run content a leaf carrying {@link HYPHEN_ELEMENT_MARK_NAME} writes back.
18
+ *
19
+ * Each carrier becomes its element again and any other character stays text.
20
+ * Typing does not normally reach a marked leaf: the mark is not inclusive and
21
+ * neighbouring carriers hold different ids. Ids only repeat when a copy is pasted
22
+ * beside its source or two paragraphs join; a character typed exactly between
23
+ * two such carriers takes the mark, so a letter stays text here and a typed
24
+ * U+00AD or U+2011 saves as the element.
25
+ */
26
+ export declare const hyphenElementRunContent: (text: string) => HyphenElementRunContent[];
27
+ export {};
@@ -4,4 +4,49 @@ const HYPHEN_TEXT_CARRIERS = {
4
4
  softHyphen: "­",
5
5
  noBreakHyphen: "‑"
6
6
  };
7
- export { HYPHEN_TEXT_CARRIERS };
7
+ /**
8
+ * The mark that tells a carrier written for `w:softHyphen` or
9
+ * `w:noBreakHyphen` apart from the same character authored inside `w:t`.
10
+ *
11
+ * Both render identically, so the text alone cannot say which one the source
12
+ * held; the save leg reads this mark to write each back as it was read.
13
+ */
14
+ const HYPHEN_ELEMENT_MARK_NAME = "hyphenElement";
15
+ const isHyphenElementType = (key) => Object.hasOwn(HYPHEN_TEXT_CARRIERS, key);
16
+ /** The inverse of {@link HYPHEN_TEXT_CARRIERS}, derived so the two cannot drift. */
17
+ const HYPHEN_ELEMENT_BY_CARRIER = new Map(Object.keys(HYPHEN_TEXT_CARRIERS).filter(isHyphenElementType).map((type) => [HYPHEN_TEXT_CARRIERS[type], type]));
18
+ /**
19
+ * The run content a leaf carrying {@link HYPHEN_ELEMENT_MARK_NAME} writes back.
20
+ *
21
+ * Each carrier becomes its element again and any other character stays text.
22
+ * Typing does not normally reach a marked leaf: the mark is not inclusive and
23
+ * neighbouring carriers hold different ids. Ids only repeat when a copy is pasted
24
+ * beside its source or two paragraphs join; a character typed exactly between
25
+ * two such carriers takes the mark, so a letter stays text here and a typed
26
+ * U+00AD or U+2011 saves as the element.
27
+ */
28
+ const hyphenElementRunContent = (text) => {
29
+ const content = [];
30
+ let pending = "";
31
+ for (const char of text) {
32
+ const type = HYPHEN_ELEMENT_BY_CARRIER.get(char);
33
+ if (type === void 0) {
34
+ pending += char;
35
+ continue;
36
+ }
37
+ if (pending.length > 0) {
38
+ content.push({
39
+ type: "text",
40
+ text: pending
41
+ });
42
+ pending = "";
43
+ }
44
+ content.push({ type });
45
+ }
46
+ if (pending.length > 0) content.push({
47
+ type: "text",
48
+ text: pending
49
+ });
50
+ return content;
51
+ };
52
+ export { HYPHEN_ELEMENT_MARK_NAME, HYPHEN_TEXT_CARRIERS, hyphenElementRunContent };
@@ -50,17 +50,17 @@ import { lineSpacingProvenanceFromSpacing } from "../paragraphSpacing.js";
50
50
  import { isRunContentProjectionNode } from "../runContentProjection.js";
51
51
  import { RUN_IDENTITY_MARK_NAME, hasRunIdentityPayload, runIdentityAttrs } from "../runIdentity.js";
52
52
  import { continueSourceCarrierIdentitiesAcrossInsertedParagraphMarks, continueSourceCarrierIdentitiesAcrossRevisions } from "../runIdentityAcrossRevisions.js";
53
- import { getParagraphMarkSuppressionOverrides, hasDirectRunFormatting, isParagraphMarkSuppressionEligible, resolveParagraphInheritedRunFormatting, stripParagraphMarkFormattingForBodyRuns, stripParagraphMarkOnlyFormatting, suppressParagraphMarkFormatting } from "../runStyleFormatting.js";
53
+ import { getParagraphMarkSuppressionOverrides, hasDirectRunFormatting, isParagraphMarkSuppressionEligible, resolveParagraphInheritedRunFormatting, stripParagraphMarkOnlyFormatting, suppressParagraphMarkFormatting } from "../runStyleFormatting.js";
54
54
  import { schema } from "../schema/index.js";
55
55
  import { PRESERVED_XML_LEVELS } from "../schema/nodes.js";
56
- import { extractTableParagraphOverlay, paragraphStyleCascadeAttrs, resolveRunFormattingWithoutDefaults } from "../styles/paragraphStyleCascade.js";
56
+ import { extractTableParagraphOverlay, inheritableParagraphMarkFormatting, paragraphStyleCascadeAttrs, resolveRunFormattingWithoutDefaults } from "../styles/paragraphStyleCascade.js";
57
57
  import { listIndentationProvenancePatch } from "../styles/resolvedStyleAttrs.js";
58
58
  import { cascadeStyleTextFormatting } from "../styles/styleToggleCascade.js";
59
59
  import { tableCellStyleRegions, tableRowBand } from "../styles/tableStyleRegions.js";
60
60
  import { canCarryTrackedRunMark, trackedRunInlineAtomDisposition } from "../trackedRunInlineAtoms.js";
61
61
  import { assertValidProseMirrorDocument } from "../validation.js";
62
62
  import { resolveEffectiveTableCellFormatting } from "./effectiveTableCellFormatting.js";
63
- import { HYPHEN_TEXT_CARRIERS } from "./hyphenTextCarriers.js";
63
+ import { HYPHEN_ELEMENT_MARK_NAME, HYPHEN_TEXT_CARRIERS } from "./hyphenTextCarriers.js";
64
64
  import { createMarkInterner } from "./markInterner.js";
65
65
  import { hasSinkChildren } from "./preservedSinkCarriers.js";
66
66
  import { sdtAttrsFromProperties } from "./sdtAttrs.js";
@@ -425,8 +425,10 @@ const resolveListRendering = (numPr, context) => {
425
425
  function convertParagraph(paragraph, styleResolver, context, extraRunFormatting, tableParagraphOverlay, textBoxAnchors) {
426
426
  const { nextHyperlinkInstanceIndex, pairedBookmarkIds, pageBreakRunSourceDescendants, storyRangedCommentIds, openCommentIds } = context;
427
427
  let runIdentityId = 0;
428
+ let hyphenElementId = 0;
428
429
  const runScope = {
429
430
  nextRunIdentityId: () => runIdentityId++,
431
+ nextHyphenElementId: () => hyphenElementId++,
430
432
  nextNoteReferenceOccurrenceId: context.nextNoteReferenceOccurrenceId,
431
433
  createMark: context.createMark
432
434
  };
@@ -500,9 +502,11 @@ function convertParagraph(paragraph, styleResolver, context, extraRunFormatting,
500
502
  if (!node) return;
501
503
  emitInlineNodes([node]);
502
504
  };
503
- const paragraphRunFormatting = resolveRunFormattingWithoutDefaults(paragraph.formatting?.runProperties, styleResolver);
504
- let inheritableParagraphRunFormatting;
505
- if (paragraphRunFormatting && !isTocParagraph && paragraph.formatting?.styleId === void 0) inheritableParagraphRunFormatting = stripParagraphMarkFormattingForBodyRuns(paragraphRunFormatting);
505
+ const inheritableParagraphRunFormatting = isTocParagraph ? void 0 : inheritableParagraphMarkFormatting({
506
+ runProperties: paragraph.formatting?.runProperties,
507
+ styleId: paragraph.formatting?.styleId,
508
+ styleResolver
509
+ });
506
510
  const { baseFormatting: baseRunFormatting, baseToggleCascade: orderedToggleFormatting, defaultFormatting: ordinaryBaseWithDefaultCharacter, defaultToggleCascade: defaultCharacterStyleCascade, inheritedFormatting: defaultRunFormatting, inheritedToggleCascade: defaultToggleCascade } = resolveParagraphInheritedRunFormatting({
507
511
  styleId: paragraph.formatting?.styleId,
508
512
  tableRunFormatting: extraRunFormatting,
@@ -1739,6 +1743,7 @@ function convertRun(run, resolvedStyleFormatting, runScope, styleResolver, textB
1739
1743
  formatting: mergedFormatting,
1740
1744
  textBoxAnchors,
1741
1745
  authoredFormatting: run.formatting,
1746
+ nextHyphenElementId: runScope.nextHyphenElementId,
1742
1747
  nextNoteReferenceOccurrenceId: runScope.nextNoteReferenceOccurrenceId
1743
1748
  });
1744
1749
  for (const node of contentNodes) if (!isRunContentProjectionNode(content.type, node.type.name)) panic(`Run content ${content.type} projected unsupported inline node ${node.type.name}`);
@@ -2147,7 +2152,7 @@ const noteReferenceVertAlign = (vertAlign) => {
2147
2152
  * run was authored with.
2148
2153
  */
2149
2154
  const carriedRunFormatting = (formatting) => formatting && Object.keys(formatting).length > 0 ? formatting : void 0;
2150
- function convertRunContent({ content, marks, formatting, textBoxAnchors, authoredFormatting, nextNoteReferenceOccurrenceId }) {
2155
+ function convertRunContent({ content, marks, formatting, textBoxAnchors, authoredFormatting, nextHyphenElementId, nextNoteReferenceOccurrenceId }) {
2151
2156
  switch (content.type) {
2152
2157
  case "noteMarker": return [schema.node("noteMarker", { kind: content.kind }).mark(marks)];
2153
2158
  case "text":
@@ -2214,8 +2219,8 @@ function convertRunContent({ content, marks, formatting, textBoxAnchors, authore
2214
2219
  text: content.text,
2215
2220
  level: PRESERVED_XML_LEVELS.run
2216
2221
  }).mark(marks)];
2217
- case "noBreakHyphen": return [schema.text(HYPHEN_TEXT_CARRIERS.noBreakHyphen, marks)];
2218
- case "softHyphen": return [schema.text(HYPHEN_TEXT_CARRIERS.softHyphen, marks)];
2222
+ case "noBreakHyphen":
2223
+ case "softHyphen": return [schema.text(HYPHEN_TEXT_CARRIERS[content.type], [...marks, schema.mark(HYPHEN_ELEMENT_MARK_NAME, { id: nextHyphenElementId() })])];
2219
2224
  case "symbol": return [schema.node("symbol", {
2220
2225
  font: content.font,
2221
2226
  char: content.char
@@ -2459,6 +2464,7 @@ function convertHyperlink(hyperlink, { getInheritedRunFormatting, styleResolver,
2459
2464
  formatting: mergedFormatting,
2460
2465
  textBoxAnchors,
2461
2466
  authoredFormatting: child.formatting,
2467
+ nextHyphenElementId: runScope.nextHyphenElementId,
2462
2468
  nextNoteReferenceOccurrenceId: runScope.nextNoteReferenceOccurrenceId
2463
2469
  }));
2464
2470
  break;
@@ -69,7 +69,7 @@ function createStarterKit(options = {}) {
69
69
  ...options.historyNewGroupDelay !== void 0 ? { newGroupDelay: options.historyNewGroupDelay } : {},
70
70
  ...options.historyShortcuts !== void 0 ? { shortcuts: options.historyShortcuts } : {}
71
71
  }));
72
- for (const name of MARK_NESTING_ORDER) add(name, MARK_EXTENSIONS[name]());
72
+ for (const name of MARK_NESTING_ORDER) add(name, MARK_EXTENSIONS[name]({ getInternalClipboardToken }));
73
73
  add("bookmarkBoundary", BookmarkBoundaryExtension({ getInternalClipboardToken }));
74
74
  add("blockBookmarkBoundary", BlockBookmarkBoundaryExtension({ getInternalClipboardToken }));
75
75
  add("blockMoveRangeBoundary", BlockMoveRangeBoundaryExtension({ getInternalClipboardToken }));
@@ -124,6 +124,7 @@ const EMPTY_SPAN = new RegExp(`<span(?:\\s${TAG_TAIL})?><\\/span>`, "gi");
124
124
  const MAX_EMPTY_SPAN_PASSES = 5;
125
125
  const INTERNAL_CLIPBOARD_ATTR_PATTERN = /\s+data-docx-internal-clipboard(?:="([^"]*)"|='([^']*)'|=([^\s>]+))?/gi;
126
126
  const TEXTBOX_ANCHOR_ATTR = /\s+data-docx-textbox-anchor(?:="[^"]*"|='[^']*')?/gi;
127
+ const HYPHEN_ELEMENT_ATTR = /\s+data-docx-hyphen-element(?:="[^"]*"|='[^']*'|=[^\s>]+)?/gi;
127
128
  const BOOKMARK_BOUNDARY_ATTR = /\s+data-docx-bookmark-(?:boundary|id|name|col-first|col-last)(?:="[^"]*"|='[^']*'|=[^\s>]+)?/gi;
128
129
  const RANGE_ANCHOR_ATTR = /\s+data-docx-range-anchor(?:="[^"]*"|='[^']*'|=[^\s>]+)?/gi;
129
130
  const COMMENT_RANGE_BOUNDARY_ATTR = /\s+data-docx-comment-range-boundary(?:="[^"]*"|='[^']*'|=[^\s>]+)?/gi;
@@ -145,6 +146,10 @@ function stripForeignTextBoxAnchors(html, internal) {
145
146
  if (internal) return html;
146
147
  return html.replace(TEXTBOX_ANCHOR_ATTR, "");
147
148
  }
149
+ function stripForeignHyphenElements(html, internal) {
150
+ if (internal) return html;
151
+ return html.replace(HYPHEN_ELEMENT_ATTR, "");
152
+ }
148
153
  function stripForeignBookmarkBoundaries(html, internal) {
149
154
  if (internal) return html;
150
155
  return html.replace(BOOKMARK_BOUNDARY_ATTR, "");
@@ -187,6 +192,7 @@ function cleanPastedHtml(html, options = {}) {
187
192
  cleaned = stripMsoStyles(cleaned);
188
193
  cleaned = stripEmptySpans(cleaned, internal);
189
194
  cleaned = stripForeignTextBoxAnchors(cleaned, internal);
195
+ cleaned = stripForeignHyphenElements(cleaned, internal);
190
196
  cleaned = stripForeignBookmarkBoundaries(cleaned, internal);
191
197
  cleaned = stripForeignRangeCarriers(cleaned, internal);
192
198
  cleaned = stripForeignStructuredFields(cleaned, internal);
@@ -1,4 +1,5 @@
1
1
  import { mintListInstance } from "../../../docx/listNumberingInstances.js";
2
+ import { NO_NUMBERING_IDS_IN_USE } from "../../../docx/numberingIds.js";
2
3
  import { createNumberingMap } from "../../../docx/numberingParser.js";
3
4
  import { expectParagraphAttrs } from "../../attrs/index.js";
4
5
  import { completeNumberingForDoc } from "../../listInstanceReferences.js";
@@ -93,7 +94,7 @@ const numberPastedHtmlLists = (slice, view) => {
93
94
  const minted = mintListInstance(definitions, {
94
95
  kind: hint.kind,
95
96
  ...hint.start === void 0 ? {} : { start: hint.start }
96
- });
97
+ }, NO_NUMBERING_IDS_IN_USE);
97
98
  definitions = minted.definitions;
98
99
  numbering = createNumberingMap(definitions);
99
100
  group = {
@@ -1,4 +1,5 @@
1
1
  import { MarkExtension } from "./types.js";
2
+ import { HyphenElementExtension } from "./marks/HyphenElementExtension.js";
2
3
  export declare const MARK_EXTENSIONS: {
3
4
  readonly bold: (options?: Partial<Record<string, unknown>> | undefined) => MarkExtension;
4
5
  readonly italic: (options?: Partial<Record<string, unknown>> | undefined) => MarkExtension;
@@ -27,6 +28,7 @@ export declare const MARK_EXTENSIONS: {
27
28
  readonly textEffect: (options?: Partial<Record<string, unknown>> | undefined) => MarkExtension;
28
29
  readonly runFormattingOverride: (options?: Partial<Record<string, unknown>> | undefined) => MarkExtension;
29
30
  readonly characterStyle: (options?: Partial<Record<string, unknown>> | undefined) => MarkExtension;
31
+ readonly hyphenElement: typeof HyphenElementExtension;
30
32
  readonly runIdentity: (options?: Partial<Record<string, unknown>> | undefined) => MarkExtension;
31
33
  readonly comment: (options?: Partial<Record<string, unknown>> | undefined) => MarkExtension;
32
34
  readonly insertion: (options?: Partial<Record<string, unknown>> | undefined) => MarkExtension;
@@ -56,4 +58,4 @@ export type SchemaMarkName = keyof typeof MARK_EXTENSIONS;
56
58
  * own in OOXML, so the save leg ranks it against nothing and its place here is
57
59
  * a presentation decision.
58
60
  */
59
- export declare const MARK_NESTING_ORDER: readonly ["bold", "italic", "underline", "strike", "textColor", "runShading", "highlight", "fontSize", "fontFamily", "language", "superscript", "subscript", "allCaps", "smallCaps", "footnoteRef", "characterSpacing", "emboss", "imprint", "hidden", "textShadow", "emphasisMark", "textOutline", "rtl", "textEffect", "runFormattingOverride", "characterStyle", "comment", "inlineWrapper", "insertion", "deletion", "hyperlink", "runPropertyChange", "runIdentity"];
61
+ export declare const MARK_NESTING_ORDER: readonly ["bold", "italic", "underline", "strike", "textColor", "runShading", "highlight", "fontSize", "fontFamily", "language", "superscript", "subscript", "allCaps", "smallCaps", "footnoteRef", "characterSpacing", "emboss", "imprint", "hidden", "textShadow", "emphasisMark", "textOutline", "rtl", "textEffect", "runFormattingOverride", "characterStyle", "hyphenElement", "comment", "inlineWrapper", "insertion", "deletion", "hyperlink", "runPropertyChange", "runIdentity"];
@@ -9,6 +9,7 @@ import { FootnoteRefExtension } from "./marks/FootnoteRefExtension.js";
9
9
  import { HiddenTextExtension } from "./marks/HiddenTextExtension.js";
10
10
  import { HighlightExtension } from "./marks/HighlightExtension.js";
11
11
  import { HyperlinkExtension } from "./marks/HyperlinkExtension.js";
12
+ import { HyphenElementExtension } from "./marks/HyphenElementExtension.js";
12
13
  import { InlineWrapperExtension } from "./marks/InlineWrapperExtension.js";
13
14
  import { ItalicExtension } from "./marks/ItalicExtension.js";
14
15
  import { LanguageExtension } from "./marks/LanguageExtension.js";
@@ -67,6 +68,7 @@ const MARK_EXTENSIONS = {
67
68
  textEffect: TextEffectExtension,
68
69
  runFormattingOverride: RunFormattingOverrideExtension,
69
70
  characterStyle: CharacterStyleExtension,
71
+ hyphenElement: HyphenElementExtension,
70
72
  runIdentity: RunIdentityExtension,
71
73
  comment: CommentExtension,
72
74
  insertion: InsertionExtension,
@@ -123,6 +125,7 @@ const MARK_NESTING_ORDER = markNestingOrder([
123
125
  "textEffect",
124
126
  "runFormattingOverride",
125
127
  "characterStyle",
128
+ "hyphenElement",
126
129
  "comment",
127
130
  "inlineWrapper",
128
131
  "insertion",
@@ -0,0 +1,6 @@
1
+ import { MarkExtension } from "../types.js";
2
+ type HyphenElementOptions = {
3
+ getInternalClipboardToken?: () => string;
4
+ };
5
+ export declare const HyphenElementExtension: (options?: Partial<HyphenElementOptions> | undefined) => MarkExtension;
6
+ export {};
@@ -0,0 +1,54 @@
1
+ import { HYPHEN_ELEMENT_MARK_NAME } from "../../conversion/hyphenTextCarriers.js";
2
+ import { expectHyphenElementMarkAttrs } from "../../hyphenElementAttrs.js";
3
+ import { createMarkExtension } from "../create.js";
4
+ // src/prosemirror/extensions/marks/HyphenElementExtension.ts
5
+ /**
6
+ * Marks the native text that stands for a `w:softHyphen` or `w:noBreakHyphen`
7
+ * run child (ECMA-376 Part 1, 17.3.3.29 and 17.3.3.18).
8
+ *
9
+ * The editor renders both elements as their Unicode characters so line
10
+ * breaking and measurement treat them as text. The same characters may also be
11
+ * authored inside `w:t`, so without this mark the save leg could not tell
12
+ * which of the two the source held. Nothing is painted for it.
13
+ *
14
+ * Not inclusive, and each carrier in a paragraph has its own `id`: ProseMirror
15
+ * keeps a non-inclusive mark only where both neighbours hold an equal mark, so
16
+ * distinct ids mean a character typed beside or between carriers is text. The
17
+ * ids only tell neighbours apart. They can repeat after a paste beside the
18
+ * source or a paragraph join, which `hyphenElementRunContent` describes.
19
+ *
20
+ * External HTML must not be able to claim the element: `cleanPastedHtml` strips
21
+ * the attribute unless the slice carries the internal clipboard capability, so
22
+ * `toDOM` stamps that capability on every carrier it writes.
23
+ */
24
+ const HYPHEN_ELEMENT_ATTRIBUTE = "data-docx-hyphen-element";
25
+ const CANONICAL_HYPHEN_ELEMENT_ID = /^(?:0|[1-9]\d*)$/u;
26
+ const HyphenElementExtension = createMarkExtension({
27
+ name: HYPHEN_ELEMENT_MARK_NAME,
28
+ schemaMarkName: HYPHEN_ELEMENT_MARK_NAME,
29
+ markSpec: (options) => ({
30
+ attrs: { id: {} },
31
+ inclusive: false,
32
+ parseDOM: [{
33
+ tag: `span[${HYPHEN_ELEMENT_ATTRIBUTE}]`,
34
+ getAttrs(dom) {
35
+ const rawId = dom.getAttribute(HYPHEN_ELEMENT_ATTRIBUTE);
36
+ if (rawId === null || !CANONICAL_HYPHEN_ELEMENT_ID.test(rawId)) return false;
37
+ const id = Number(rawId);
38
+ return Number.isSafeInteger(id) ? { id } : false;
39
+ }
40
+ }],
41
+ toDOM(mark) {
42
+ const { id } = expectHyphenElementMarkAttrs(mark);
43
+ return [
44
+ "span",
45
+ {
46
+ [HYPHEN_ELEMENT_ATTRIBUTE]: String(id),
47
+ ...options.getInternalClipboardToken ? { "data-docx-internal-clipboard": options.getInternalClipboardToken() } : {}
48
+ },
49
+ 0
50
+ ];
51
+ }
52
+ })
53
+ });
54
+ export { HyphenElementExtension };
@@ -1,3 +1,4 @@
1
+ import "./conversion/hyphenTextCarriers.js";
1
2
  import "./extensions/marks/InlineWrapperExtension.js";
2
3
  import { getInlineDisplayText } from "./inlineDisplayText.js";
3
4
  import "./runIdentity.js";
@@ -11,7 +12,7 @@ const fieldCommentCoverage = (fieldKind) => FIELD_COMMENT_COVERAGE[fieldKind];
11
12
  /** Current children alone decide whether a field needs editable structural content. */
12
13
  const fieldRequiresStructuredContent = (children) => {
13
14
  const first = children.at(0);
14
- return children.some((child) => !child.isText || first !== void 0 && !Mark.sameSet(first.marks, child.marks) || child.marks.some((mark) => mark.type.name === "runIdentity" || mark.type.name === "footnoteRef" || mark.type.name === "hyperlink" || mark.type.name === "inlineWrapper" || mark.type.name === "insertion" || mark.type.name === "deletion"));
15
+ return children.some((child) => !child.isText || first !== void 0 && !Mark.sameSet(first.marks, child.marks) || child.marks.some((mark) => mark.type.name === "runIdentity" || mark.type.name === "hyphenElement" || mark.type.name === "footnoteRef" || mark.type.name === "hyperlink" || mark.type.name === "inlineWrapper" || mark.type.name === "insertion" || mark.type.name === "deletion"));
15
16
  };
16
17
  /** Resolution collapses a field only when the atom can retain its result. */
17
18
  const canonicalFieldNode = (node) => {
@@ -0,0 +1,10 @@
1
+ import { ReadProseMirrorAttrsResult } from "../internal/prosemirrorAttrBoundary.js";
2
+ import "./attrs/index.js";
3
+ import { Mark } from "prosemirror-model";
4
+ type HyphenElementMarkAttrs = {
5
+ /** Tells this carrier from its neighbours. Monotonic within one paragraph. */
6
+ id: number;
7
+ };
8
+ export declare const readHyphenElementMarkAttrs: (mark: Mark) => ReadProseMirrorAttrsResult<HyphenElementMarkAttrs>;
9
+ export declare const expectHyphenElementMarkAttrs: (mark: Mark) => HyphenElementMarkAttrs;
10
+ export {};
@@ -0,0 +1,32 @@
1
+ import { panic } from "better-result";
2
+ // src/prosemirror/hyphenElementAttrs.ts
3
+ const readHyphenElementMarkAttrs = (mark) => {
4
+ const id = mark.attrs["id"];
5
+ if (mark.type.name !== "hyphenElement") return {
6
+ ok: false,
7
+ issues: [{
8
+ path: "mark.type.name",
9
+ message: `Expected hyphenElement, got ${mark.type.name}.`
10
+ }]
11
+ };
12
+ if (typeof id !== "number" || !Number.isSafeInteger(id) || id < 0) return {
13
+ ok: false,
14
+ issues: [{
15
+ path: "hyphenElement.attrs.id",
16
+ message: "Expected a non-negative safe integer."
17
+ }]
18
+ };
19
+ return {
20
+ ok: true,
21
+ value: { id }
22
+ };
23
+ };
24
+ const expectHyphenElementMarkAttrs = (mark) => {
25
+ const result = readHyphenElementMarkAttrs(mark);
26
+ if (!result.ok) {
27
+ const details = result.issues.map((issue) => `${issue.path}: ${issue.message}`).join("\n");
28
+ panic(`Invalid ProseMirror hyphen element attrs:\n${details}`);
29
+ }
30
+ return result.value;
31
+ };
32
+ export { expectHyphenElementMarkAttrs, readHyphenElementMarkAttrs };
@@ -6,6 +6,8 @@ import { Transaction } from "prosemirror-state";
6
6
  type BoundaryItem = Extract<document_d_exports.ParagraphContent, {
7
7
  type: "bookmarkStart" | "bookmarkEnd" | "commentRangeStart" | "commentRangeEnd" | "moveFromRangeStart" | "moveFromRangeEnd" | "moveToRangeStart" | "moveToRangeEnd";
8
8
  }>;
9
+ /** The marks that say a node is in a revision: its wrappers, in the engine. */
10
+ export declare const REVISION_MARK_NAMES: ReadonlySet<string>;
9
11
  /** The range boundaries `node` carries; none for any other node. */
10
12
  export declare const rangeBoundaryItemsOf: (node: Node) => readonly BoundaryItem[];
11
13
  /** Range boundary nodes, as the nodes that stand for them between blocks. */
@@ -377,4 +377,4 @@ const removeResolvedSubtree = (node, selection) => {
377
377
  if (selection.mode === "reject") visit(node);
378
378
  return { kept };
379
379
  };
380
- export { blockBoundariesOf, carriesCommentAnchor, inlineBoundariesOf, isBlockBoundaryNode, normalizeKeptRangeBoundaries, paragraphRemainsOf, rangeBoundaryItemsOf, removeResolvedSubtree, revisionLayerRemovesContent };
380
+ export { REVISION_MARK_NAMES, blockBoundariesOf, carriesCommentAnchor, inlineBoundariesOf, isBlockBoundaryNode, normalizeKeptRangeBoundaries, paragraphRemainsOf, rangeBoundaryItemsOf, removeResolvedSubtree, revisionLayerRemovesContent };
@@ -1,4 +1,5 @@
1
1
  import { mintListInstance, restartListInstance } from "../docx/listNumberingInstances.js";
2
+ import { NO_NUMBERING_IDS_IN_USE } from "../docx/numberingIds.js";
2
3
  import { createNumberingMap, isBulletLevel } from "../docx/numberingParser.js";
3
4
  import { paragraphNumberingReferenceId } from "../docx/numberingReference.js";
4
5
  import { expectParagraphAttrs } from "./attrs/index.js";
@@ -179,7 +180,7 @@ const resolveListTarget = ({ numbering, $from, $to, requests }) => {
179
180
  ilvl: 0,
180
181
  numbering
181
182
  };
182
- const minted = mintListInstance(numbering?.definitions, first);
183
+ const minted = mintListInstance(numbering?.definitions, first, NO_NUMBERING_IDS_IN_USE);
183
184
  return {
184
185
  numId: minted.numId,
185
186
  ilvl: 0,
@@ -289,7 +290,7 @@ const setNumberingValue = (value) => withCanonicalCommand((state, dispatch) => {
289
290
  abstractNumId,
290
291
  ilvl: selected.membership.ilvl,
291
292
  start: value
292
- });
293
+ }, NO_NUMBERING_IDS_IN_USE);
293
294
  dispatch(moveItems({
294
295
  state,
295
296
  items: itemsFrom(state, selected.membership.numId, selected.pos),
@@ -1,3 +1,4 @@
1
+ import { hangingFirstLineIndent } from "../utils/units.js";
1
2
  import { effectiveParagraphNumberingReference, paragraphNumberingSourcesFromBoundary } from "./numberingAttr.js";
2
3
  import { panic } from "better-result";
3
4
  // src/prosemirror/paragraphIndentation.ts
@@ -70,7 +71,7 @@ const directParagraphIndentation = (attrs) => {
70
71
  if (indentation.indentFirstLine !== void 0) {
71
72
  const hanging = attrs.hangingIndent === true;
72
73
  indentation.hangingIndent = hanging;
73
- if (hanging) indentation.indentFirstLine = -Math.abs(indentation.indentFirstLine);
74
+ if (hanging) indentation.indentFirstLine = hangingFirstLineIndent(indentation.indentFirstLine);
74
75
  } else if (original?.hangingIndent !== void 0 && original.indentFirstLine === void 0 && typeof attrs.hangingIndent === "boolean") indentation.hangingIndent = attrs.hangingIndent;
75
76
  return Object.keys(indentation).length > 0 ? indentation : void 0;
76
77
  };
@@ -3,7 +3,6 @@ import { NumberingMap } from "../docx/numberingParser.js";
3
3
  import { RunStyleResolver } from "./runStyleFormatting.js";
4
4
  import { Node } from "prosemirror-model";
5
5
  import { Transaction } from "prosemirror-state";
6
- type RecordedFormatting = NonNullable<ParagraphPropertyChangeAttrs["previousFormatting"]>;
7
6
  type CarryParagraphPropertiesOptions = {
8
7
  tr: Transaction;
9
8
  /** The paragraph that takes the properties. */
@@ -14,14 +13,7 @@ type CarryParagraphPropertiesOptions = {
14
13
  numbering: NumberingMap | null;
15
14
  /** Records the change as a tracked property change; applied directly when omitted. */
16
15
  revision?: ParagraphPropertyChangeAttrs["info"];
17
- /** Properties the paragraph keeps its own values of: ones its own batch set. */
18
- keep?: ReadonlySet<string>;
19
16
  };
20
- type CarriedParagraphPropertiesOptions = Pick<CarryParagraphPropertiesOptions, "source" | "keep"> & {
21
- target: Node;
22
- };
23
- /** The exact formatting transfer, shared with pre-mutation capacity checks. */
24
- export declare const carriedParagraphProperties: ({ source, target, keep }: CarriedParagraphPropertiesOptions) => RecordedFormatting;
25
17
  /**
26
18
  * Give a paragraph another one's properties: the pPr a property change
27
19
  * covers. Its identity, mark, mark run properties and section stay its own,
@@ -29,7 +21,7 @@ export declare const carriedParagraphProperties: ({ source, target, keep }: Carr
29
21
  * change now records what it had: a paragraph holds one, and a pending one
30
22
  * already records it.
31
23
  */
32
- export declare const carryParagraphProperties: ({ tr, position, source, styleResolver, numbering, revision, keep }: CarryParagraphPropertiesOptions) => {
24
+ export declare const carryParagraphProperties: ({ tr, position, source, styleResolver, numbering, revision }: CarryParagraphPropertiesOptions) => {
33
25
  tr: Transaction;
34
26
  changed: boolean;
35
27
  tracked: boolean;
@@ -60,9 +52,4 @@ export declare const recordReplacedParagraphProperties: ({ tr, position, replace
60
52
  replaced: Node;
61
53
  revision: ParagraphPropertyChangeAttrs["info"];
62
54
  }) => boolean;
63
- /**
64
- * The properties a batch's own property change set on a paragraph: the ones
65
- * whose value differs from what that change records as before.
66
- */
67
- export declare const propertiesSetInBatch: (node: Node, batchRevisionIds: ReadonlySet<number>) => ReadonlySet<string>;
68
55
  export {};