@stll/folio-core 0.53.0 → 0.54.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/dist/ai-edits/apply.js +390 -166
  2. package/dist/ai-edits/batch-claims.d.ts +21 -6
  3. package/dist/ai-edits/batch-claims.js +47 -16
  4. package/dist/ai-edits/headless.d.ts +20 -0
  5. package/dist/ai-edits/headless.js +140 -11
  6. package/dist/ai-edits/pending-suggestions.js +1 -1
  7. package/dist/ai-edits/read.d.ts +5 -3
  8. package/dist/ai-edits/read.js +97 -47
  9. package/dist/ai-edits/revisionStretches.d.ts +17 -0
  10. package/dist/ai-edits/revisionStretches.js +80 -0
  11. package/dist/ai-edits/snapshot.js +3 -2
  12. package/dist/ai-edits/table-cell-mutations.d.ts +3 -1
  13. package/dist/ai-edits/table-cell-mutations.js +2 -1
  14. package/dist/ai-edits/table-row-column-mutations.d.ts +6 -1
  15. package/dist/ai-edits/table-row-column-mutations.js +27 -10
  16. package/dist/compare/content-alignment.d.ts +5 -1
  17. package/dist/compare/content-alignment.js +39 -32
  18. package/dist/compare/inline-provenance.js +1 -1
  19. package/dist/controller/layoutPipeline.js +3 -2
  20. package/dist/docx/archiveInflation.d.ts +127 -0
  21. package/dist/docx/archiveInflation.js +181 -0
  22. package/dist/docx/metadataPrivacy.js +24 -1
  23. package/dist/docx/noteReferenceMark.d.ts +15 -0
  24. package/dist/docx/noteReferenceMark.js +48 -0
  25. package/dist/docx/paragraphParser.js +5 -3
  26. package/dist/docx/paragraphPropertySource.d.ts +1 -1
  27. package/dist/docx/paragraphPropertySource.js +3 -1
  28. package/dist/docx/parser.js +2 -2
  29. package/dist/docx/rezip.js +4 -2
  30. package/dist/docx/selectiveXmlPatch.d.ts +3 -1
  31. package/dist/docx/selectiveXmlPatch.js +81 -3
  32. package/dist/docx/server/boundedArchive.d.ts +8 -13
  33. package/dist/docx/server/boundedArchive.js +70 -59
  34. package/dist/docx/server/extractDocxText.d.ts +8 -2
  35. package/dist/docx/server/extractDocxText.js +2 -2
  36. package/dist/docx/server/validateDocxConformance.d.ts +1 -1
  37. package/dist/docx/server/validateDocxConformance.js +1 -0
  38. package/dist/docx/unzip.d.ts +19 -2
  39. package/dist/docx/unzip.js +135 -24
  40. package/dist/fonts/embeddedFonts.js +1 -1
  41. package/dist/i18n/messages/catalogs.gen.d.ts +34 -34
  42. package/dist/i18n/messages/catalogs.gen.js +34 -34
  43. package/dist/i18n/messages/messages.gen.d.ts +2 -2
  44. package/dist/internal/wholeStoryRevisionResolution.js +95 -25
  45. package/dist/layout-engine/measure/measureParagraph.js +2 -2
  46. package/dist/layout-engine/measure/paragraphMeasureShared.d.ts +12 -1
  47. package/dist/layout-engine/measure/paragraphMeasureShared.js +14 -1
  48. package/dist/markdown/escape.d.ts +2 -2
  49. package/dist/markdown/escape.js +4 -5
  50. package/dist/markdown/renderBlock.js +91 -25
  51. package/dist/markdown/renderRuns.d.ts +10 -1
  52. package/dist/markdown/renderRuns.js +320 -59
  53. package/dist/prosemirror/clearRunColor.d.ts +6 -0
  54. package/dist/prosemirror/clearRunColor.js +46 -0
  55. package/dist/prosemirror/commands/comments.js +56 -21
  56. package/dist/prosemirror/commands/formatPainter.js +1 -1
  57. package/dist/prosemirror/commands/hyperlink.js +2 -1
  58. package/dist/prosemirror/commands/propertyChangeScope.d.ts +3 -2
  59. package/dist/prosemirror/commands/propertyChangeScope.js +24 -3
  60. package/dist/prosemirror/commands/resolveAllTableChanges.js +46 -41
  61. package/dist/prosemirror/commands/resolveParagraphProperties.js +20 -6
  62. package/dist/prosemirror/commands/tableCellMergeResolution.d.ts +8 -1
  63. package/dist/prosemirror/commands/tableCellMergeResolution.js +23 -7
  64. package/dist/prosemirror/containerFinalParagraph.d.ts +11 -5
  65. package/dist/prosemirror/containerFinalParagraph.js +7 -6
  66. package/dist/prosemirror/conversion/fromProseDoc.d.ts +2 -13
  67. package/dist/prosemirror/conversion/fromProseDoc.js +12 -590
  68. package/dist/prosemirror/conversion/toProseDoc.d.ts +4 -11
  69. package/dist/prosemirror/conversion/toProseDoc.js +31 -150
  70. package/dist/prosemirror/documentSchema.d.ts +8 -0
  71. package/dist/prosemirror/documentSchema.js +18 -0
  72. package/dist/prosemirror/extensions/StarterKit.js +2 -0
  73. package/dist/prosemirror/extensions/core/ParagraphExtension.js +104 -63
  74. package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +1 -1
  75. package/dist/prosemirror/extensions/features/EmptyParagraphFormatExtension.js +24 -3
  76. package/dist/prosemirror/extensions/features/JoinedRunStyleExtension.d.ts +20 -0
  77. package/dist/prosemirror/extensions/features/JoinedRunStyleExtension.js +43 -0
  78. package/dist/prosemirror/extensions/features/ListExtension.js +10 -9
  79. package/dist/prosemirror/extensions/features/pastedHeadingStyles.js +1 -1
  80. package/dist/prosemirror/extensions/features/pastedHtmlLists.js +2 -4
  81. package/dist/prosemirror/extensions/marks/FootnoteRefExtension.js +14 -8
  82. package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +8 -4
  83. package/dist/prosemirror/extensions/marks/StrikeExtension.js +2 -2
  84. package/dist/prosemirror/extensions/marks/SubscriptExtension.js +2 -2
  85. package/dist/prosemirror/extensions/marks/SuperscriptExtension.js +2 -2
  86. package/dist/prosemirror/extensions/marks/TextColorExtension.js +4 -3
  87. package/dist/prosemirror/extensions/marks/markUtils.d.ts +8 -6
  88. package/dist/prosemirror/extensions/marks/markUtils.js +17 -8
  89. package/dist/prosemirror/extensions/marks/noteReferenceDeletion.d.ts +3 -1
  90. package/dist/prosemirror/extensions/marks/noteReferenceDeletion.js +12 -1
  91. package/dist/prosemirror/extensions/nodes/CommentReferenceExtension.js +2 -2
  92. package/dist/prosemirror/extensions/nodes/HardBreakExtension.js +5 -3
  93. package/dist/prosemirror/extensions/nodes/TableExtension.js +104 -210
  94. package/dist/prosemirror/extensions/types.d.ts +7 -0
  95. package/dist/prosemirror/hyperlinkRemoval.d.ts +9 -0
  96. package/dist/prosemirror/hyperlinkRemoval.js +129 -0
  97. package/dist/prosemirror/index.d.ts +2 -1
  98. package/dist/prosemirror/index.js +2 -1
  99. package/dist/prosemirror/listNumbering.d.ts +3 -21
  100. package/dist/prosemirror/listNumbering.js +10 -25
  101. package/dist/prosemirror/listRendering.d.ts +27 -0
  102. package/dist/prosemirror/listRendering.js +27 -0
  103. package/dist/prosemirror/markupViewNotes.d.ts +8 -0
  104. package/dist/prosemirror/markupViewNotes.js +49 -0
  105. package/dist/prosemirror/markupViewProjection.d.ts +3 -1
  106. package/dist/prosemirror/markupViewProjection.js +7 -2
  107. package/dist/prosemirror/noteReferenceReview.d.ts +77 -0
  108. package/dist/prosemirror/noteReferenceReview.js +332 -0
  109. package/dist/prosemirror/paragraphIndentation.js +5 -2
  110. package/dist/prosemirror/paragraphMarkJoin.d.ts +6 -4
  111. package/dist/prosemirror/paragraphMarkJoin.js +21 -19
  112. package/dist/prosemirror/paragraphPropertyCarry.d.ts +64 -0
  113. package/dist/prosemirror/paragraphPropertyCarry.js +158 -0
  114. package/dist/prosemirror/plugins/documentStyleState.d.ts +26 -0
  115. package/dist/prosemirror/plugins/documentStyleState.js +32 -0
  116. package/dist/prosemirror/plugins/documentStyles.d.ts +2 -21
  117. package/dist/prosemirror/plugins/documentStyles.js +66 -29
  118. package/dist/prosemirror/plugins/index.d.ts +2 -1
  119. package/dist/prosemirror/plugins/index.js +2 -1
  120. package/dist/prosemirror/plugins/paragraphStyleResolution.d.ts +15 -0
  121. package/dist/prosemirror/plugins/paragraphStyleResolution.js +230 -0
  122. package/dist/prosemirror/plugins/suggestionMode.d.ts +12 -2
  123. package/dist/prosemirror/plugins/suggestionMode.js +391 -28
  124. package/dist/prosemirror/rebaseParagraphRunFormatting.d.ts +22 -2
  125. package/dist/prosemirror/rebaseParagraphRunFormatting.js +39 -13
  126. package/dist/prosemirror/rebaseParagraphRuns.d.ts +36 -0
  127. package/dist/prosemirror/rebaseParagraphRuns.js +99 -0
  128. package/dist/prosemirror/runFormattingFromMarks.d.ts +18 -0
  129. package/dist/prosemirror/runFormattingFromMarks.js +567 -0
  130. package/dist/prosemirror/runFormattingReconciliation.js +10 -9
  131. package/dist/prosemirror/schema/nodes.d.ts +2 -0
  132. package/dist/prosemirror/styles/paragraphStyleCascade.d.ts +59 -0
  133. package/dist/prosemirror/styles/paragraphStyleCascade.js +145 -0
  134. package/dist/prosemirror/styles/resolvedStyleAttrs.d.ts +21 -2
  135. package/dist/prosemirror/styles/resolvedStyleAttrs.js +38 -1
  136. package/dist/prosemirror/styles/tableStyleRegions.d.ts +31 -0
  137. package/dist/prosemirror/styles/tableStyleRegions.js +55 -0
  138. package/dist/prosemirror/tableCellPaste.d.ts +74 -0
  139. package/dist/prosemirror/tableCellPaste.js +525 -0
  140. package/dist/prosemirror/tableGridMutation.d.ts +53 -14
  141. package/dist/prosemirror/tableGridMutation.js +166 -18
  142. package/dist/prosemirror/tableRunIn.d.ts +51 -0
  143. package/dist/prosemirror/tableRunIn.js +165 -0
  144. package/dist/prosemirror/textInput.js +11 -2
  145. package/dist/prosemirror/trackedRevisionPath.d.ts +17 -0
  146. package/dist/prosemirror/trackedRevisionPath.js +32 -0
  147. package/dist/server.d.ts +2 -2
  148. package/package.json +5 -5
