@stll/folio-core 0.66.1 → 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 (80) 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/i18n/messages/ar.gen.js +1 -0
  20. package/dist/i18n/messages/catalogs.gen.d.ts +17 -0
  21. package/dist/i18n/messages/catalogs.gen.js +17 -0
  22. package/dist/i18n/messages/cs.gen.js +1 -0
  23. package/dist/i18n/messages/de.gen.js +1 -0
  24. package/dist/i18n/messages/en.gen.js +1 -0
  25. package/dist/i18n/messages/es.gen.js +1 -0
  26. package/dist/i18n/messages/et.gen.js +1 -0
  27. package/dist/i18n/messages/fr.gen.js +1 -0
  28. package/dist/i18n/messages/he.gen.js +1 -0
  29. package/dist/i18n/messages/hi.gen.js +1 -0
  30. package/dist/i18n/messages/hu.gen.js +1 -0
  31. package/dist/i18n/messages/lt.gen.js +1 -0
  32. package/dist/i18n/messages/lv.gen.js +1 -0
  33. package/dist/i18n/messages/messages.gen.d.ts +1 -0
  34. package/dist/i18n/messages/pl.gen.js +1 -0
  35. package/dist/i18n/messages/pt-BR.gen.js +1 -0
  36. package/dist/i18n/messages/sk.gen.js +1 -0
  37. package/dist/i18n/messages/tr.gen.js +1 -0
  38. package/dist/i18n/messages/zh-CN.gen.js +1 -0
  39. package/dist/internal/wholeStoryRevisionResolution.d.ts +1 -1
  40. package/dist/internal/wholeStoryRevisionResolution.js +24 -9
  41. package/dist/layout-bridge/convert/runMarkFormatting.js +1 -0
  42. package/dist/markdown/renderRuns.js +68 -12
  43. package/dist/prosemirror/commands/comments.js +2 -0
  44. package/dist/prosemirror/commands/resolveParagraphProperties.js +15 -2
  45. package/dist/prosemirror/conversion/fromProseDoc.js +55 -18
  46. package/dist/prosemirror/conversion/hyphenTextCarriers.d.ts +23 -1
  47. package/dist/prosemirror/conversion/hyphenTextCarriers.js +46 -1
  48. package/dist/prosemirror/conversion/toProseDoc.js +15 -9
  49. package/dist/prosemirror/extensions/StarterKit.js +1 -1
  50. package/dist/prosemirror/extensions/features/pasteCleanup.js +6 -0
  51. package/dist/prosemirror/extensions/features/pastedHtmlLists.js +2 -1
  52. package/dist/prosemirror/extensions/markRegistry.d.ts +3 -1
  53. package/dist/prosemirror/extensions/markRegistry.js +3 -0
  54. package/dist/prosemirror/extensions/marks/HyphenElementExtension.d.ts +6 -0
  55. package/dist/prosemirror/extensions/marks/HyphenElementExtension.js +54 -0
  56. package/dist/prosemirror/fieldRepresentation.js +2 -1
  57. package/dist/prosemirror/hyphenElementAttrs.d.ts +10 -0
  58. package/dist/prosemirror/hyphenElementAttrs.js +32 -0
  59. package/dist/prosemirror/keptRangeBoundaries.d.ts +2 -0
  60. package/dist/prosemirror/keptRangeBoundaries.js +1 -1
  61. package/dist/prosemirror/listNumbering.js +3 -2
  62. package/dist/prosemirror/paragraphIndentation.js +2 -1
  63. package/dist/prosemirror/paragraphPropertyCarry.d.ts +1 -14
  64. package/dist/prosemirror/paragraphPropertyCarry.js +4 -28
  65. package/dist/prosemirror/plugins/suggestionMode.js +19 -6
  66. package/dist/prosemirror/replacedAnnotations.d.ts +1 -0
  67. package/dist/prosemirror/replacedAnnotations.js +1 -0
  68. package/dist/prosemirror/sectionCarrier.d.ts +8 -0
  69. package/dist/prosemirror/sectionCarrier.js +29 -1
  70. package/dist/prosemirror/styles/paragraphStyleCascade.d.ts +10 -0
  71. package/dist/prosemirror/styles/paragraphStyleCascade.js +12 -2
  72. package/dist/prosemirror/tableRunIn.d.ts +24 -9
  73. package/dist/prosemirror/tableRunIn.js +107 -21
  74. package/dist/prosemirror/textInput.js +3 -1
  75. package/dist/prosemirror/validation.js +6 -0
  76. package/dist/utils/createDocument.js +11 -8
  77. package/dist/utils/formatToStyle.js +2 -2
  78. package/dist/utils/units.d.ts +5 -0
  79. package/dist/utils/units.js +6 -1
  80. package/package.json +3 -3
