@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
@@ -18,8 +18,6 @@ import { panic } from "better-result";
18
18
  * revision, so accepting reads as the direct edit and rejecting restores the
19
19
  * second's own.
20
20
  */
21
- /** The exact formatting transfer, shared with pre-mutation capacity checks. */
22
- const carriedParagraphProperties = ({ source, target, keep }) => Object.fromEntries([...Object.entries(paragraphPropertiesSnapshot(source)).filter(([key]) => !keep?.has(key)), ...Object.entries(paragraphPropertiesSnapshot(target)).filter(([key]) => keep?.has(key))]);
23
21
  /**
24
22
  * Give a paragraph another one's properties: the pPr a property change
25
23
  * covers. Its identity, mark, mark run properties and section stay its own,
@@ -27,7 +25,7 @@ const carriedParagraphProperties = ({ source, target, keep }) => Object.fromEntr
27
25
  * change now records what it had: a paragraph holds one, and a pending one
28
26
  * already records it.
29
27
  */
30
- const carryParagraphProperties = ({ tr, position, source, styleResolver, numbering, revision, keep }) => {
28
+ const carryParagraphProperties = ({ tr, position, source, styleResolver, numbering, revision }) => {
31
29
  const target = tr.doc.nodeAt(position);
32
30
  if (!target || target.type !== source.type) return {
33
31
  tr,
@@ -35,11 +33,7 @@ const carryParagraphProperties = ({ tr, position, source, styleResolver, numberi
35
33
  tracked: false
36
34
  };
37
35
  const previousFormatting = paragraphPropertiesSnapshot(target);
38
- const formatting = carriedParagraphProperties({
39
- source,
40
- target,
41
- ...keep === void 0 ? {} : { keep }
42
- });
36
+ const formatting = paragraphPropertiesSnapshot(source);
43
37
  if (JSON.stringify(previousFormatting) === JSON.stringify(formatting)) return {
44
38
  tr,
45
39
  changed: false,
@@ -74,7 +68,7 @@ const carryParagraphProperties = ({ tr, position, source, styleResolver, numberi
74
68
  }] : []];
75
69
  tr.setNodeMarkup(position, void 0, {
76
70
  ...carried,
77
- ...carriedNumberingProvenance(source, target, keep),
71
+ numPrFromStyle: expectParagraphAttrs(source).numPrFromStyle,
78
72
  _sectionProperties: target.attrs["_sectionProperties"],
79
73
  _propertyChanges: changes.length > 0 ? changes : null
80
74
  });
@@ -91,12 +85,6 @@ const carryParagraphProperties = ({ tr, position, source, styleResolver, numberi
91
85
  };
92
86
  };
93
87
  /**
94
- * Whether the carried numbering is the paragraph's own or its style's: the
95
- * record holds only stated numbering, and a style's numbering
96
- * stays the style's rather than becoming the paragraph's own `w:numPr`.
97
- */
98
- const carriedNumberingProvenance = (source, target, keep) => ({ numPrFromStyle: expectParagraphAttrs(keep?.has("numPr") ? target : source).numPrFromStyle });
99
- /**
100
88
  * The paragraph left once the break of the paragraph at `paragraphPos` goes:
101
89
  * the next one, or, when its own break is pending deletion too, the first
102
90
  * paragraph after it whose break stays. Null when no paragraph follows.
@@ -143,16 +131,4 @@ const recordReplacedParagraphProperties = ({ tr, position, replaced, revision })
143
131
  }]);
144
132
  return true;
145
133
  };
146
- /**
147
- * The properties a batch's own property change set on a paragraph: the ones
148
- * whose value differs from what that change records as before.
149
- */
150
- const propertiesSetInBatch = (node, batchRevisionIds) => {
151
- const changes = expectParagraphAttrs(node)._propertyChanges;
152
- const change = Array.isArray(changes) ? changes.find(({ info }) => batchRevisionIds.has(info.id)) : void 0;
153
- if (change === void 0) return /* @__PURE__ */ new Set();
154
- const before = change.previousFormatting ?? {};
155
- const now = paragraphPropertiesSnapshot(node);
156
- return new Set([.../* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(now)])].filter((key) => JSON.stringify(before[key]) !== JSON.stringify(now[key])));
157
- };
158
- export { carriedParagraphProperties, carryParagraphProperties, paragraphLeftAfter, propertiesSetInBatch, recordReplacedParagraphProperties };
134
+ export { carryParagraphProperties, paragraphLeftAfter, recordReplacedParagraphProperties };
@@ -531,6 +531,17 @@ function applySuggestionInsert(view, from, to, text, pluginState) {
531
531
  if (selectsAll) {
532
532
  from = Math.max(from, Selection.atStart(view.state.doc).from);
533
533
  to = Math.min(to, Selection.atEnd(view.state.doc).to);
534
+ } else if (typesOverSelection && selection instanceof CellSelection) {
535
+ const cellDeletion = view.state.schema.marks["deletion"];
536
+ if (cellDeletion) {
537
+ const ranges = selection.ranges.toSorted((a, b) => b.$from.pos - a.$from.pos);
538
+ for (const range of ranges) markRangeAsDeleted(tr, view.state.doc, range.$from.pos, range.$to.pos, cellDeletion, pluginState, view.state);
539
+ }
540
+ const last = Selection.findFrom(view.state.doc.resolve(to), -1, true);
541
+ if (last && last.to >= from) {
542
+ from = last.to;
543
+ to = last.to;
544
+ }
534
545
  }
535
546
  const replacedFormatting = selectsAll ? (view.state.doc.resolve(from).nodeAfter?.marks ?? []).filter(({ type }) => RUN_FORMATTING_MARK_NAMES.has(type.name)) : null;
536
547
  const insertAttrs = findAdjacentRevision(view.state.doc, from, "insertion", pluginState.author) || makeMarkAttrs(pluginState);
@@ -545,16 +556,18 @@ function applySuggestionInsert(view, from, to, text, pluginState) {
545
556
  to
546
557
  });
547
558
  if (formattingMarks !== void 0) tr.setStoredMarks(formattingMarks);
559
+ const typedFrom = tr.doc.resolve(insertAt).parent.inlineContent ? insertAt : insertAt + 1;
560
+ const typedTo = typedFrom + text.length;
548
561
  tr.insertText(text, insertAt, insertAt);
549
562
  const deletionType = view.state.schema.marks["deletion"];
550
- if (deletionType) tr.removeMark(insertAt, insertAt + text.length, deletionType);
551
- tr.addMark(insertAt, insertAt + text.length, insertionType.create(insertAttrs));
563
+ if (deletionType) tr.removeMark(typedFrom, typedTo, deletionType);
564
+ tr.addMark(typedFrom, typedTo, insertionType.create(insertAttrs));
552
565
  if (replacedFormatting) {
553
- for (const mark of tr.doc.resolve(insertAt).nodeAfter?.marks ?? []) if (RUN_FORMATTING_MARK_NAMES.has(mark.type.name)) tr.removeMark(insertAt, insertAt + text.length, mark.type);
554
- for (const mark of replacedFormatting) tr.addMark(insertAt, insertAt + text.length, mark);
566
+ for (const mark of tr.doc.resolve(typedFrom).nodeAfter?.marks ?? []) if (RUN_FORMATTING_MARK_NAMES.has(mark.type.name)) tr.removeMark(typedFrom, typedTo, mark.type);
567
+ for (const mark of replacedFormatting) tr.addMark(typedFrom, typedTo, mark);
555
568
  }
556
- separateSplitStretch(tr, insertAt, insertAt + text.length, insertAttrs.revisionId);
557
- if (typesOverSelection) tr.setSelection(TextSelection.create(tr.doc, insertAt + text.length));
569
+ separateSplitStretch(tr, typedFrom, typedTo, insertAttrs.revisionId);
570
+ if (typesOverSelection) tr.setSelection(TextSelection.create(tr.doc, typedTo));
558
571
  view.dispatch(tr.scrollIntoView());
559
572
  return true;
560
573
  }
@@ -12,6 +12,7 @@ export declare const NON_INCLUSIVE_MARK_DISPOSITION: {
12
12
  readonly hyperlink: "carry";
13
13
  readonly footnoteRef: "followsTheText";
14
14
  readonly runIdentity: "followsTheText";
15
+ readonly hyphenElement: "followsTheText";
15
16
  readonly insertion: "followsTheText";
16
17
  readonly deletion: "followsTheText";
17
18
  readonly runPropertyChange: "followsTheText";
@@ -84,6 +84,7 @@ const NON_INCLUSIVE_MARK_DISPOSITION = {
84
84
  hyperlink: "carry",
85
85
  footnoteRef: "followsTheText",
86
86
  runIdentity: "followsTheText",
87
+ hyphenElement: "followsTheText",
87
88
  insertion: "followsTheText",
88
89
  deletion: "followsTheText",
89
90
  runPropertyChange: "followsTheText",
@@ -14,6 +14,14 @@ export type SectionBreakType = (typeof SECTION_BREAK_TYPES)[number];
14
14
  export declare const parseSectionBreakType: (value: unknown) => SectionBreakType | null;
15
15
  /** The section record a paragraph holds, or `null` when it ends no section. */
16
16
  export declare const sectionPropertiesOf: (paragraph: Node) => document_d_exports.SectionProperties | null;
17
+ /**
18
+ * The positions of paragraphs that hold only a copy of a section record: every
19
+ * carrier of a record but its last. The from-leg writes the record on the last
20
+ * carrier alone (`keepTheSectionBreakOnTheLastParagraphThatCarriesIt`), so a
21
+ * resolution that lets the last carrier's mark go must not leave a copy to
22
+ * inherit the section a saved package no longer has.
23
+ */
24
+ export declare const sectionRecordCopyPositions: (doc: Node) => number[];
17
25
  /**
18
26
  * The break type a section record states, or `null` when it states none.
19
27
  *
@@ -19,6 +19,34 @@ const parseSectionBreakType = (value) => isSectionBreakType(value) ? value : nul
19
19
  /** The section record a paragraph holds, or `null` when it ends no section. */
20
20
  const sectionPropertiesOf = (paragraph) => paragraph.attrs["_sectionProperties"] ?? null;
21
21
  /**
22
+ * The positions of paragraphs that hold only a copy of a section record: every
23
+ * carrier of a record but its last. The from-leg writes the record on the last
24
+ * carrier alone (`keepTheSectionBreakOnTheLastParagraphThatCarriesIt`), so a
25
+ * resolution that lets the last carrier's mark go must not leave a copy to
26
+ * inherit the section a saved package no longer has.
27
+ */
28
+ const sectionRecordCopyPositions = (doc) => {
29
+ const lastCarrier = /* @__PURE__ */ new Map();
30
+ const copies = [];
31
+ const visit = (parent, parentStart) => {
32
+ parent.forEach((node, offset) => {
33
+ const position = parentStart + offset;
34
+ if (!node.isTextblock) {
35
+ if (!node.isLeaf) visit(node, position + 1);
36
+ return;
37
+ }
38
+ if (node.type.name !== "paragraph") return;
39
+ const record = sectionPropertiesOf(node);
40
+ if (record === null) return;
41
+ const earlier = lastCarrier.get(record);
42
+ if (earlier !== void 0) copies.push(earlier);
43
+ lastCarrier.set(record, position);
44
+ });
45
+ };
46
+ visit(doc, 0);
47
+ return copies;
48
+ };
49
+ /**
22
50
  * The break type a section record states, or `null` when it states none.
23
51
  *
24
52
  * The single derivation every reader goes through. Every producer of a record
@@ -36,4 +64,4 @@ const sectionBreakTypeOf = (properties) => properties?.sectionStart ?? null;
36
64
  * package does with a `w:sectPr` that omits them.
37
65
  */
38
66
  const mintSectionProperties = (breakType) => ({ sectionStart: breakType });
39
- export { SECTION_BREAK_TYPES, mintSectionProperties, parseSectionBreakType, sectionBreakTypeOf, sectionPropertiesOf };
67
+ export { SECTION_BREAK_TYPES, mintSectionProperties, parseSectionBreakType, sectionBreakTypeOf, sectionPropertiesOf, sectionRecordCopyPositions };
@@ -13,6 +13,16 @@ export type ParagraphCascadeResolver = Pick<StyleEngine, "getStyle" | "getDocDef
13
13
  */
14
14
  type ParagraphDefaultFormattingResolver = Pick<StyleEngine, "getStyle" | "getDocDefaults" | "getDefaultParagraphStyle" | "getDefaultCharacterStyle" | "getRunStyleOwnProperties">;
15
15
  export declare function resolveRunFormattingWithoutDefaults(formatting: document_d_exports.TextFormatting | undefined, styleResolver: ParagraphDefaultFormattingResolver | null): document_d_exports.TextFormatting | undefined;
16
+ /**
17
+ * What a paragraph's mark lends its body runs: nothing when the paragraph has a
18
+ * style, and never the font or the mark-only decorations. One rule for a
19
+ * document read and for a paragraph whose properties a review restores.
20
+ */
21
+ export declare const inheritableParagraphMarkFormatting: ({ runProperties, styleId, styleResolver }: {
22
+ runProperties: document_d_exports.TextFormatting | undefined;
23
+ styleId: string | undefined;
24
+ styleResolver: Parameters<typeof resolveRunFormattingWithoutDefaults>[1];
25
+ }) => document_d_exports.TextFormatting | undefined;
16
26
  /** @internal Recompute a paragraph's inherited run defaults from authored package state. */
17
27
  export declare function resolveParagraphDefaultTextFormatting(styleId: string | undefined, formatting: document_d_exports.Paragraph["formatting"] | undefined, styleResolver: ParagraphDefaultFormattingResolver, options?: {
18
28
  includeParagraphMarkRunProperties?: boolean;
@@ -5,7 +5,7 @@ import { paragraphNumberingAttr } from "../numberingAttr.js";
5
5
  import { directionFromBidi } from "../paragraphDirection.js";
6
6
  import { styleResolvedParagraphFormatting } from "../paragraphFormattingProvenance.js";
7
7
  import { lineSpacingProvenanceFromSpacing } from "../paragraphSpacing.js";
8
- import { stripParagraphMarkOnlyFormatting } from "../runStyleFormatting.js";
8
+ import { stripParagraphMarkFormattingForBodyRuns, stripParagraphMarkOnlyFormatting } from "../runStyleFormatting.js";
9
9
  import { cascadeStyleTextFormatting } from "./styleToggleCascade.js";
10
10
  // src/prosemirror/styles/paragraphStyleCascade.ts
11
11
  /**
@@ -30,6 +30,16 @@ function resolveRunFormattingWithoutDefaults(formatting, styleResolver) {
30
30
  type: "direct"
31
31
  }]).formatting;
32
32
  }
33
+ /**
34
+ * What a paragraph's mark lends its body runs: nothing when the paragraph has a
35
+ * style, and never the font or the mark-only decorations. One rule for a
36
+ * document read and for a paragraph whose properties a review restores.
37
+ */
38
+ const inheritableParagraphMarkFormatting = ({ runProperties, styleId, styleResolver }) => {
39
+ if (styleId !== void 0) return void 0;
40
+ const formatting = resolveRunFormattingWithoutDefaults(runProperties, styleResolver);
41
+ return formatting ? stripParagraphMarkFormattingForBodyRuns(formatting) : void 0;
42
+ };
33
43
  /** @internal Recompute a paragraph's inherited run defaults from authored package state. */
34
44
  function resolveParagraphDefaultTextFormatting(styleId, formatting, styleResolver, options = {}) {
35
45
  const style = styleId ? styleResolver.getStyle(styleId) ?? styleResolver.getDefaultParagraphStyle() : styleResolver.getDefaultParagraphStyle();
@@ -155,4 +165,4 @@ function tableCellParagraphOverlay(styleResolver, tableStyleId, regions) {
155
165
  }
156
166
  return overlay;
157
167
  }
158
- export { extractTableParagraphOverlay, paragraphStyleCascadeAttrs, resolveParagraphDefaultTextFormatting, resolveRunFormattingWithoutDefaults, tableCellParagraphOverlay };
168
+ export { extractTableParagraphOverlay, inheritableParagraphMarkFormatting, paragraphStyleCascadeAttrs, resolveParagraphDefaultTextFormatting, resolveRunFormattingWithoutDefaults, tableCellParagraphOverlay };
@@ -1,20 +1,26 @@
1
1
  import { RunStyleResolver } from "./runStyleFormatting.js";
2
2
  import { ParagraphPropertySourceTransfer } from "../docx/paragraphPropertySource.js";
3
- import { MarkType, Node } from "prosemirror-model";
3
+ import { Node } from "prosemirror-model";
4
4
  import { StepMap, Transform } from "prosemirror-transform";
5
+ type ResolutionMode = "accept" | "reject";
5
6
  /**
6
7
  * Whether the cell a paragraph's words would run into is itself pending: its
7
8
  * row or the cell (through nested tables' first cells) is inserted, deleted
8
- * or merged, so resolving may not keep it. Pending rows or cells elsewhere in
9
- * the table leave that cell where it is.
9
+ * or merged, so resolving may not keep it. A deletion is not pending for a
10
+ * rejection of every change. Pending rows or cells elsewhere in the table leave
11
+ * that cell where it is.
10
12
  */
11
- export declare const runInCellPending: (table: Node) => boolean;
13
+ export declare const runInCellPending: (table: Node, everyChange?: ResolutionMode) => boolean;
12
14
  type RunParagraphIntoTableOptions = {
13
15
  tr: Transform;
14
16
  paragraphPos: number;
15
17
  styleResolver: RunStyleResolver | null;
16
- /** Content a resolution in progress removes: it does not count as words left. */
17
- removedMark?: MarkType | undefined;
18
+ /**
19
+ * Set when every change of the story resolves this way, not only some: what
20
+ * it removes does not count as words left, and a rejection takes a deleted
21
+ * row as staying.
22
+ */
23
+ everyChange?: ResolutionMode | undefined;
18
24
  };
19
25
  /**
20
26
  * Run the paragraph at `paragraphPos` on into the table right after it.
@@ -22,7 +28,7 @@ type RunParagraphIntoTableOptions = {
22
28
  * paragraph-property source it hands over, or null when it did not run in and
23
29
  * the caller keeps its own handling.
24
30
  */
25
- export declare const runParagraphIntoTable: ({ tr, paragraphPos, styleResolver, removedMark }: RunParagraphIntoTableOptions) => {
31
+ export declare const runParagraphIntoTable: ({ tr, paragraphPos, styleResolver, everyChange }: RunParagraphIntoTableOptions) => {
26
32
  position: number;
27
33
  map: StepMap;
28
34
  transfer: ParagraphPropertySourceTransfer;
@@ -34,8 +40,17 @@ export declare const runParagraphIntoTable: ({ tr, paragraphPos, styleResolver,
34
40
  type RunsIntoFollowingTableOptions = {
35
41
  parent: Node;
36
42
  index: number;
37
- mode: "accept" | "reject";
43
+ mode: ResolutionMode;
38
44
  };
45
+ /**
46
+ * Whether what stands right after a paragraph whose mark goes is itself removed
47
+ * by this resolution, so the paragraph runs on into what stands after that, as
48
+ * it would had the removed block never been there: a table it removes, or, when
49
+ * rejecting, an inserted paragraph it empties with a table after it. (Accepted,
50
+ * a deleted paragraph did stand there, and the join ended at it. Paragraphs join
51
+ * among themselves without help.)
52
+ */
53
+ export declare const paragraphBeforeRemovedBlock: ({ parent, index, mode }: RunsIntoFollowingTableOptions) => boolean;
39
54
  export declare const runsIntoFollowingTable: ({ parent, index, mode }: RunsIntoFollowingTableOptions) => boolean;
40
55
  /**
41
56
  * Before every change in a story is resolved: run each paragraph whose mark
@@ -43,7 +58,7 @@ export declare const runsIntoFollowingTable: ({ parent, index, mode }: RunsIntoF
43
58
  * paragraph that took words (its position after the step count given) and
44
59
  * the paragraph-property sources handed over.
45
60
  */
46
- export declare const runParagraphsIntoTables: (tr: Transform, mode: "accept" | "reject", styleResolver: RunStyleResolver | null) => {
61
+ export declare const runParagraphsIntoTables: (tr: Transform, mode: ResolutionMode, styleResolver: RunStyleResolver | null) => {
47
62
  targets: {
48
63
  position: number;
49
64
  step: number;
@@ -1,11 +1,12 @@
1
1
  import { getProseParagraphPropertySourceToken, recreateProseNodeWithParagraphPropertySource } from "../docx/paragraphPropertySource.js";
2
2
  import { expectParagraphAttrs } from "./attrs/index.js";
3
- import { paragraphRemainsOf } from "./keptRangeBoundaries.js";
3
+ import { REVISION_MARK_NAMES, paragraphRemainsOf, revisionLayerRemovesContent } from "./keptRangeBoundaries.js";
4
4
  import { rebaseParagraphRuns } from "./rebaseParagraphRunFormatting.js";
5
5
  import { paragraphRunStyleContext } from "./runStyleFormatting.js";
6
+ import { trackedRevisionPathOf } from "./trackedRevisionPath.js";
6
7
  import { paragraphContentOf } from "./zeroWidthAnchors.js";
7
8
  import { Fragment, Slice } from "prosemirror-model";
8
- import { ReplaceAroundStep, StepMap } from "prosemirror-transform";
9
+ import { ReplaceAroundStep, ReplaceStep, StepMap } from "prosemirror-transform";
9
10
  import { paragraphMarkOutcome } from "@stll/docx-core/ops";
10
11
  // src/prosemirror/tableRunIn.ts
11
12
  /**
@@ -19,35 +20,53 @@ import { paragraphMarkOutcome } from "@stll/docx-core/ops";
19
20
  *
20
21
  * A paragraph with nothing left to carry is simply removed by the resolvers;
21
22
  * one that ends a section keeps its place, as does one before a table whose
22
- * rows or cells are themselves pending.
23
+ * first row or cell may not stay. Rejecting every change gives the document
24
+ * before back, so a deleted row stays for it. A table that resolving every
25
+ * change removes never stood in the way: the paragraph runs on into what follows
26
+ * that table.
23
27
  */
24
28
  /**
25
29
  * Whether the cell a paragraph's words would run into is itself pending: its
26
30
  * row or the cell (through nested tables' first cells) is inserted, deleted
27
- * or merged, so resolving may not keep it. Pending rows or cells elsewhere in
28
- * the table leave that cell where it is.
31
+ * or merged, so resolving may not keep it. A deletion is not pending for a
32
+ * rejection of every change. Pending rows or cells elsewhere in the table leave
33
+ * that cell where it is.
29
34
  */
30
- const runInCellPending = (table) => {
35
+ const runInCellPending = (table, everyChange) => {
36
+ const deletionStays = everyChange === "reject";
31
37
  let node = table;
32
38
  while (node?.type.spec["tableRole"] === "table") {
33
39
  const row = node.firstChild;
34
40
  if (!row) return false;
35
- if (row.attrs["trIns"] != null || row.attrs["trDel"] != null) return true;
41
+ if (row.attrs["trIns"] != null) return true;
42
+ if (row.attrs["trDel"] != null && !deletionStays) return true;
36
43
  const cell = row.firstChild;
37
44
  if (!cell) return false;
38
- if (cell.attrs["cellMarker"] != null) return true;
45
+ const marker = cell.attrs["cellMarker"];
46
+ if (marker != null && !(deletionStays && marker.kind === "del")) return true;
39
47
  node = cell.firstChild;
40
48
  }
41
49
  return false;
42
50
  };
51
+ /**
52
+ * Whether resolving every change takes this row out with what it records. The
53
+ * table it stands in goes when all its rows do.
54
+ */
55
+ const rowGoes = (row, mode) => row.attrs[mode === "accept" ? "trDel" : "trIns"] != null;
56
+ /** Whether resolving every change in `mode` removes the whole table. */
57
+ const tableGoes = (table, mode) => {
58
+ if (table.type.spec["tableRole"] !== "table" || table.childCount === 0) return false;
59
+ for (let index = 0; index < table.childCount; index++) if (!rowGoes(table.child(index), mode)) return false;
60
+ return true;
61
+ };
43
62
  /** The first paragraph of a table's first cell, descending into nested tables. */
44
- const runInTarget = (doc, tablePos) => {
63
+ const runInTarget = (doc, tablePos, everyChange) => {
45
64
  let node = doc.nodeAt(tablePos);
46
65
  let pos = tablePos;
47
66
  const table = node;
48
67
  const firstRow = table?.firstChild;
49
68
  if (!table || table.type.spec["tableRole"] !== "table" || !firstRow) return null;
50
- if (runInCellPending(table)) return null;
69
+ if (runInCellPending(table, everyChange)) return null;
51
70
  while (node && node.type.name !== "paragraph") {
52
71
  const child = node.firstChild;
53
72
  if (!child || child.isInline) return null;
@@ -59,22 +78,22 @@ const runInTarget = (doc, tablePos) => {
59
78
  node
60
79
  } : null;
61
80
  };
62
- const paragraphAfterContentRemoval = (paragraph, removedMark) => removedMark ? paragraph.copy(Fragment.fromArray(paragraph.children.filter((child) => !removedMark.isInSet(child.marks)))) : paragraph;
81
+ const paragraphAfterContentRemoval = (paragraph, everyChange) => everyChange ? paragraph.copy(Fragment.fromArray(paragraph.children.filter((child) => !child.marks.some((mark) => REVISION_MARK_NAMES.has(mark.type.name) && trackedRevisionPathOf(mark).some((layer) => revisionLayerRemovesContent(layer, everyChange)))))) : paragraph;
63
82
  /**
64
83
  * Run the paragraph at `paragraphPos` on into the table right after it.
65
84
  * Returns where the paragraph that took its words now starts and the
66
85
  * paragraph-property source it hands over, or null when it did not run in and
67
86
  * the caller keeps its own handling.
68
87
  */
69
- const runParagraphIntoTable = ({ tr, paragraphPos, styleResolver, removedMark }) => {
88
+ const runParagraphIntoTable = ({ tr, paragraphPos, styleResolver, everyChange }) => {
70
89
  const paragraph = tr.doc.nodeAt(paragraphPos);
71
90
  if (paragraph?.type.name !== "paragraph") return null;
72
91
  if (expectParagraphAttrs(paragraph)._sectionProperties !== void 0) return null;
73
- const left = paragraphAfterContentRemoval(paragraph, removedMark);
92
+ const left = paragraphAfterContentRemoval(paragraph, everyChange);
74
93
  const remains = paragraphRemainsOf(left);
75
94
  if (!paragraphContentOf(left) && (remains.blocks.length > 0 || remains.comments.length === 0)) return null;
76
95
  const tablePos = paragraphPos + paragraph.nodeSize;
77
- const target = runInTarget(tr.doc, tablePos);
96
+ const target = runInTarget(tr.doc, tablePos, everyChange);
78
97
  if (!target) return null;
79
98
  const $target = tr.doc.resolve(target.pos + 1);
80
99
  const containerDepth = tr.doc.resolve(paragraphPos).depth;
@@ -118,6 +137,36 @@ const markGoes = (paragraph, mode) => {
118
137
  if (!mark) return false;
119
138
  return (mark.kind === "ins" || mark.kind === "moveTo") !== (mode === "accept");
120
139
  };
140
+ /** Whether resolving every change in `mode` leaves nothing of this paragraph. */
141
+ const paragraphGoes = (paragraph, mode) => {
142
+ if (paragraph.type.name !== "paragraph" || !markGoes(paragraph, mode) || expectParagraphAttrs(paragraph)._sectionProperties != null) return false;
143
+ const left = paragraphAfterContentRemoval(paragraph, mode);
144
+ if (paragraphContentOf(left)) return false;
145
+ const remains = paragraphRemainsOf(left);
146
+ return remains.blocks.length === 0 && remains.comments.length === 0 && remains.inline.length === 0;
147
+ };
148
+ /**
149
+ * Whether what stands right after a paragraph whose mark goes is itself removed
150
+ * by this resolution, so the paragraph runs on into what stands after that, as
151
+ * it would had the removed block never been there: a table it removes, or, when
152
+ * rejecting, an inserted paragraph it empties with a table after it. (Accepted,
153
+ * a deleted paragraph did stand there, and the join ended at it. Paragraphs join
154
+ * among themselves without help.)
155
+ */
156
+ const paragraphBeforeRemovedBlock = ({ parent, index, mode }) => {
157
+ const paragraph = parent.child(index);
158
+ const next = parent.maybeChild(index + 1);
159
+ if (paragraph.type.name !== "paragraph" || !next || !markGoes(paragraph, mode)) return false;
160
+ if (tableGoes(next, mode)) return true;
161
+ if (mode !== "reject" || paragraphGoes(paragraph, mode)) return false;
162
+ let following = index + 1;
163
+ while (following < parent.childCount) {
164
+ const block = parent.child(following);
165
+ if (!paragraphGoes(block, mode)) break;
166
+ following += 1;
167
+ }
168
+ return following > index + 1 && parent.maybeChild(following)?.type.spec["tableRole"] === "table";
169
+ };
121
170
  const runsIntoFollowingTable = ({ parent, index, mode }) => {
122
171
  const paragraph = parent.child(index);
123
172
  const next = parent.maybeChild(index + 1);
@@ -125,7 +174,6 @@ const runsIntoFollowingTable = ({ parent, index, mode }) => {
125
174
  const remains = paragraphRemainsOf(paragraph);
126
175
  const holdsContent = paragraphContentOf(paragraph);
127
176
  const needsCommentHost = !holdsContent && remains.blocks.length === 0 && remains.comments.length > 0;
128
- const removedMark = paragraph.type.schema.marks[mode === "accept" ? "deletion" : "insertion"];
129
177
  let hasPrecedingCommentHost = false;
130
178
  for (let previousIndex = needsCommentHost ? index - 1 : -1; previousIndex >= 0; previousIndex -= 1) {
131
179
  const previous = parent.child(previousIndex);
@@ -134,7 +182,7 @@ const runsIntoFollowingTable = ({ parent, index, mode }) => {
134
182
  hasPrecedingCommentHost = true;
135
183
  break;
136
184
  }
137
- const retained = paragraphAfterContentRemoval(previous, removedMark);
185
+ const retained = paragraphAfterContentRemoval(previous, mode);
138
186
  if (paragraphContentOf(retained)) break;
139
187
  if (paragraphRemainsOf(retained).blocks.length > 0) {
140
188
  hasPrecedingCommentHost = true;
@@ -146,22 +194,60 @@ const runsIntoFollowingTable = ({ parent, index, mode }) => {
146
194
  holdsContent,
147
195
  markWasAdded: expectParagraphAttrs(paragraph).pPrMark?.kind === "ins" || expectParagraphAttrs(paragraph).pPrMark?.kind === "moveTo",
148
196
  containerStillEndsWithParagraph: false,
149
- followingTableCanReceiveRunIn: expectParagraphAttrs(paragraph)._sectionProperties === void 0 && !runInCellPending(next),
197
+ followingTableCanReceiveRunIn: expectParagraphAttrs(paragraph)._sectionProperties === void 0 && !runInCellPending(next, mode),
150
198
  needsFollowingCommentHost: needsCommentHost && !hasPrecedingCommentHost
151
199
  }).kind === "run-in";
152
200
  };
153
201
  /**
202
+ * Stand each block this resolution removes before the paragraph whose mark goes
203
+ * right before it, so that paragraph meets what it will run on into. The block
204
+ * is going, so where it goes from does not show; the paragraph's words keep
205
+ * mapping to themselves.
206
+ */
207
+ const moveRemovedBlocksBeforeParagraphs = (tr, mode) => {
208
+ const maps = [];
209
+ for (;;) {
210
+ let move = null;
211
+ tr.doc.descendants((node, position, parent, index) => {
212
+ if (move !== null) return false;
213
+ if (node.type.name !== "paragraph") return !node.isTextblock;
214
+ const block = parent?.maybeChild(index + 1);
215
+ if (parent && block && paragraphBeforeRemovedBlock({
216
+ parent,
217
+ index,
218
+ mode
219
+ })) move = {
220
+ position,
221
+ paragraph: node,
222
+ block
223
+ };
224
+ return false;
225
+ });
226
+ if (move === null) return maps;
227
+ const { position, paragraph, block } = move;
228
+ const blockPosition = position + paragraph.nodeSize;
229
+ tr.step(new ReplaceStep(position, blockPosition + block.nodeSize, new Slice(Fragment.fromArray([block, paragraph]), 0, 0)));
230
+ maps.push(new StepMap([
231
+ position,
232
+ 0,
233
+ block.nodeSize,
234
+ blockPosition,
235
+ block.nodeSize,
236
+ 0
237
+ ]));
238
+ }
239
+ };
240
+ /**
154
241
  * Before every change in a story is resolved: run each paragraph whose mark
155
242
  * goes and that sits right before a table on into that table. Returns each
156
243
  * paragraph that took words (its position after the step count given) and
157
244
  * the paragraph-property sources handed over.
158
245
  */
159
246
  const runParagraphsIntoTables = (tr, mode, styleResolver) => {
160
- const removedMark = tr.doc.type.schema.marks[mode === "accept" ? "deletion" : "insertion"];
161
247
  const tried = /* @__PURE__ */ new Set();
162
248
  const targets = [];
163
249
  const transfers = [];
164
- const maps = [];
250
+ const maps = moveRemovedBlocksBeforeParagraphs(tr, mode);
165
251
  for (;;) {
166
252
  let candidate = null;
167
253
  tr.doc.descendants((node, pos, parent, index) => {
@@ -183,7 +269,7 @@ const runParagraphsIntoTables = (tr, mode, styleResolver) => {
183
269
  tr,
184
270
  paragraphPos: candidate,
185
271
  styleResolver,
186
- removedMark
272
+ everyChange: mode
187
273
  });
188
274
  if (ranIn !== null) {
189
275
  targets.push({
@@ -196,4 +282,4 @@ const runParagraphsIntoTables = (tr, mode, styleResolver) => {
196
282
  }
197
283
  }
198
284
  };
199
- export { runInCellPending, runParagraphIntoTable, runParagraphsIntoTables, runsIntoFollowingTable };
285
+ export { paragraphBeforeRemovedBlock, runInCellPending, runParagraphIntoTable, runParagraphsIntoTables, runsIntoFollowingTable };
@@ -30,8 +30,10 @@ const dispatchEditorTextInput = (view, input) => {
30
30
  });
31
31
  const tr = view.state.tr;
32
32
  if (formattingMarks !== void 0) tr.setStoredMarks(formattingMarks);
33
+ const typesOverSelection = !selection.empty && selection.from === from && selection.to === to;
34
+ if (typesOverSelection && !selection.$from.parent.inlineContent) return tr.insertText(text);
33
35
  tr.insertText(text, from, to);
34
- return !selection.empty && selection.from === from && selection.to === to ? tr.setSelection(TextSelection.create(tr.doc, from + text.length)) : tr;
36
+ return typesOverSelection ? tr.setSelection(TextSelection.create(tr.doc, from + text.length)) : tr;
35
37
  }
36
38
  const start = Selection.atStart(view.state.doc);
37
39
  const end = Selection.atEnd(view.state.doc);
@@ -3,7 +3,9 @@ import { readBlockCustomXmlAttrs, readBlockSdtAttrs, readCharacterSpacingMarkAtt
3
3
  import { readBookmarkBoundaryAttrs } from "./bookmarkBoundaryAttrs.js";
4
4
  import { readCommentRangeBoundaryAttrs } from "./commentRangeBoundaryAttrs.js";
5
5
  import { readCommentReferenceAttrs } from "./commentReferenceAttrs.js";
6
+ import { HYPHEN_ELEMENT_MARK_NAME } from "./conversion/hyphenTextCarriers.js";
6
7
  import { fieldRequiresStructuredContent } from "./fieldRepresentation.js";
8
+ import { readHyphenElementMarkAttrs } from "./hyphenElementAttrs.js";
7
9
  import { readMoveRangeBoundaryAttrs } from "./moveRangeBoundaryAttrs.js";
8
10
  import { readRangeAnchorAttrs } from "./rangeAnchorAttrs.js";
9
11
  import { isRunContentInlineNodeName } from "./runContentProjection.js";
@@ -15,6 +17,7 @@ import { readTextBoxAnchorAttrs } from "./textBoxAnchorAttrs.js";
15
17
  const COMPLEX_FIELD_RESULT_MARK_NAMES = /* @__PURE__ */ new Set([
16
18
  ...RUN_FORMATTING_MARK_NAMES,
17
19
  RUN_IDENTITY_MARK_NAME,
20
+ HYPHEN_ELEMENT_MARK_NAME,
18
21
  "runPropertyChange",
19
22
  "footnoteRef"
20
23
  ]);
@@ -352,6 +355,9 @@ const validateMark = (mark, markPath, issues) => {
352
355
  case "textShadow":
353
356
  case "textOutline":
354
357
  case "rtl": return;
358
+ case "hyphenElement":
359
+ appendAttrIssues(markPath, readHyphenElementMarkAttrs(mark), issues);
360
+ return;
355
361
  case "textEffect":
356
362
  appendAttrIssues(markPath, readTextEffectMarkAttrs(mark), issues);
357
363
  return;
@@ -5,6 +5,7 @@ import { formatParseWarnings } from "../docx/parseWarningMessage.js";
5
5
  import { DEFAULT_TAB_STOP_TWIPS as DEFAULT_TAB_STOP_TWIPS$1 } from "../docx/settingsParser.js";
6
6
  import { STYLE_SET_PART, normalizeDocumentStyleSet } from "../style-sets/styleSetNormalization.js";
7
7
  import "../style-sets/types.js";
8
+ import { generateHexId } from "./hexId.js";
8
9
  import { panic } from "better-result";
9
10
  import { DOCX_CONFORMANCE_CLASSES } from "@stll/docx-core/model";
10
11
  // src/utils/createDocument.ts
@@ -368,17 +369,19 @@ function createEmptyDocument(options = {}) {
368
369
  if (options.marginLeft !== void 0) sectionProps.marginLeft = Math.round(options.marginLeft);
369
370
  if (options.marginRight !== void 0) sectionProps.marginRight = Math.round(options.marginRight);
370
371
  const initialText = options.initialText ?? "";
372
+ const run = {
373
+ type: "run",
374
+ content: initialText ? [{
375
+ type: "text",
376
+ text: initialText
377
+ }] : [],
378
+ formatting: {}
379
+ };
371
380
  const documentBody = {
372
381
  content: [{
373
382
  type: "paragraph",
374
- content: [{
375
- type: "run",
376
- content: initialText ? [{
377
- type: "text",
378
- text: initialText
379
- }] : [],
380
- formatting: {}
381
- }],
383
+ paraId: generateHexId(),
384
+ content: [run],
382
385
  formatting: { styleId: styleSet?.initialParagraphStyleId ?? "Normal" }
383
386
  }],
384
387
  finalSectionProperties: sectionProps
@@ -2,7 +2,7 @@
2
2
  * Font Loader
3
3
  *
4
4
  * Manages font availability for the DOCX editor. Fonts are bundled via
5
- * fontsource packages (WOFF2, no external requests). This module
5
+ * `@stll/folio-fonts` (WOFF2, no external requests). This module
6
6
  * tracks which fonts are loaded and provides callbacks for re-rendering
7
7
  * when fonts become available.
8
8
  */