@@ -2,6 +2,26 @@ import { createExtension } from "../create.js";
2
2
  import { textFormattingToMarks } from "../marks/markUtils.js";
3
3
  import { Plugin, PluginKey } from "prosemirror-state";
4
4
  //#region src/prosemirror/extensions/features/EmptyParagraphFormatExtension.ts
5
+ /**
6
+ * EmptyParagraphFormat — re-establishes stored marks for an empty styled
7
+ * paragraph so typed text inherits the style's run formatting (bold, color,
8
+ * size, font).
9
+ *
10
+ * Why this exists:
11
+ * An empty paragraph carries its run defaults in the `defaultTextFormatting`
12
+ * attr (set at load by `toProseDoc`, and when a style is applied via
13
+ * `applyStyle`). The visible painter only derives font/size from that attr,
14
+ * so an empty heading renders a correctly sized caret — but typed text only
15
+ * becomes bold/colored if it carries real marks. Those marks live in
16
+ * `storedMarks`, which ProseMirror clears on the next selection change
17
+ * (e.g. when the style picker dropdown returns focus to the editor).
18
+ *
19
+ * This plugin watches for the caret sitting in an empty paragraph with no
20
+ * stored marks and re-derives them from `defaultTextFormatting`, but only
21
+ * when the defaults carry formatting the painter can't reproduce on its own
22
+ * (bold/italic/color/…). Plain body paragraphs (font + size only) are left
23
+ * untouched so ordinary typed text stays mark-free and serializes cleanly.
24
+ */
5
25
  const emptyParagraphFormatKey = new PluginKey("emptyParagraphFormat");