@@ -335,6 +335,7 @@ const messages = { "folio": {
335
335
  "editSaveFailedTitle": "DOCX सहेजा नहीं जा सका",
336
336
  "editSessionTakenOver": "किसी अन्य टैब, विंडो या डिवाइस ने इस दस्तावेज़ को संपादन के लिए खोला, इसलिए यह सत्र बंद कर दिया गया। सहेजे नहीं गए स्थानीय परिवर्तन सुरक्षित हैं — जारी रखने के लिए दस्तावेज़ फिर से खोलें।",
337
337
  "editor": {
338
+ "accessibleName": "दस्तावेज़ संपादक",
338
339
  "editing": "संपादन",
339
340
  "editingDescription": "दस्तावेज़ को सीधे संपादित करें",
340
341
  "outlineDepthAll": "सभी स्तर",
@@ -335,6 +335,7 @@ const messages = { "folio": {
335
335
  "editSaveFailedTitle": "A DOCX nem menthető",
336
336
  "editSessionTakenOver": "Ezt a szerkesztési munkamenetet máshol vették át.",
337
337
  "editor": {
338
+ "accessibleName": "Dokumentumszerkesztő",
338
339
  "editing": "Szerkesztés",
339
340
  "editingDescription": "A dokumentum közvetlen szerkesztése",
340
341
  "outlineDepthAll": "Minden szint",
@@ -335,6 +335,7 @@ const messages = { "folio": {
335
335
  "editSaveFailedTitle": "Nepavyko išsaugoti DOCX",
336
336
  "editSessionTakenOver": "Ši redagavimo sesija buvo perimta kitur.",
337
337
  "editor": {
338
+ "accessibleName": "Dokumentų redaktorius",
338
339
  "editing": "Redagavimas",
339
340
  "editingDescription": "Redaguokite dokumentą tiesiogiai",
340
341
  "outlineDepthAll": "Visi lygiai",
@@ -335,6 +335,7 @@ const messages = { "folio": {
335
335
  "editSaveFailedTitle": "Neizdevās saglabāt DOCX",
336
336
  "editSessionTakenOver": "Šo rediģēšanas sesiju pārņēma citur.",
337
337
  "editor": {
338
+ "accessibleName": "Dokumentu redaktors",
338
339
  "editing": "Rediģēšana",
339
340
  "editingDescription": "Rediģējiet dokumentu tieši",
340
341
  "outlineDepthAll": "Visi līmeņi",
@@ -335,6 +335,7 @@ type Messages = {
335
335
  "editSaveFailedTitle": "Could not save DOCX";
336
336
  "editSessionTakenOver": "Another tab, window, or device opened this document for editing, so this session was released. Unsaved local changes are preserved — reopen the doc to continue.";
337
337
  "editor": {
338
+ "accessibleName": "Document editor";
338
339
  "editing": "Editing";
339
340
  "editingDescription": "Edit the document directly";
340
341
  "outlineDepthAll": "All levels";
@@ -335,6 +335,7 @@ const messages = { "folio": {
335
335
  "editSaveFailedTitle": "Nie udało się zapisać pliku DOCX",
336
336
  "editSessionTakenOver": "Ta sesja edycji została przejęta gdzie indziej.",
337
337
  "editor": {
338
+ "accessibleName": "Edytor dokumentów",
338
339
  "editing": "Edytowanie",
339
340
  "editingDescription": "Edytuj dokument bezpośrednio",
340
341
  "outlineDepthAll": "Wszystkie poziomy",
@@ -335,6 +335,7 @@ const messages = { "folio": {
335
335
  "editSaveFailedTitle": "Não foi possível salvar DOCX",
336
336
  "editSessionTakenOver": "Esta sessão de edição foi assumida em outro lugar.",
337
337
  "editor": {
338
+ "accessibleName": "Editor de documentos",
338
339
  "editing": "Editando",
339
340
  "editingDescription": "Edite o documento diretamente",
340
341
  "outlineDepthAll": "Todos os níveis",
@@ -335,6 +335,7 @@ const messages = { "folio": {
335
335
  "editSaveFailedTitle": "DOCX sa nepodarilo uložiť",
336
336
  "editSessionTakenOver": "Túto reláciu úprav prevzalo iné okno.",
337
337
  "editor": {
338
+ "accessibleName": "Editor dokumentov",
338
339
  "editing": "Úpravy",
339
340
  "editingDescription": "Upravujte dokument priamo",
340
341
  "outlineDepthAll": "Všetky úrovne",
@@ -335,6 +335,7 @@ const messages = { "folio": {
335
335
  "editSaveFailedTitle": "DOCX kaydedilemedi",
336
336
  "editSessionTakenOver": "Başka bir sekme, pencere veya cihaz bu belgeyi düzenlemek için açtığından bu oturum sonlandırıldı. Kaydedilmemiş yerel değişiklikler korunur; devam etmek için belgeyi yeniden açın.",
337
337
  "editor": {
338
+ "accessibleName": "Belge düzenleyici",
338
339
  "editing": "Düzenleme",
339
340
  "editingDescription": "Belgeyi doğrudan düzenleyin",
340
341
  "outlineDepthAll": "Tüm düzeyler",
@@ -335,6 +335,7 @@ const messages = { "folio": {
335
335
  "editSaveFailedTitle": "无法保存 DOCX",
336
336
  "editSessionTakenOver": "另一个标签页、窗口或设备已打开此文档进行编辑,因此本会话已释放。未保存的本地更改会保留,请重新打开文档以继续。",
337
337
  "editor": {
338
+ "accessibleName": "文档编辑器",
338
339
  "editing": "编辑",
339
340
  "editingDescription": "直接编辑文档",
340
341
  "outlineDepthAll": "所有级别",
@@ -11,7 +11,7 @@ type ResolveWholeStoryOptions = {
11
11
  styleResolver: RunStyleResolver | null;
12
12
  numbering: NumberingMap | null;
13
13
  };
14
- export declare const resolveWholeStory: ({ doc, mode, styleResolver, numbering }: ResolveWholeStoryOptions) => {
14
+ export declare const resolveWholeStory: ({ doc: storyDoc, mode, styleResolver, numbering }: ResolveWholeStoryOptions) => {
15
15
  resolved: Node;
16
16
  mapping: Mapping;
17
17
  positionMap: StepMap;
@@ -13,7 +13,8 @@ import { createResolvedCommentPointCollector } from "../prosemirror/replacedAnno
13
13
  import { expandRunFormattingCarrier } from "../prosemirror/runFormattingInlineCarriers.js";
14
14
  import { readAuthoredRunFormatting, reconcileRunFormattingMarks } from "../prosemirror/runFormattingReconciliation.js";
15
15
  import { paragraphRunStyleContext } from "../prosemirror/runStyleFormatting.js";
16
- import { runParagraphsIntoTables, runsIntoFollowingTable } from "../prosemirror/tableRunIn.js";
16
+ import { sectionRecordCopyPositions } from "../prosemirror/sectionCarrier.js";
17
+ import { paragraphBeforeRemovedBlock, runParagraphsIntoTables, runsIntoFollowingTable } from "../prosemirror/tableRunIn.js";
17
18
  import { paragraphContentOf } from "../prosemirror/zeroWidthAnchors.js";
18
19
  import { recordNodeResolution } from "./revisionResolutionEdits.js";
19
20
  import { resolveInlineRevisions } from "./revisionResolutionInline.js";
@@ -36,12 +37,13 @@ const resolveProperties = ({ doc, mode, styleResolver, numbering, steps, changed
36
37
  const walk = ({ node, position, eligibleComments = true, previous }) => {
37
38
  const childComments = eligibleComments && collector.visit(node, position, previous);
38
39
  if (node.isText) return node;
39
- if (!runsIntoTable && !node.isTextblock) {
40
- for (let index = 0; index + 1 < node.childCount; index++) if (runsIntoFollowingTable({
40
+ if (!runsIntoTable && !node.isTextblock) for (let index = 0; index + 1 < node.childCount; index++) {
41
+ const options = {
41
42
  parent: node,
42
43
  index,
43
44
  mode
44
- })) {
45
+ };
46
+ if (runsIntoFollowingTable(options) || paragraphBeforeRemovedBlock(options)) {
45
47
  runsIntoTable = true;
46
48
  break;
47
49
  }
@@ -400,7 +402,7 @@ const resolveStructure = ({ node, position, context }) => {
400
402
  markWasAdded,
401
403
  containerStillEndsWithParagraph: true,
402
404
  followingTableCanReceiveRunIn: false,
403
- removedSectionProperties: markWasAdded && properties !== void 0,
405
+ removedSectionProperties: markWasAdded && ownsSection(paragraph),
404
406
  precedingSectionOwner: precedingSectionOwner(preceding, properties),
405
407
  followingHasSectionProperties: ownsSection(chain.node)
406
408
  });
@@ -612,17 +614,26 @@ const resolveStructure = ({ node, position, context }) => {
612
614
  });
613
615
  return final;
614
616
  };
615
- const resolveWholeStory = ({ doc, mode, styleResolver, numbering }) => {
617
+ const resolveWholeStory = ({ doc: storyDoc, mode, styleResolver, numbering }) => {
618
+ const withoutCopies = new Transform(storyDoc);
619
+ for (const position of sectionRecordCopyPositions(storyDoc)) withoutCopies.setNodeAttribute(position, "_sectionProperties", null);
620
+ const doc = withoutCopies.doc;
616
621
  const { runsIntoTable, ...direct } = resolveStoryRevisions({
617
622
  doc,
618
623
  mode,
619
624
  styleResolver,
620
625
  numbering
621
626
  });
622
- if (!runsIntoTable) return direct;
627
+ if (!runsIntoTable) return {
628
+ ...direct,
629
+ steps: [...withoutCopies.steps, ...direct.steps]
630
+ };
623
631
  const runIn = new Transform(doc);
624
632
  const { targets, maps, transfers } = runParagraphsIntoTables(runIn, mode, styleResolver);
625
- if (!runIn.docChanged) return direct;
633
+ if (!runIn.docChanged) return {
634
+ ...direct,
635
+ steps: [...withoutCopies.steps, ...direct.steps]
636
+ };
626
637
  const { runsIntoTable: _rerun, ...result } = resolveStoryRevisions({
627
638
  doc: runIn.doc,
628
639
  mode,
@@ -642,7 +653,11 @@ const resolveWholeStory = ({ doc, mode, styleResolver, numbering }) => {
642
653
  const positionMap = composeRevisionResolutionMaps(runInMap, result.positionMap);
643
654
  return {
644
655
  ...result,
645
- steps: [...runIn.steps, ...result.steps],
656
+ steps: [
657
+ ...withoutCopies.steps,
658
+ ...runIn.steps,
659
+ ...result.steps
660
+ ],
646
661
  positionMap,
647
662
  mapping: new Mapping([positionMap]),
648
663
  transfers: [...transfers, ...result.transfers]
@@ -56,6 +56,7 @@ const MARK_PAINT_DISPOSITIONS = {
56
56
  deletion: "runFormatting",
57
57
  inlineWrapper: "runFormatting",
58
58
  characterStyle: "resolvedElsewhere",
59
+ hyphenElement: "notPainted",
59
60
  runIdentity: "notPainted",
60
61
  runPropertyChange: "notPainted"
61
62
  };
@@ -148,17 +148,38 @@ function wrapFlanking(inner, mark, { before, after }) {
148
148
  }
149
149
  return units.slice(0, start).join("") + applyMarks(units.slice(start, end).join(""), start < end ? [mark] : []) + units.slice(end).join("");
150
150
  }
151
- /** Paragraph content that writes nothing to the line. */
152
- const SILENT_MARKERS = /* @__PURE__ */ new Set([
153
- "bookmarkStart",
154
- "bookmarkEnd",
155
- "moveFromRangeStart",
156
- "moveFromRangeEnd",
157
- "moveToRangeStart",
158
- "moveToRangeEnd"
159
- ]);
160
- /** Comment range markers, which write nothing when comments are stripped. */
161
- const COMMENT_MARKERS = /* @__PURE__ */ new Set(["commentRangeStart", "commentRangeEnd"]);
151
+ /**
152
+ * Whether paragraph content puts nothing on the line in this render. Left out
153
+ * of the content before runs group, so the runs on either side of it meet as
154
+ * they do on the line: emphasis closed and reopened around nothing reads as
155
+ * literal asterisks. Exhaustive, so new content decides here.
156
+ */
157
+ const writesNothing = (ctx, item) => {
158
+ switch (item.type) {
159
+ case "bookmarkStart":
160
+ case "bookmarkEnd":
161
+ case "moveFromRangeStart":
162
+ case "moveFromRangeEnd":
163
+ case "moveToRangeStart":
164
+ case "moveToRangeEnd":
165
+ case "preservedInline": return true;
166
+ case "commentRangeStart":
167
+ case "commentRangeEnd":
168
+ case "commentReference": return ctx.opts.comments === "strip";
169
+ case "mathEquation": return !item.plainText;
170
+ case "inlineSdt":
171
+ case "inlineWrapper": return item.content.every((child) => writesNothing(ctx, child));
172
+ case "run":
173
+ case "hyperlink":
174
+ case "simpleField":
175
+ case "complexField":
176
+ case "insertion":
177
+ case "deletion":
178
+ case "moveFrom":
179
+ case "moveTo": return false;
180
+ default: return item;
181
+ }
182
+ };
162
183
  /** The first character `piece` puts on the line, inside the marks `outer` already opened. */
163
184
  const pieceStart = (piece, outer, order) => {
164
185
  const first = piece.text.match(INLINE_UNIT)?.[0];
@@ -449,6 +470,40 @@ const cleanRevisionContent = (content) => content.flatMap((item) => {
449
470
  default: return [item];
450
471
  }
451
472
  });
473
+ const COMMENT_MARKER_TYPES = /* @__PURE__ */ new Set([
474
+ "commentRangeStart",
475
+ "commentRangeEnd",
476
+ "commentReference"
477
+ ]);
478
+ /**
479
+ * A comment marker bounds its comment wherever it stands; a save may write it
480
+ * inside the revision wrapper of the text it starts at, or beside it. Revision
481
+ * wrappers are split around their comment markers, so either reads the same.
482
+ */
483
+ const commentMarkersOutsideRevisions = (content) => content.flatMap((item) => {
484
+ switch (item.type) {
485
+ case "insertion":
486
+ case "deletion":
487
+ case "moveFrom":
488
+ case "moveTo": break;
489
+ default: return [item];
490
+ }
491
+ const parts = [];
492
+ let held = [];
493
+ const flush = () => {
494
+ if (held.length > 0) parts.push({
495
+ ...item,
496
+ content: held
497
+ });
498
+ held = [];
499
+ };
500
+ for (const child of commentMarkersOutsideRevisions(item.content)) if (COMMENT_MARKER_TYPES.has(child.type)) {
501
+ flush();
502
+ parts.push(child);
503
+ } else held.push(child);
504
+ flush();
505
+ return parts;
506
+ });
452
507
  /**
453
508
  * Render the full inline content of a paragraph, tracking comment-range
454
509
  * boundaries to apply the configured wrapper.
@@ -459,7 +514,8 @@ function renderParagraphInline(ctx, pkg, content, paraId, edges = {
459
514
  }) {
460
515
  let out = "";
461
516
  const openComments = [];
462
- const items = (ctx.opts.trackedChanges === "clean" ? cleanRevisionContent(content) : content).filter((item) => !SILENT_MARKERS.has(item.type) && !(ctx.opts.comments === "strip" && COMMENT_MARKERS.has(item.type)));
517
+ const hoistedContent = commentMarkersOutsideRevisions(content);
518
+ const items = (ctx.opts.trackedChanges === "clean" ? cleanRevisionContent(hoistedContent) : hoistedContent).filter((item) => !writesNothing(ctx, item));
463
519
  const around = (index) => {
464
520
  const next = items[index + 1];
465
521
  return {
@@ -29,6 +29,7 @@ import { getFolioNodeRevisionCarriers, nodePropertyRevisionSites } from "../revi
29
29
  import { RUN_FORMATTING_MARK_NAMES } from "../runFormattingMarkNames.js";
30
30
  import { reconstructRejectedRunFormattingMarks, restoreHistoricalRunFormatting } from "../runPropertyChangeResolution.js";
31
31
  import { paragraphRunStyleContextAt } from "../runStyleFormatting.js";
32
+ import { sectionRecordCopyPositions } from "../sectionCarrier.js";
32
33
  import { getTableCellMergeChange } from "../tableCellMergeRevision.js";
33
34
  import { isTableCellRetainedInReviewView } from "../tableCellRevisionVisibility.js";
34
35
  import { markRowEmptiedInBatch, reconcileTableGridAfterColumnRemoval, removeRowsWithoutCellsAfterBatch, removeTableRow } from "../tableGridMutation.js";
@@ -391,6 +392,7 @@ function resolveChange(from, to, mode, revisionIds) {
391
392
  if (node.type.spec["tableRole"] === "table" && runInCellPending(node)) pendingTables.push(position);
392
393
  return !node.isTextblock;
393
394
  });
395
+ if (pPrMarkOps.some(({ action }) => action !== "clear")) for (const position of sectionRecordCopyPositions(tr.doc)) tr.setNodeAttribute(position, "_sectionProperties", null);
394
396
  const joinSeams = [];
395
397
  for (const op of pPrMarkOps) {
396
398
  const mappedPos = tr.mapping.map(op.paragraphPos);
@@ -3,7 +3,8 @@ import { expectParagraphAttrs } from "../attrs/index.js";
3
3
  import { rejectedListRenderingPatch } from "../listRendering.js";
4
4
  import { effectiveParagraphNumbering, paragraphNumberingAttr, paragraphNumberingSourcesFromBoundary } from "../numberingAttr.js";
5
5
  import { paragraphIndentationFromFormatting } from "../paragraphIndentation.js";
6
- import { resolveParagraphDefaultTextFormatting } from "../styles/paragraphStyleCascade.js";
6
+ import { resolveParagraphInheritedRunFormatting } from "../runStyleFormatting.js";
7
+ import { inheritableParagraphMarkFormatting, resolveParagraphDefaultTextFormatting } from "../styles/paragraphStyleCascade.js";
7
8
  import { listIndentationProvenancePatch } from "../styles/resolvedStyleAttrs.js";
8
9
  import { paragraphRejectAttrPatch, paragraphRejectOriginalFormatting, removeParagraphPropertyChanges, sectionRejectProperties } from "./propertyChangeScope.js";
9
10
  // src/prosemirror/commands/resolveParagraphProperties.ts
@@ -70,7 +71,19 @@ const resolveParagraphChangeAttrs = ({ node, boundaryCovered, mode, revisionSet,
70
71
  });
71
72
  Object.assign(nextAttrs, indentation);
72
73
  nextAttrs["_originalFormatting"] = restoredFormatting;
73
- if (styleResolver) nextAttrs["defaultTextFormatting"] = resolveParagraphDefaultTextFormatting(rejection.previousFormatting?.styleId, restoredFormatting ?? void 0, styleResolver) ?? null;
74
+ if (styleResolver) {
75
+ const styleId = rejection.previousFormatting?.styleId;
76
+ nextAttrs["defaultTextFormatting"] = (node.content.size === 0 ? resolveParagraphDefaultTextFormatting(styleId, restoredFormatting ?? void 0, styleResolver) : resolveParagraphInheritedRunFormatting({
77
+ styleId,
78
+ tableRunFormatting: expectParagraphAttrs(node)._tableRunFormatting,
79
+ styleResolver,
80
+ paragraphMarkFormatting: inheritableParagraphMarkFormatting({
81
+ runProperties: restoredFormatting?.runProperties,
82
+ styleId,
83
+ styleResolver
84
+ })
85
+ }).inheritedFormatting) ?? null;
86
+ }
74
87
  }
75
88
  }
76
89
  }
@@ -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 }));