6
26
  /**
7
27
  * Run-formatting properties the painter does NOT reproduce from
@@ -15,7 +35,7 @@ const FONT_ONLY_DEFAULTS = /* @__PURE__ */ new Set(["fontFamily", "fontSize"]);
15
35
  function hasNonFontDefaults(dtf) {
16
36
  return Object.entries(dtf).some(([property, value]) => value !== void 0 && !FONT_ONLY_DEFAULTS.has(property));
17
37
  }
18
- function createEmptyParagraphFormatPlugin(schema) {
38
+ function createEmptyParagraphFormatPlugin() {
19
39
  return new Plugin({
20
40
  key: emptyParagraphFormatKey,
21
41
  appendTransaction(transactions, _oldState, newState) {
@@ -27,6 +47,7 @@ function createEmptyParagraphFormatPlugin(schema) {
27
47
  if (para.type.name !== "paragraph" || para.content.size !== 0) return null;
28
48
  const dtf = para.attrs["defaultTextFormatting"];
29
49
  if (!dtf || !hasNonFontDefaults(dtf)) return null;
50
+ const { schema } = newState;
30
51
  const marks = textFormattingToMarks(dtf, schema);
31
52
  if (dtf.styleId) marks.push(schema.mark("characterStyle", { styleId: dtf.styleId }));
32
53
  if (marks.length === 0) return null;
@@ -38,8 +59,8 @@ function createEmptyParagraphFormatPlugin(schema) {
38
59
  }
39
60
  const EmptyParagraphFormatExtension = createExtension({
40
61
  name: "emptyParagraphFormat",
41
- onSchemaReady(ctx) {
42
- return { plugins: [createEmptyParagraphFormatPlugin(ctx.schema)] };
62
+ onSchemaReady() {
63
+ return { plugins: [createEmptyParagraphFormatPlugin()] };
43
64
  }
44
65
  });
45
66
  //#endregion
@@ -0,0 +1,20 @@
1
+ import { Extension } from "../types.js";
2
+ //#region src/prosemirror/extensions/features/JoinedRunStyleExtension.d.ts
3
+ /**
4
+ * Runs that a join moves into another paragraph take that paragraph's style.
5
+ *
6
+ * Deleting across a paragraph break (Backspace or Delete at the break, a
7
+ * deletion or typing over a selection that spans paragraphs) runs the later
8
+ * paragraph's remaining text on into the earlier one. A run's direct
9
+ * formatting is its own and stays; what the later paragraph's style lent it
10
+ * does not travel, and it reads in the style of the paragraph it now belongs
11
+ * to. The document styles plugin re-reads those runs after an ordinary edit.
12
+ *
13
+ * A transaction that re-reads the runs it moves itself (resolving a tracked
14
+ * change, an operation that merges paragraphs) says so with
15
+ * {@link JOINED_RUNS_RESTYLED_META}, and is left as it is.
16
+ */
17
+ declare const JOINED_RUNS_RESTYLED_META = "joinedRunsRestyled";
18
+ declare const JoinedRunStyleExtension: (options?: Partial<{}> | undefined) => Extension;
19
+ //#endregion
20
+ export { JOINED_RUNS_RESTYLED_META, JoinedRunStyleExtension };
@@ -0,0 +1,43 @@
1
+ import { createExtension } from "../create.js";
2
+ import { Plugin, PluginKey } from "prosemirror-state";
3
+ //#region src/prosemirror/extensions/features/JoinedRunStyleExtension.ts
4
+ /**
5
+ * Runs that a join moves into another paragraph take that paragraph's style.
6
+ *
7
+ * Deleting across a paragraph break (Backspace or Delete at the break, a
8
+ * deletion or typing over a selection that spans paragraphs) runs the later
9
+ * paragraph's remaining text on into the earlier one. A run's direct
10
+ * formatting is its own and stays; what the later paragraph's style lent it
11
+ * does not travel, and it reads in the style of the paragraph it now belongs
12
+ * to. The document styles plugin re-reads those runs after an ordinary edit.
13
+ *
14
+ * A transaction that re-reads the runs it moves itself (resolving a tracked
15
+ * change, an operation that merges paragraphs) says so with
16
+ * {@link JOINED_RUNS_RESTYLED_META}, and is left as it is.
17
+ */
18
+ const JOINED_RUNS_RESTYLED_META = "joinedRunsRestyled";
19
+ const pastedTableEndKey = new PluginKey("pastedTableEnd");
20
+ /**
21
+ * A table pasted over a story's last paragraph leaves that paragraph, emptied,
22
+ * after it: a story ends with a paragraph, and the paste replaced the words,
23
+ * not the break that ends the story. ProseMirror's paste replaces the whole
24
+ * paragraph, so the empty one is put back.
25
+ */
26
+ const createPastedTableEndPlugin = () => new Plugin({
27
+ key: pastedTableEndKey,
28
+ appendTransaction(transactions, oldState, newState) {
29
+ if (!transactions.some((transaction) => transaction.docChanged && transaction.getMeta("uiEvent") === "paste")) return null;
30
+ if (newState.doc.lastChild?.type.spec["tableRole"] !== "table" || oldState.doc.lastChild?.type.name !== "paragraph") return null;
31
+ const paragraph = newState.schema.nodes["paragraph"]?.createAndFill();
32
+ return paragraph ? newState.tr.insert(newState.doc.content.size, paragraph) : null;
33
+ }
34
+ });
35
+ const JoinedRunStyleExtension = createExtension({
36
+ name: "joinedRunStyle",
37
+ defaultOptions: {},
38
+ onSchemaReady() {
39
+ return { plugins: [createPastedTableEndPlugin()] };
40
+ }
41
+ });
42
+ //#endregion
43
+ export { JOINED_RUNS_RESTYLED_META, JoinedRunStyleExtension };
@@ -7,7 +7,7 @@ import { resolveListState } from "../../listState.js";
7
7
  import { removedNumberingAttr } from "../../numberingAttr.js";
8
8
  import { getDocumentNumbering } from "../../plugins/documentNumbering.js";
9
9
  import { SUGGESTION_META, makeRevisionInfo, suggestRangeDeletion } from "../../plugins/suggestionMode.js";
10
- import { listLevelAttrPatch } from "../../styles/resolvedStyleAttrs.js";
10
+ import { listLevelAttrPatch, listLevelIndentRemovalPatch } from "../../styles/resolvedStyleAttrs.js";
11
11
  import { createExtension } from "../create.js";
12
12
  import { goToNextCell, goToPrevCell } from "../nodes/TableExtension.js";
13
13
  import { Priority } from "../types.js";
@@ -27,9 +27,10 @@ function chainCommands(...commands) {
27
27
  return false;
28
28
  };
29
29
  }
30
- function clearListAttrs(attrs) {
30
+ function clearListAttrs(attrs, numbering) {
31
31
  return {
32
32
  ...attrs,
33
+ ...listLevelIndentRemovalPatch(attrs, numbering),
33
34
  numPr: removedNumberingAttr(attrs.numPrFromStyle),
34
35
  ...CLEARED_LIST_RENDERING_ATTRS
35
36
  };
@@ -69,7 +70,7 @@ const numberParagraphs = ({ state, tr, from, to, requests }) => {
69
70
  updates: paragraphsBetween(tr.doc, from, to).map(({ pos, node }) => ({
70
71
  pos,
71
72
  node,
72
- next: listItemAttrs(node.attrs, {
73
+ next: listItemAttrs(expectParagraphAttrs(node), {
73
74
  numId: target.numId,
74
75
  ilvl: paragraphNumberingLevel(expectParagraphAttrs(node).numPr) ?? target.ilvl
75
76
  }, target.numbering)
@@ -92,7 +93,7 @@ function toggleList(intent) {
92
93
  updates: paragraphsBetween(state.doc, $from.pos, $to.pos).filter(({ node }) => isItemOfKind(node)).map(({ pos, node }) => ({
93
94
  pos,
94
95
  node,
95
- next: clearListAttrs(expectParagraphAttrs(node))
96
+ next: clearListAttrs(expectParagraphAttrs(node), getDocumentNumbering(state))
96
97
  }))
97
98
  });
98
99
  else numberParagraphs({
@@ -141,7 +142,7 @@ const decreaseListLevel = (state, dispatch) => {
141
142
  if (!dispatch) return true;
142
143
  const paragraphPos = $from.before($from.depth);
143
144
  if (currentLevel <= 0) dispatch(state.tr.setNodeMarkup(paragraphPos, void 0, {
144
- ...clearListAttrs(attrs),
145
+ ...clearListAttrs(attrs, getDocumentNumbering(state)),
145
146
  indentLeft: null,
146
147
  indentFirstLine: null,
147
148
  hangingIndent: null
@@ -157,7 +158,7 @@ const removeList = (state, dispatch) => {
157
158
  state.doc.nodesBetween($from.pos, $to.pos, (node, pos) => {
158
159
  if (node.type.name === "paragraph" && hasActiveListNumbering(expectParagraphAttrs(node)) && !seen.has(pos)) {
159
160
  seen.add(pos);
160
- tr = tr.setNodeMarkup(pos, void 0, clearListAttrs(expectParagraphAttrs(node)));
161
+ tr = tr.setNodeMarkup(pos, void 0, clearListAttrs(expectParagraphAttrs(node), getDocumentNumbering(state)));
161
162
  }
162
163
  });
163
164
  dispatch(tr.scrollIntoView());
@@ -197,7 +198,7 @@ function exitListOnEmptyEnter() {
197
198
  updates: [{
198
199
  pos: $from.before(),
199
200
  node: paragraph,
200
- next: clearListAttrs(attrs)
201
+ next: clearListAttrs(attrs, getDocumentNumbering(state))
201
202
  }]
202
203
  });
203
204
  dispatch(tr);
@@ -250,7 +251,7 @@ function backspaceExitList() {
250
251
  updates: [{
251
252
  pos: $from.before(),
252
253
  node: paragraph,
253
- next: clearListAttrs(attrs)
254
+ next: clearListAttrs(attrs, getDocumentNumbering(state))
254
255
  }]
255
256
  });
256
257
  dispatch(tr);
@@ -300,7 +301,7 @@ function decreaseListIndent() {
300
301
  for (const { pos, attrs } of positions) {
301
302
  const currentLevel = attrs.numPr.ilvl ?? 0;
302
303
  if (currentLevel <= 0) tr = tr.setNodeMarkup(pos, void 0, {
303
- ...clearListAttrs(attrs),
304
+ ...clearListAttrs(attrs, getDocumentNumbering(state)),
304
305
  indentLeft: null,
305
306
  indentFirstLine: null,
306
307
  hangingIndent: null
@@ -1,5 +1,5 @@
1
1
  import { readOutlineLevelAttr } from "../../outlineLevelAttr.js";
2
- import { getDocumentBuiltInStyles, getDocumentStyleResolver } from "../../plugins/documentStyles.js";
2
+ import { getDocumentBuiltInStyles, getDocumentStyleResolver } from "../../plugins/documentStyleState.js";
3
3
  import { headingLevelOf } from "@stll/docx-core/model";
4
4
  import { Fragment, Slice } from "prosemirror-model";
5
5
  //#region src/prosemirror/extensions/features/pastedHeadingStyles.ts
@@ -103,10 +103,8 @@ const numberPastedHtmlLists = (slice, view) => {
103
103
  groups.set(hint.group, group);
104
104
  }
105
105
  if (!numbering) panic("Pasted list group has no numbering definition");
106
- result = node.type.create(listItemAttrs({
107
- ...attrs,
108
- _pastedHtmlList: void 0
109
- }, {
106
+ const { _pastedHtmlList: _consumed, ...unhinted } = attrs;
107
+ result = node.type.create(listItemAttrs(unhinted, {
110
108
  numId: group.numId,
111
109
  ilvl: hint.level
112
110
  }, numbering), node.content, node.marks);
@@ -1,7 +1,7 @@
1
1
  import { expectFootnoteRefMarkAttrs } from "../../attrs/index.js";
2
- import { suggestionModeKey } from "../../plugins/suggestionMode.js";
2
+ import { suggestRangeDeletion, suggestionModeKey } from "../../plugins/suggestionMode.js";
3
3
  import { createMarkExtension } from "../create.js";
4
- import { expandNoteReferenceDeletionRange } from "./noteReferenceDeletion.js";
4
+ import { expandNoteReferenceDeletionRange, noteReferenceRanges } from "./noteReferenceDeletion.js";
5
5
  import { panic } from "better-result";
6
6
  //#region src/prosemirror/extensions/marks/FootnoteRefExtension.ts
7
7
  /**
@@ -59,8 +59,7 @@ const FootnoteRefExtension = createMarkExtension({
59
59
  ];
60
60
  }
61
61
  },
62
- onSchemaReady(ctx) {
63
- const { schema } = ctx;
62
+ onSchemaReady() {
64
63
  const deleteWholeNoteReference = (direction) => (state, dispatch) => {
65
64
  if (suggestionModeKey.getState(state)?.active) return false;
66
65
  const { from, to, empty } = state.selection;
@@ -72,6 +71,7 @@ const FootnoteRefExtension = createMarkExtension({
72
71
  function makeInsertNote(noteType) {
73
72
  return (id) => (state, dispatch) => {
74
73
  if (!dispatch) return true;
74
+ const { schema } = state;
75
75
  const footnoteRefType = schema.marks["footnoteRef"];
76
76
  if (!footnoteRefType) panic("Missing mark type: footnoteRef");
77
77
  const mark = footnoteRefType.create({
@@ -84,12 +84,18 @@ const FootnoteRefExtension = createMarkExtension({
84
84
  return true;
85
85
  };
86
86
  }
87
+ /**
88
+ * Delete the note references the selection touches (the one beside a
89
+ * caret), each whole: tracked as a deletion of the reference run when
90
+ * suggesting, removed outright otherwise.
91
+ */
87
92
  const deleteNoteRef = (state, dispatch) => {
88
- const { $from, $to } = state.selection;
93
+ const { from, to, empty } = state.selection;
94
+ const references = noteReferenceRanges(state.doc, empty ? Math.max(0, from - 1) : from, empty ? Math.min(state.doc.content.size, to + 1) : to);
95
+ if (references.length === 0) return false;
89
96
  if (!dispatch) return true;
90
- let tr = state.tr;
91
- const markType = schema.marks["footnoteRef"];
92
- tr = tr.removeMark($from.pos, $to.pos, markType);
97
+ const tr = state.tr;
98
+ for (const reference of references.toReversed()) if (!suggestRangeDeletion(state, tr, reference.from, reference.to)) tr.delete(reference.from, reference.to);
93
99
  dispatch(tr.scrollIntoView());
94
100
  return true;
95
101
  };
@@ -1,5 +1,6 @@
1
1
  import { anchorTargetAttrs, normalizeUserUrl, sanitizeExternalUrl } from "../../../utils/urlSecurity.js";
2
2
  import { expectHyperlinkMarkAttrs } from "../../attrs/index.js";
3
+ import { removeHyperlinkInRange } from "../../hyperlinkRemoval.js";
3
4
  import { createMarkExtension } from "../create.js";
4
5
  import { isMarkActive } from "./markUtils.js";
5
6
  import { panic } from "better-result";
@@ -118,9 +119,10 @@ const HyperlinkExtension = createMarkExtension({
118
119
  }
119
120
  },
120
121
  onSchemaReady(ctx) {
121
- const hlType = ctx.schema.marks["hyperlink"];
122
- if (!hlType) panic("Missing mark type: hyperlink");
122
+ if (!ctx.schema.marks["hyperlink"]) panic("Missing mark type: hyperlink");
123
+ const documentHyperlinkType = (state) => state.schema.marks["hyperlink"] ?? panic("Missing mark type: hyperlink");
123
124
  const setHyperlink = (href, tooltip) => (state, dispatch) => {
125
+ const hlType = documentHyperlinkType(state);
124
126
  const { from, to, empty } = state.selection;
125
127
  if (empty) return false;
126
128
  if (dispatch) {
@@ -136,6 +138,7 @@ const HyperlinkExtension = createMarkExtension({
136
138
  return true;
137
139
  };
138
140
  const removeHyperlink = (state, dispatch) => {
141
+ const hlType = documentHyperlinkType(state);
139
142
  const { from, to, empty } = state.selection;
140
143
  if (empty) {
141
144
  const $pos = state.selection.$from;
@@ -154,13 +157,14 @@ const HyperlinkExtension = createMarkExtension({
154
157
  }
155
158
  }
156
159
  });
157
- if (dispatch) dispatch(state.tr.removeMark(start, end, hlType).scrollIntoView());
160
+ if (dispatch) dispatch(removeHyperlinkInRange(state, state.tr, start, end).scrollIntoView());
158
161
  return true;
159
162
  }
160
- if (dispatch) dispatch(state.tr.removeMark(from, to, hlType).scrollIntoView());
163
+ if (dispatch) dispatch(removeHyperlinkInRange(state, state.tr, from, to).scrollIntoView());
161
164
  return true;
162
165
  };
163
166
  const insertHyperlink = (text, href, tooltip) => (state, dispatch) => {
167
+ const hlType = documentHyperlinkType(state);
164
168
  if (dispatch) {
165
169
  const mark = hlType.create({
166
170
  href: normalizeHyperlinkInput(href),
@@ -1,6 +1,6 @@
1
1
  import { createMarkExtension } from "../create.js";
2
+ import { toggleDocumentMark } from "./markUtils.js";
2
3
  import { panic } from "better-result";
3
- import { toggleMark } from "prosemirror-commands";
4
4
  //#region src/prosemirror/extensions/marks/StrikeExtension.ts
5
5
  /**
6
6
  * Strikethrough Mark Extension
@@ -26,7 +26,7 @@ const StrikeExtension = createMarkExtension({
26
26
  onSchemaReady(ctx) {
27
27
  const strikeType = ctx.schema.marks["strike"];
28
28
  if (!strikeType) panic("Missing mark type: strike");
29
- return { commands: { toggleStrike: () => toggleMark(strikeType) } };
29
+ return { commands: { toggleStrike: () => toggleDocumentMark(strikeType) } };
30
30
  }
31
31
  });
32
32
  //#endregion
@@ -1,6 +1,6 @@
1
1
  import { createMarkExtension } from "../create.js";
2
+ import { toggleDocumentMark } from "./markUtils.js";
2
3
  import { panic } from "better-result";
3
- import { toggleMark } from "prosemirror-commands";
4
4
  //#region src/prosemirror/extensions/marks/SubscriptExtension.ts
5
5
  /**
6
6
  * Subscript Mark Extension
@@ -18,7 +18,7 @@ const SubscriptExtension = createMarkExtension({
18
18
  onSchemaReady(ctx) {
19
19
  const subscriptType = ctx.schema.marks["subscript"];
20
20
  if (!subscriptType) panic("Missing mark type: subscript");
21
- return { commands: { toggleSubscript: () => toggleMark(subscriptType) } };
21
+ return { commands: { toggleSubscript: () => toggleDocumentMark(subscriptType) } };
22
22
  }
23
23
  });
24
24
  //#endregion
@@ -1,6 +1,6 @@
1
1
  import { createMarkExtension } from "../create.js";
2
+ import { toggleDocumentMark } from "./markUtils.js";
2
3
  import { panic } from "better-result";
3
- import { toggleMark } from "prosemirror-commands";
4
4
  //#region src/prosemirror/extensions/marks/SuperscriptExtension.ts
5
5
  /**
6
6
  * Superscript Mark Extension
@@ -18,7 +18,7 @@ const SuperscriptExtension = createMarkExtension({
18
18
  onSchemaReady(ctx) {
19
19
  const superscriptType = ctx.schema.marks["superscript"];
20
20
  if (!superscriptType) panic("Missing mark type: superscript");
21
- return { commands: { toggleSuperscript: () => toggleMark(superscriptType) } };
21
+ return { commands: { toggleSuperscript: () => toggleDocumentMark(superscriptType) } };
22
22
  }
23
23
  });
24
24
  //#endregion
@@ -1,7 +1,8 @@
1
1
  import { textToStyle } from "../../../utils/formatToStyle.js";
2
2
  import { expectTextColorMarkAttrs } from "../../attrs/index.js";
3
+ import { clearRunColor } from "../../clearRunColor.js";
3
4
  import { createMarkExtension } from "../create.js";
4
- import { removeMark, setMark } from "./markUtils.js";
5
+ import { setMark } from "./markUtils.js";
5
6
  import { panic } from "better-result";
6
7
  //#region src/prosemirror/extensions/marks/TextColorExtension.ts
7
8
  /**
@@ -39,10 +40,10 @@ const TextColorExtension = createMarkExtension({
39
40
  if (!textColorType) panic("Missing mark type: textColor");
40
41
  return { commands: {
41
42
  setTextColor: (attrs) => {
42
- if (!attrs.rgb && !attrs.themeColor) return removeMark(textColorType);
43
+ if (!attrs.rgb && !attrs.themeColor) return clearRunColor();
43
44
  return setMark(textColorType, attrs);
44
45
  },
45
- clearTextColor: () => removeMark(textColorType)
46
+ clearTextColor: () => clearRunColor()
46
47
  } };
47
48
  }
48
49
  });
@@ -4,24 +4,26 @@ import { Command, EditorState } from "prosemirror-state";
4
4
  //#region src/prosemirror/extensions/marks/markUtils.d.ts
5
5
  type MarkAttrs = Record<string, unknown>;
6
6
  declare function marksToTextFormatting(marks: readonly Mark[]): document_d_exports.TextFormatting;
7
- declare function setMark(markType: MarkType, attrs: MarkAttrs): Command;
7
+ declare function setMark(type: MarkType, attrs: MarkAttrs): Command;
8
8
  type PairedToggleProperty = "bold" | "italic";
9
+ /** `toggleMark` for the document's own counterpart of `type`. */
10
+ declare const toggleDocumentMark: (type: MarkType) => Command;
9
11
  /** UI toggle whose direct-formatting contract explicitly targets both script families. */
10
- declare const toggleMarkForAllScripts: (markType: MarkType, property: PairedToggleProperty) => Command;
12
+ declare const toggleMarkForAllScripts: (type: MarkType, property: PairedToggleProperty) => Command;
11
13
  /** UI size command whose direct-formatting contract explicitly targets both script families. */
12
14
  declare const setFontSizeForAllScripts: (markType: MarkType, size: number) => Command;
13
15
  /** Clears both ordinary and complex-script direct size through the UI command boundary. */
14
16
  declare const clearFontSizeForAllScripts: (markType: MarkType) => Command;
15
17
  declare function toggleUnderlineMark(markType: MarkType): Command;
16
- declare function removeMark(markType: MarkType): Command;
18
+ declare function removeMark(type: MarkType): Command;
17
19
  /**
18
20
  * Check if a mark is active in the current selection
19
21
  */
20
- declare function isMarkActive(state: EditorState, markType: MarkType, attrs?: Record<string, unknown>): boolean;
22
+ declare function isMarkActive(state: EditorState, type: MarkType, attrs?: Record<string, unknown>): boolean;
21
23
  /**
22
24
  * Get the current value of a mark attribute
23
25
  */
24
- declare function getMarkAttr(state: EditorState, markType: MarkType, attr: string): unknown;
26
+ declare function getMarkAttr(state: EditorState, type: MarkType, attr: string): unknown;
25
27
  type AuthoredRunFormattingCarrier = "preserve" | "reconstruct";
26
28
  /** Builds a mark by type name; a conversion passes one that shares equal marks. */
27
29
  type MarkFactory = (type: string, attrs?: Attrs | null) => Mark;
@@ -56,4 +58,4 @@ declare function createSetMarkCommand(markType: MarkType, attrs?: Record<string,
56
58
  */
57
59
  declare function createRemoveMarkCommand(markType: MarkType): Command;
58
60
  //#endregion
59
- export { AuthoredRunFormattingCarrier, MarkFactory, clearFontSizeForAllScripts, clearFormatting, createRemoveMarkCommand, createSetMarkCommand, getMarkAttr, isMarkActive, marksToTextFormatting, removeMark, setFontSizeForAllScripts, setMark, textFormattingToMarks, toggleMarkForAllScripts, toggleUnderlineMark };
61
+ export { AuthoredRunFormattingCarrier, MarkFactory, clearFontSizeForAllScripts, clearFormatting, createRemoveMarkCommand, createSetMarkCommand, getMarkAttr, isMarkActive, marksToTextFormatting, removeMark, setFontSizeForAllScripts, setMark, textFormattingToMarks, toggleDocumentMark, toggleMarkForAllScripts, toggleUnderlineMark };
@@ -3,6 +3,7 @@ import { mergeFontFamily } from "../../../utils/fontFamilyMerge.js";
3
3
  import { normalizeHorizontalScalePercent } from "../../../utils/horizontalScale.js";
4
4
  import { expectCharacterSpacingMarkAttrs, expectCharacterStyleMarkAttrs, expectEmphasisMarkAttrs, expectFontFamilyMarkAttrs, expectRunFormattingOverrideMarkAttrs, expectRunShadingMarkAttrs, expectStrikeMarkAttrs, expectTextEffectMarkAttrs } from "../../attrs/index.js";
5
5
  import { runShadingAttrsToShading, shadingToRunShadingAttrs } from "../../conversion/runShadingMark.js";
6
+ import { documentMarkType } from "../../documentSchema.js";
6
7
  import { selectRunFormattingCarrierRepresentations } from "../../runFormattingInlineCarriers.js";
7
8
  import { RUN_FORMATTING_MARK_NAMES } from "../../runFormattingMarkNames.js";
8
9
  import { hasRunFormattingOverrideAttrs } from "../../runFormattingProvenance.js";
@@ -178,8 +179,9 @@ function createMarkWithMergedAttrs(markType, currentMark, nextAttrs) {
178
179
  if (!markRequiresAttrMerge(markType)) return markType.create(nextAttrs);
179
180
  return markType.create(mergeMarkAttrs(markType, currentMark, nextAttrs));
180
181
  }
181
- function setMark(markType, attrs) {
182
+ function setMark(type, attrs) {
182
183
  return (state, dispatch) => {
184
+ const markType = documentMarkType(state, type);
183
185
  const { from, to, empty } = state.selection;
184
186
  if (empty) {
185
187
  if (dispatch) {
@@ -201,7 +203,8 @@ function setMark(markType, attrs) {
201
203
  if (!node.isText) return;
202
204
  const start = Math.max(from, pos);
203
205
  const end = Math.min(to, pos + node.nodeSize);
204
- const mark = createMarkWithMergedAttrs(markType, markType.isInSet(node.marks), attrs);
206
+ const currentMark = markType.isInSet(node.marks);
207
+ const mark = createMarkWithMergedAttrs(markType, currentMark, attrs);
205
208
  tr = tr.addMark(start, end, mark);
206
209
  });
207
210
  dispatch(tr.scrollIntoView());
@@ -283,8 +286,11 @@ const updateRunFormattingOverride = (state, tr, update) => {
283
286
  const withRunFormattingOverride = (command, update) => (state, dispatch) => command(state, dispatch ? (tr) => {
284
287
  dispatch(updateRunFormattingOverride(state, tr, update));
285
288
  } : void 0);
289
+ /** `toggleMark` for the document's own counterpart of `type`. */
290
+ const toggleDocumentMark = (type) => (state, dispatch, view) => toggleMark(documentMarkType(state, type))(state, dispatch, view);
286
291
  /** UI toggle whose direct-formatting contract explicitly targets both script families. */
287
- const toggleMarkForAllScripts = (markType, property) => (state, dispatch) => {
292
+ const toggleMarkForAllScripts = (type, property) => (state, dispatch) => {
293
+ const markType = documentMarkType(state, type);
288
294
  const enabled = !isMarkActive(state, markType);
289
295
  return withRunFormattingOverride(toggleMark(markType), (attrs) => updatePairedToggleAttrs(attrs, property, enabled))(state, dispatch);
290
296
  };
@@ -311,10 +317,11 @@ function selectionHasVisibleUnderline(state, markType) {
311
317
  return hasVisibleUnderline;
312
318
  }
313
319
  function toggleUnderlineMark(markType) {
314
- return (state, dispatch) => setMark(markType, { style: selectionHasVisibleUnderline(state, markType) ? "none" : "single" })(state, dispatch);
320
+ return (state, dispatch) => setMark(markType, { style: selectionHasVisibleUnderline(state, documentMarkType(state, markType)) ? "none" : "single" })(state, dispatch);
315
321
  }
316
- function removeMark(markType) {
322
+ function removeMark(type) {
317
323
  return (state, dispatch) => {
324
+ const markType = documentMarkType(state, type);
318
325
  const { from, to, empty } = state.selection;
319
326
  if (empty) {
320
327
  if (dispatch) dispatchStoredMarks(state, dispatch, (state.storedMarks ?? state.selection.$from.marks()).filter((m) => m.type !== markType));
@@ -327,7 +334,8 @@ function removeMark(markType) {
327
334
  /**
328
335
  * Check if a mark is active in the current selection
329
336
  */
330
- function isMarkActive(state, markType, attrs) {
337
+ function isMarkActive(state, type, attrs) {
338
+ const markType = documentMarkType(state, type);
331
339
  const { from, to, empty } = state.selection;
332
340
  if (empty) return (state.storedMarks ?? state.selection.$from.marks()).some((mark) => {
333
341
  if (mark.type !== markType) return false;
@@ -356,7 +364,8 @@ function isMarkActive(state, markType, attrs) {
356
364
  /**
357
365
  * Get the current value of a mark attribute
358
366
  */
359
- function getMarkAttr(state, markType, attr) {
367
+ function getMarkAttr(state, type, attr) {
368
+ const markType = documentMarkType(state, type);
360
369
  const { empty, $from, from, to } = state.selection;
361
370
  if (empty) {
362
371
  const marks = state.storedMarks ?? $from.marks();
@@ -524,4 +533,4 @@ function createRemoveMarkCommand(markType) {
524
533
  return removeMark(markType);
525
534
  }
526
535
  //#endregion
527
- export { clearFontSizeForAllScripts, clearFormatting, createRemoveMarkCommand, createSetMarkCommand, getMarkAttr, isMarkActive, marksToTextFormatting, removeMark, setFontSizeForAllScripts, setMark, textFormattingToMarks, toggleMarkForAllScripts, toggleUnderlineMark };
536
+ export { clearFontSizeForAllScripts, clearFormatting, createRemoveMarkCommand, createSetMarkCommand, getMarkAttr, isMarkActive, marksToTextFormatting, removeMark, setFontSizeForAllScripts, setMark, textFormattingToMarks, toggleDocumentMark, toggleMarkForAllScripts, toggleUnderlineMark };
@@ -6,5 +6,7 @@ type DeletionRange = {
6
6
  };
7
7
  /** Keep the text carrying one OOXML note reference together during deletion. */
8
8
  declare const expandNoteReferenceDeletionRange: (doc: Node, from: number, to: number) => DeletionRange | null;
9
+ /** Each whole note reference [`from`, `to`) of `doc` touches, in document order. */
10
+ declare const noteReferenceRanges: (doc: Node, from: number, to: number) => DeletionRange[];
9
11
  //#endregion
10
- export { expandNoteReferenceDeletionRange };
12
+ export { expandNoteReferenceDeletionRange, noteReferenceRanges };
@@ -31,5 +31,16 @@ const expandNoteReferenceDeletionRange = (doc, from, to) => {
31
31
  to: expandedTo
32
32
  } : null;
33
33
  };
34
+ /** Each whole note reference [`from`, `to`) of `doc` touches, in document order. */
35
+ const noteReferenceRanges = (doc, from, to) => {
36
+ const ranges = [];
37
+ doc.nodesBetween(from, to, (node, pos) => {
38
+ if (!node.isText || !node.marks.some((mark) => mark.type.name === "footnoteRef")) return;
39
+ const whole = expandNoteReferenceDeletionRange(doc, pos, pos + node.nodeSize);
40
+ const last = ranges.at(-1);
41
+ if (whole && (!last || whole.from >= last.to)) ranges.push(whole);
42
+ });
43
+ return ranges;
44
+ };
34
45
  //#endregion
35
- export { expandNoteReferenceDeletionRange };
46
+ export { expandNoteReferenceDeletionRange, noteReferenceRanges };
@@ -80,9 +80,9 @@ const CommentReferenceExtension = createNodeExtension({
80
80
  }];
81
81
  }
82
82
  }),
83
- onSchemaReady: ({ schema }) => ({ plugins: [new Plugin({ appendTransaction(transactions, _oldState, newState) {
83
+ onSchemaReady: () => ({ plugins: [new Plugin({ appendTransaction(transactions, _oldState, newState) {
84
84
  if (!transactions.some(({ docChanged }) => docChanged)) return null;
85
- const referenceType = schema.nodes[COMMENT_REFERENCE_NODE_NAME];
85
+ const referenceType = newState.schema.nodes[COMMENT_REFERENCE_NODE_NAME];
86
86
  if (!referenceType) return null;
87
87
  const repairs = planCommentReferenceRepairs(surveyCommentReferences(newState.doc));
88
88
  if (repairs.length === 0) return null;
@@ -41,10 +41,12 @@ const HardBreakExtension = createNodeExtension({
41
41
  }
42
42
  },
43
43
  onSchemaReady(ctx) {
44
- const hardBreakType = ctx.schema.nodes["hardBreak"];
45
- if (!hardBreakType) panic("Missing node type: hardBreak");
44
+ if (!ctx.schema.nodes["hardBreak"]) panic("Missing node type: hardBreak");
46
45
  return { keyboardShortcuts: { "Shift-Enter": (state, dispatch) => {
47
- if (dispatch) dispatch(state.tr.replaceSelectionWith(hardBreakType.create()).scrollIntoView());
46
+ if (dispatch) {
47
+ const hardBreakType = state.schema.nodes["hardBreak"] ?? panic("Missing node type: hardBreak");
48
+ dispatch(state.tr.replaceSelectionWith(hardBreakType.create()).scrollIntoView());
49
+ }
48
50
  return true;
49
51
  } } };
50
52
  }