@stll/folio-core 0.55.0 → 0.56.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 (119) hide show
  1. package/dist/ai-edits/apply.js +169 -38
  2. package/dist/ai-edits/headless.js +2 -2
  3. package/dist/ai-edits/types.d.ts +7 -1
  4. package/dist/compare/compare.js +1 -0
  5. package/dist/compare/reproducible-package.d.ts +0 -17
  6. package/dist/compare/reproducible-package.js +12 -18
  7. package/dist/controller/canonicalClipboard.d.ts +23 -0
  8. package/dist/controller/canonicalClipboard.js +476 -0
  9. package/dist/controller/canonicalClipboardResources.d.ts +21 -0
  10. package/dist/controller/canonicalClipboardResources.js +382 -0
  11. package/dist/controller/canonicalComposition.d.ts +20 -3
  12. package/dist/controller/canonicalComposition.js +166 -38
  13. package/dist/controller/canonicalInlineProjection.d.ts +113 -0
  14. package/dist/controller/canonicalInlineProjection.js +393 -0
  15. package/dist/controller/canonicalInput.d.ts +13 -4
  16. package/dist/controller/canonicalInput.js +52 -13
  17. package/dist/controller/canonicalInputTimer.d.ts +7 -0
  18. package/dist/controller/canonicalInputTimer.js +11 -0
  19. package/dist/controller/canonicalPublicOperations.d.ts +48 -0
  20. package/dist/controller/canonicalPublicOperations.js +556 -0
  21. package/dist/controller/canonicalSession.d.ts +24 -6
  22. package/dist/controller/canonicalSession.js +259 -111
  23. package/dist/controller/canonicalStoryEditor.d.ts +5 -3
  24. package/dist/controller/canonicalStoryEditor.js +7 -2
  25. package/dist/controller/canonicalStructure.js +57 -4
  26. package/dist/controller/canonicalTocSelection.d.ts +11 -0
  27. package/dist/controller/canonicalTocSelection.js +43 -0
  28. package/dist/controller/folioEditor.d.ts +2 -9
  29. package/dist/controller/folioEditor.js +5 -4
  30. package/dist/controller/headerFooterEditorManager.d.ts +2 -1
  31. package/dist/controller/headerFooterEditorManager.js +8 -7
  32. package/dist/controller/hiddenEditorApi.d.ts +12 -2
  33. package/dist/controller/hiddenEditorApi.js +4 -0
  34. package/dist/controller/hiddenEditorManager.d.ts +3 -1
  35. package/dist/controller/hiddenEditorManager.js +216 -34
  36. package/dist/controller/noteEditorManager.d.ts +2 -1
  37. package/dist/controller/noteEditorManager.js +8 -7
  38. package/dist/document-operations.d.ts +8 -1
  39. package/dist/document-operations.js +6 -4
  40. package/dist/docx/blockPlainText.d.ts +1 -1
  41. package/dist/docx/blockPlainText.js +11 -3
  42. package/dist/docx/canonicalResourceSave.d.ts +16 -0
  43. package/dist/docx/canonicalResourceSave.js +27 -0
  44. package/dist/docx/canonicalSave.d.ts +29 -0
  45. package/dist/docx/canonicalSave.js +101 -0
  46. package/dist/docx/canonicalStoryRepack.d.ts +1 -1
  47. package/dist/docx/canonicalStoryRepack.js +38 -5
  48. package/dist/docx/documentParser.d.ts +1 -13
  49. package/dist/docx/documentParser.js +1 -47
  50. package/dist/docx/ensureParaIds.js +10 -4
  51. package/dist/docx/footnoteParser.d.ts +1 -11
  52. package/dist/docx/footnoteParser.js +1 -16
  53. package/dist/docx/headerFooterParser.d.ts +1 -9
  54. package/dist/docx/headerFooterParser.js +1 -12
  55. package/dist/docx/headerFooterVerbatim.d.ts +26 -1
  56. package/dist/docx/headerFooterVerbatim.js +81 -3
  57. package/dist/docx/imageParser.d.ts +13 -1
  58. package/dist/docx/imageParser.js +20 -11
  59. package/dist/docx/metadataPrivacy.js +6 -1
  60. package/dist/docx/packageParts.js +9 -3
  61. package/dist/docx/paragraphPropertySource.js +2 -1
  62. package/dist/docx/parser.js +2 -1
  63. package/dist/docx/removeHeaderFooterParts.js +17 -6
  64. package/dist/docx/rezip.d.ts +49 -5
  65. package/dist/docx/rezip.js +745 -229
  66. package/dist/docx/saveDiagnostics.d.ts +11 -0
  67. package/dist/docx/selectiveSave.d.ts +2 -1
  68. package/dist/docx/selectiveSave.js +32 -3
  69. package/dist/docx/selectiveXmlPatch.js +7 -5
  70. package/dist/docx/serializer/documentSerializer.d.ts +9 -2
  71. package/dist/docx/serializer/documentSerializer.js +29 -10
  72. package/dist/docx/server/applyDocxXmlPatchProposal.js +14 -9
  73. package/dist/docx/storyBlockReplay.d.ts +21 -1
  74. package/dist/docx/storyBlockReplay.js +113 -9
  75. package/dist/docx/storyPlainText.d.ts +20 -0
  76. package/dist/docx/storyPlainText.js +23 -0
  77. package/dist/docx/structuralXmlPatch.js +10 -22
  78. package/dist/docx/styleParser.d.ts +5 -1
  79. package/dist/docx/styleParser.js +1 -1
  80. package/dist/docx/tableParser.d.ts +1 -8
  81. package/dist/docx/tableParser.js +1 -28
  82. package/dist/docx/textBoxParser.d.ts +1 -5
  83. package/dist/docx/textBoxParser.js +1 -20
  84. package/dist/docx/unzip.js +6 -1
  85. package/dist/internal/acceptedBlockProjection.d.ts +12 -0
  86. package/dist/internal/acceptedBlockProjection.js +45 -0
  87. package/dist/managers/DocumentLoaderManager.js +2 -1
  88. package/dist/markdown/escape.d.ts +3 -1
  89. package/dist/markdown/escape.js +9 -2
  90. package/dist/markdown/renderBlock.js +5 -120
  91. package/dist/markdown/renderParagraph.js +4 -2
  92. package/dist/markdown/renderRuns.d.ts +7 -1
  93. package/dist/markdown/renderRuns.js +32 -13
  94. package/dist/prosemirror/canonicalCommands.d.ts +28 -4
  95. package/dist/prosemirror/canonicalCommands.js +11 -4
  96. package/dist/prosemirror/canonicalSelectionRange.d.ts +9 -0
  97. package/dist/prosemirror/canonicalSelectionRange.js +15 -0
  98. package/dist/prosemirror/clipboardIntent.d.ts +7 -0
  99. package/dist/prosemirror/clipboardIntent.js +9 -0
  100. package/dist/prosemirror/commands/pageBreak.js +2 -2
  101. package/dist/prosemirror/commands/pastePlainText.js +2 -1
  102. package/dist/prosemirror/conversion/hyphenTextCarriers.d.ts +8 -0
  103. package/dist/prosemirror/conversion/hyphenTextCarriers.js +8 -0
  104. package/dist/prosemirror/conversion/toProseDoc.d.ts +7 -1
  105. package/dist/prosemirror/conversion/toProseDoc.js +5 -4
  106. package/dist/prosemirror/extensions/core/ParagraphExtension.js +39 -30
  107. package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +56 -28
  108. package/dist/prosemirror/extensions/marks/markUtils.js +3 -1
  109. package/dist/prosemirror/insertOperations.js +2 -1
  110. package/dist/prosemirror/paragraphPropertyCarry.d.ts +7 -1
  111. package/dist/prosemirror/paragraphPropertyCarry.js +8 -3
  112. package/dist/prosemirror/plugins/suggestionMode.js +7 -5
  113. package/dist/types/canonicalCapabilities.d.ts +215 -0
  114. package/dist/types/canonicalCapabilities.js +212 -0
  115. package/dist/types/canonicalSave.d.ts +11 -0
  116. package/dist/types/canonicalSave.js +0 -0
  117. package/dist/types/docxSerialization.d.ts +14 -0
  118. package/dist/types/docxSerialization.js +7 -0
  119. package/package.json +4 -4
@@ -1,4 +1,5 @@
1
1
  import { withCanonicalCommand } from "../canonicalCommands.js";
2
+ import { canonicalSelectionRange } from "../canonicalSelectionRange.js";
2
3
  import { pageBreakRunParagraphProjectionDisposition } from "../pageBreakRunProjection.js";
3
4
  //#region src/prosemirror/commands/pageBreak.ts
4
5
  const UNSUPPORTED_PAGE_BREAK_RUN_ANCESTORS = /* @__PURE__ */ new Set([
@@ -46,8 +47,7 @@ const insertPageBreakCommand = (state, dispatch) => {
46
47
  */
47
48
  const insertPageBreak = withCanonicalCommand(insertPageBreakCommand, (state) => insertPageBreakCommand(state) ? [{
48
49
  type: "insertBreak",
49
- from: state.selection.from,
50
- to: state.selection.to,
50
+ ...canonicalSelectionRange(state),
51
51
  breakType: "page"
52
52
  }] : []);
53
53
  //#endregion
@@ -1,3 +1,4 @@
1
+ import { dispatchClipboardIntent } from "../clipboardIntent.js";
1
2
  import { Fragment, Slice } from "prosemirror-model";
2
3
  //#region src/prosemirror/commands/pastePlainText.ts
3
4
  /**
@@ -54,7 +55,7 @@ const pasteWithoutFormatting = (state, dispatch, view) => {
54
55
  navigator.clipboard.readText().then((text) => {
55
56
  if (!text || view.isDestroyed) return;
56
57
  const slice = buildPlainTextSlice(text, schema);
57
- view.dispatch(view.state.tr.replaceSelection(slice).scrollIntoView());
58
+ if (dispatchClipboardIntent(view, slice) === void 0) view.dispatch(view.state.tr.replaceSelection(slice).scrollIntoView());
58
59
  }).catch((error) => {
59
60
  if (typeof CustomEvent === "function" && !view.isDestroyed) view.dom.dispatchEvent(new CustomEvent(CLIPBOARD_READ_ERROR_EVENT, {
60
61
  detail: { error },
@@ -0,0 +1,8 @@
1
+ //#region src/prosemirror/conversion/hyphenTextCarriers.d.ts
2
+ /** These source units render as native text while keeping one logical atom each. */
3
+ declare const HYPHEN_TEXT_CARRIERS: {
4
+ readonly softHyphen: "­";
5
+ readonly noBreakHyphen: "‑";
6
+ };
7
+ //#endregion
8
+ export { HYPHEN_TEXT_CARRIERS };
@@ -0,0 +1,8 @@
1
+ //#region src/prosemirror/conversion/hyphenTextCarriers.ts
2
+ /** These source units render as native text while keeping one logical atom each. */
3
+ const HYPHEN_TEXT_CARRIERS = {
4
+ softHyphen: "­",
5
+ noBreakHyphen: "‑"
6
+ };
7
+ //#endregion
8
+ export { HYPHEN_TEXT_CARRIERS };
@@ -18,6 +18,12 @@ type ToProseDocOptions = {
18
18
  */
19
19
  warn?: ParseContext["warn"];
20
20
  };
21
+ /**
22
+ * Find bookmark pairs across every editable inline wrapper in a document story.
23
+ * Each endpoint is converted at its own structural position, so a range may
24
+ * start outside a hyperlink and end inside it (or the inverse).
25
+ */
26
+ declare const collectPairedBookmarkIds: (blocks: readonly document_d_exports.BlockContent[]) => ReadonlySet<number>;
21
27
  /**
22
28
  * Convert a Document to a ProseMirror document
23
29
  *
@@ -40,4 +46,4 @@ declare function footnoteToProseDoc(content: document_d_exports.BlockContent[],
40
46
  */
41
47
  declare function createEmptyDoc(): Node;
42
48
  //#endregion
43
- export { ToProseDocOptions, createEmptyDoc, footnoteToProseDoc, headerFooterToProseDoc, standaloneTableCellToProseMirror, toProseDoc };
49
+ export { ToProseDocOptions, collectPairedBookmarkIds, createEmptyDoc, footnoteToProseDoc, headerFooterToProseDoc, standaloneTableCellToProseMirror, toProseDoc };
@@ -49,6 +49,7 @@ import { tableCellStyleRegions, tableRowBand } from "../styles/tableStyleRegions
49
49
  import { canCarryTrackedRunMark, trackedRunInlineAtomDisposition } from "../trackedRunInlineAtoms.js";
50
50
  import { assertValidProseMirrorDocument } from "../validation.js";
51
51
  import { resolveEffectiveTableCellFormatting } from "./effectiveTableCellFormatting.js";
52
+ import { HYPHEN_TEXT_CARRIERS } from "./hyphenTextCarriers.js";
52
53
  import { listRenderingDefinitionsMatch } from "./listRenderingDefinition.js";
53
54
  import { createMarkInterner } from "./markInterner.js";
54
55
  import { hasSinkChildren } from "./preservedSinkCarriers.js";
@@ -786,7 +787,7 @@ function paragraphFormattingToAttrs(paragraph, styleResolver, tableParagraphOver
786
787
  if (tableOfContentsLevel !== void 0) attrs._tableOfContentsLevel = tableOfContentsLevel;
787
788
  if (formatting?.numPr) attrs.numPr = paragraphNumberingAttr(formatting.numPr);
788
789
  if (formatting?.numPrFromStyle) attrs.numPrFromStyle = paragraphNumberingAttr(formatting.numPrFromStyle);
789
- if (paragraph.listRendering && formatting?.numPr?.kind !== "none") Object.assign(attrs, listRenderingAttrPatch(paragraph.listRendering));
790
+ if (paragraph.listRendering && formatting?.numPr?.kind !== "none") Object.assign(attrs, CLEARED_LIST_RENDERING_ATTRS, listRenderingAttrPatch(paragraph.listRendering));
790
791
  if (formatting) attrs._originalFormatting = formatting;
791
792
  if (paragraph.propertyChanges && paragraph.propertyChanges.length > 0) attrs._propertyChanges = paragraph.propertyChanges.map(({ previousFormatting, ...change }) => {
792
793
  if (previousFormatting === void 0) return change;
@@ -2034,8 +2035,8 @@ function convertRunContent(content, marks, formatting, textBoxAnchors, authoredF
2034
2035
  text: content.text,
2035
2036
  level: PRESERVED_XML_LEVELS.run
2036
2037
  }).mark(marks)];
2037
- case "noBreakHyphen": return [schema.text("‑", marks)];
2038
- case "softHyphen": return [schema.text("­", marks)];
2038
+ case "noBreakHyphen": return [schema.text(HYPHEN_TEXT_CARRIERS.noBreakHyphen, marks)];
2039
+ case "softHyphen": return [schema.text(HYPHEN_TEXT_CARRIERS.softHyphen, marks)];
2039
2040
  case "symbol": return [schema.node("symbol", {
2040
2041
  font: content.font,
2041
2042
  char: content.char
@@ -2765,4 +2766,4 @@ function createEmptyDoc() {
2765
2766
  return schema.node("doc", null, [schema.node("paragraph", {}, [])]);
2766
2767
  }
2767
2768
  //#endregion
2768
- export { createEmptyDoc, footnoteToProseDoc, headerFooterToProseDoc, standaloneTableCellToProseMirror, toProseDoc };
2769
+ export { collectPairedBookmarkIds, createEmptyDoc, footnoteToProseDoc, headerFooterToProseDoc, standaloneTableCellToProseMirror, toProseDoc };
@@ -8,7 +8,7 @@ import { collectHeadings } from "../../../utils/headingCollector.js";
8
8
  import { tableOfContentsStyleLevel } from "../../../utils/tableOfContentsStyle.js";
9
9
  import { expectParagraphAttrs } from "../../attrs/index.js";
10
10
  import { autospacingMatchesBase } from "../../autospacingBase.js";
11
- import { withCanonicalParagraphFormatting } from "../../canonicalCommands.js";
11
+ import { withCanonicalCommand, withCanonicalParagraphFormatting, withCanonicalStartParagraphFormatting } from "../../canonicalCommands.js";
12
12
  import { removeSectionBreakAtSelection, setSectionBreakType } from "../../commands/sectionBreak.js";
13
13
  import { CLEARED_LIST_RENDERING_ATTRS } from "../../listMarker.js";
14
14
  import { directParagraphAlignment } from "../../paragraphAlignment.js";
@@ -696,7 +696,7 @@ const ParagraphExtension = createNodeExtension({
696
696
  }, { styleId: null }),
697
697
  insertSectionBreak: (breakType) => setSectionBreakType(breakType),
698
698
  removeSectionBreak: () => removeSectionBreakAtSelection,
699
- generateTOC: ({ title }) => (state, dispatch) => {
699
+ generateTOC: ({ title }) => withCanonicalCommand((state, dispatch) => {
700
700
  const builtInStyles = getDocumentBuiltInStyles(state);
701
701
  const headings = collectHeadings(state.doc, builtInStyles);
702
702
  if (headings.length === 0) return false;
@@ -773,44 +773,53 @@ const ParagraphExtension = createNodeExtension({
773
773
  tr.insert(insertPos, Fragment.from(tocNodes));
774
774
  dispatch(tr.scrollIntoView());
775
775
  return true;
776
- },
777
- toggleBidi: () => (state, dispatch) => {
778
- const { $from } = state.selection;
779
- const paragraph = $from.parent;
780
- if (paragraph.type.name !== "paragraph") return false;
781
- return setParagraphAttr("direction", directionIsRtl(paragraph.attrs["direction"]) ? {
782
- source: "manual",
783
- value: "ltr"
784
- } : {
776
+ }, (state) => [{
777
+ type: "generateTOC",
778
+ at: state.selection.from,
779
+ title
780
+ }]),
781
+ toggleBidi: () => {
782
+ const directionFor = (paragraph) => ({
785
783
  source: "manual",
786
- value: "rtl"
787
- })(state, dispatch);
784
+ value: directionIsRtl(expectParagraphAttrs(paragraph).direction) ? "ltr" : "rtl"
785
+ });
786
+ return withCanonicalStartParagraphFormatting((state, dispatch) => {
787
+ const paragraph = state.selection.$from.parent;
788
+ if (paragraph.type.name !== "paragraph") return false;
789
+ return setParagraphAttr("direction", directionFor(paragraph))(state, dispatch);
790
+ }, (paragraph) => ({ bidi: directionIsRtl(directionFor(paragraph)) }));
788
791
  },
789
- setRtl: () => setParagraphAttr("direction", {
792
+ setRtl: () => withCanonicalParagraphFormatting(setParagraphAttr("direction", {
790
793
  source: "manual",
791
794
  value: "rtl"
792
- }),
793
- setLtr: () => setParagraphAttr("direction", {
795
+ }), { bidi: true }),
796
+ setLtr: () => withCanonicalParagraphFormatting(setParagraphAttr("direction", {
794
797
  source: "manual",
795
798
  value: "ltr"
796
- }),
797
- setTabs: (tabs) => setParagraphAttr("tabs", tabs.length > 0 ? tabs : null),
798
- addTabStop: (position, alignment = "left", leader = "none") => (state, dispatch) => {
799
- const { $from } = state.selection;
800
- const paragraph = $from.parent;
801
- if (paragraph.type.name !== "paragraph") return false;
802
- return setParagraphAttr("tabs", [...(paragraph.attrs["tabs"] || []).filter((t) => t.position !== position), {
799
+ }), { bidi: false }),
800
+ setTabs: (tabs) => withCanonicalParagraphFormatting(setParagraphAttr("tabs", tabs.length > 0 ? tabs : null), { tabs: tabs.length > 0 ? tabs : null }),
801
+ addTabStop: (position, alignment = "left", leader = "none") => {
802
+ const tabsFor = (paragraph) => [...(expectParagraphAttrs(paragraph).tabs ?? []).filter((tab) => tab.position !== position), {
803
803
  position,
804
804
  alignment,
805
805
  leader
806
- }].toSorted((a, b) => a.position - b.position))(state, dispatch);
806
+ }].toSorted((a, b) => a.position - b.position);
807
+ return withCanonicalStartParagraphFormatting((state, dispatch) => {
808
+ const paragraph = state.selection.$from.parent;
809
+ if (paragraph.type.name !== "paragraph") return false;
810
+ return setParagraphAttr("tabs", tabsFor(paragraph))(state, dispatch);
811
+ }, (paragraph) => ({ tabs: tabsFor(paragraph) }));
807
812
  },
808
- removeTabStop: (position) => (state, dispatch) => {
809
- const { $from } = state.selection;
810
- const paragraph = $from.parent;
811
- if (paragraph.type.name !== "paragraph") return false;
812
- const newTabs = (paragraph.attrs["tabs"] || []).filter((t) => t.position !== position);
813
- return setParagraphAttr("tabs", newTabs.length > 0 ? newTabs : null)(state, dispatch);
813
+ removeTabStop: (position) => {
814
+ const tabsFor = (paragraph) => {
815
+ const tabs = (expectParagraphAttrs(paragraph).tabs ?? []).filter((tab) => tab.position !== position);
816
+ return tabs.length > 0 ? tabs : null;
817
+ };
818
+ return withCanonicalStartParagraphFormatting((state, dispatch) => {
819
+ const paragraph = state.selection.$from.parent;
820
+ if (paragraph.type.name !== "paragraph") return false;
821
+ return setParagraphAttr("tabs", tabsFor(paragraph))(state, dispatch);
822
+ }, (paragraph) => ({ tabs: tabsFor(paragraph) }));
814
823
  }
815
824
  } };
816
825
  }
@@ -1,6 +1,10 @@
1
+ import { BUILT_IN_STYLE_NAME } from "../../../docx/builtInStyles.js";
1
2
  import { anchorTargetAttrs, normalizeUserUrl, sanitizeExternalUrl } from "../../../utils/urlSecurity.js";
2
3
  import { expectHyperlinkMarkAttrs } from "../../attrs/index.js";
4
+ import { withCanonicalCommand } from "../../canonicalCommands.js";
5
+ import { canonicalSelectionRange } from "../../canonicalSelectionRange.js";
3
6
  import { removeHyperlinkInRange } from "../../hyperlinkRemoval.js";
7
+ import { getDocumentBuiltInStyles } from "../../plugins/documentStyleState.js";
4
8
  import { createMarkExtension } from "../create.js";
5
9
  import { isMarkActive } from "./markUtils.js";
6
10
  import { panic } from "better-result";
@@ -121,7 +125,7 @@ const HyperlinkExtension = createMarkExtension({
121
125
  onSchemaReady(ctx) {
122
126
  if (!ctx.schema.marks["hyperlink"]) panic("Missing mark type: hyperlink");
123
127
  const documentHyperlinkType = (state) => state.schema.marks["hyperlink"] ?? panic("Missing mark type: hyperlink");
124
- const setHyperlink = (href, tooltip) => (state, dispatch) => {
128
+ const setHyperlink = (href, tooltip) => withCanonicalCommand((state, dispatch) => {
125
129
  const hlType = documentHyperlinkType(state);
126
130
  const { from, to, empty } = state.selection;
127
131
  if (empty) return false;
@@ -136,34 +140,52 @@ const HyperlinkExtension = createMarkExtension({
136
140
  dispatch(tr.scrollIntoView());
137
141
  }
138
142
  return true;
139
- };
140
- const removeHyperlink = (state, dispatch) => {
143
+ }, (state) => state.selection.empty ? [] : [{
144
+ type: "setHyperlink",
145
+ ...canonicalSelectionRange(state),
146
+ href: normalizeHyperlinkInput(href),
147
+ ...tooltip ? { tooltip } : {}
148
+ }]);
149
+ const removalRange = (state) => {
150
+ const { empty, $from } = state.selection;
151
+ const { from, to } = canonicalSelectionRange(state);
152
+ if (!empty) return {
153
+ from,
154
+ to
155
+ };
141
156
  const hlType = documentHyperlinkType(state);
142
- const { from, to, empty } = state.selection;
143
- if (empty) {
144
- const $pos = state.selection.$from;
145
- if (!$pos.marks().find((m) => m.type === hlType)) return false;
146
- let start = $pos.pos;
147
- let end = $pos.pos;
148
- $pos.parent.forEach((node, offset) => {
149
- if (node.isText) {
150
- const nodeStart = $pos.start() + offset;
151
- const nodeEnd = nodeStart + node.nodeSize;
152
- if (nodeStart <= $pos.pos && $pos.pos <= nodeEnd) {
153
- if (node.marks.some((m) => m.type === hlType)) {
154
- start = Math.min(start, nodeStart);
155
- end = Math.max(end, nodeEnd);
156
- }
157
- }
158
- }
159
- });
160
- if (dispatch) dispatch(removeHyperlinkInRange(state, state.tr, start, end).scrollIntoView());
161
- return true;
162
- }
163
- if (dispatch) dispatch(removeHyperlinkInRange(state, state.tr, from, to).scrollIntoView());
164
- return true;
157
+ if (!$from.marks().some((mark) => mark.type === hlType)) return void 0;
158
+ let start = from;
159
+ let end = to;
160
+ $from.parent.forEach((node, offset) => {
161
+ const nodeStart = $from.start() + offset;
162
+ const nodeEnd = nodeStart + node.nodeSize;
163
+ if (node.isText && nodeStart <= from && from <= nodeEnd && node.marks.some((mark) => mark.type === hlType)) {
164
+ start = Math.min(start, nodeStart);
165
+ end = Math.max(end, nodeEnd);
166
+ }
167
+ });
168
+ return {
169
+ from: start,
170
+ to: end
171
+ };
165
172
  };
166
- const insertHyperlink = (text, href, tooltip) => (state, dispatch) => {
173
+ const removeHyperlink = withCanonicalCommand((state, dispatch) => {
174
+ const range = removalRange(state);
175
+ if (!range) return false;
176
+ if (dispatch) dispatch(removeHyperlinkInRange(state, state.tr, range.from, range.to).scrollIntoView());
177
+ return true;
178
+ }, (state) => {
179
+ const range = removalRange(state);
180
+ if (!range) return [];
181
+ const hyperlinkStyleId = getDocumentBuiltInStyles(state).styleIdForBuiltInName(BUILT_IN_STYLE_NAME.hyperlink);
182
+ return [{
183
+ type: "removeHyperlink",
184
+ ...range,
185
+ ...hyperlinkStyleId === void 0 ? {} : { hyperlinkStyleId }
186
+ }];
187
+ });
188
+ const insertHyperlink = (text, href, tooltip) => withCanonicalCommand((state, dispatch) => {
167
189
  const hlType = documentHyperlinkType(state);
168
190
  if (dispatch) {
169
191
  const mark = hlType.create({
@@ -174,7 +196,13 @@ const HyperlinkExtension = createMarkExtension({
174
196
  dispatch(state.tr.replaceSelectionWith(textNode, false).scrollIntoView());
175
197
  }
176
198
  return true;
177
- };
199
+ }, (state) => [{
200
+ type: "insertHyperlink",
201
+ ...canonicalSelectionRange(state),
202
+ text,
203
+ href: normalizeHyperlinkInput(href),
204
+ ...tooltip ? { tooltip } : {}
205
+ }]);
178
206
  return { commands: {
179
207
  setHyperlink,
180
208
  removeHyperlink: () => removeHyperlink,
@@ -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 { canonicalRunFormatting, withCanonicalCommand } from "../../canonicalCommands.js";
6
+ import { canonicalSelectionRange } from "../../canonicalSelectionRange.js";
6
7
  import { runShadingAttrsToShading, shadingToRunShadingAttrs } from "../../conversion/runShadingMark.js";
7
8
  import { documentMarkType } from "../../documentSchema.js";
8
9
  import { selectRunFormattingCarrierRepresentations } from "../../runFormattingInlineCarriers.js";
@@ -188,7 +189,8 @@ function createMarkWithMergedAttrs(markType, currentMark, nextAttrs) {
188
189
  }
189
190
  const canonicalSetMarkFormatting = ({ state, type, attrs }) => {
190
191
  const markType = documentMarkType(state, type);
191
- const { from, to, empty } = state.selection;
192
+ const { empty } = state.selection;
193
+ const { from, to } = canonicalSelectionRange(state);
192
194
  if (empty) return canonicalRunFormatting(state, marksToTextFormatting([createMarkWithMergedAttrs(markType, markType.isInSet(state.storedMarks ?? state.selection.$from.marks()), attrs)]));
193
195
  return selectRunFormattingCarrierRepresentations({
194
196
  doc: state.doc,
@@ -2,6 +2,7 @@ import { insertImageFromFile } from "./commands/image.js";
2
2
  import { insertPageBreak } from "./commands/pageBreak.js";
3
3
  import { generateTableOfContents } from "./commands/paragraph.js";
4
4
  import { insertTable } from "./commands/table.js";
5
+ import { executeEditorCommand } from "./executeEditorCommand.js";
5
6
  //#region src/prosemirror/insertOperations.ts
6
7
  /** Insert a `rows × columns` table at the current selection. */
7
8
  function insertTableInView(view, rows, columns) {
@@ -18,7 +19,7 @@ function insertPageBreakInView(view) {
18
19
  * {@link GenerateTableOfContentsOptions}.
19
20
  */
20
21
  function insertTableOfContentsInView(view, options) {
21
- return generateTableOfContents(options)(view.state, view.dispatch);
22
+ return executeEditorCommand(view, generateTableOfContents(options));
22
23
  }
23
24
  //#endregion
24
25
  export { insertImageFromFile, insertPageBreakInView, insertTableInView, insertTableOfContentsInView };
@@ -4,6 +4,7 @@ import { NumberingMap } from "../docx/numberingParser.js";
4
4
  import { Node } from "prosemirror-model";
5
5
  import { Transaction } from "prosemirror-state";
6
6
  //#region src/prosemirror/paragraphPropertyCarry.d.ts
7
+ type RecordedFormatting = NonNullable<ParagraphPropertyChangeAttrs["previousFormatting"]>;
7
8
  type CarryParagraphPropertiesOptions = {
8
9
  tr: Transaction;
9
10
  /** The paragraph that takes the properties. */
@@ -17,6 +18,11 @@ type CarryParagraphPropertiesOptions = {
17
18
  /** Properties the paragraph keeps its own values of: ones its own batch set. */
18
19
  keep?: ReadonlySet<string>;
19
20
  };
21
+ type CarriedParagraphPropertiesOptions = Pick<CarryParagraphPropertiesOptions, "source" | "keep"> & {
22
+ target: Node;
23
+ };
24
+ /** The exact formatting transfer, shared with pre-mutation capacity checks. */
25
+ declare const carriedParagraphProperties: ({ source, target, keep }: CarriedParagraphPropertiesOptions) => RecordedFormatting;
20
26
  /**
21
27
  * Give a paragraph another one's properties: the pPr a property change
22
28
  * covers. Its identity, mark, mark run properties and section stay its own,
@@ -61,4 +67,4 @@ declare const recordReplacedParagraphProperties: ({ tr, position, replaced, revi
61
67
  */
62
68
  declare const propertiesSetInBatch: (node: Node, batchRevisionIds: ReadonlySet<number>) => ReadonlySet<string>;
63
69
  //#endregion
64
- export { carryParagraphProperties, paragraphLeftAfter, propertiesSetInBatch, recordReplacedParagraphProperties };
70
+ export { carriedParagraphProperties, carryParagraphProperties, paragraphLeftAfter, propertiesSetInBatch, recordReplacedParagraphProperties };
@@ -17,6 +17,8 @@ import { panic } from "better-result";
17
17
  * revision, so accepting reads as the direct edit and rejecting restores the
18
18
  * second's own.
19
19
  */
20
+ /** The exact formatting transfer, shared with pre-mutation capacity checks. */
21
+ 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))]);
20
22
  /**
21
23
  * Give a paragraph another one's properties: the pPr a property change
22
24
  * covers. Its identity, mark, mark run properties and section stay its own,
@@ -32,8 +34,11 @@ const carryParagraphProperties = ({ tr, position, source, styleResolver, numberi
32
34
  tracked: false
33
35
  };
34
36
  const previousFormatting = paragraphPropertiesSnapshot(target);
35
- const own = previousFormatting;
36
- const formatting = Object.fromEntries([...Object.entries(paragraphPropertiesSnapshot(source)).filter(([key]) => !keep?.has(key)), ...Object.entries(own).filter(([key]) => keep?.has(key))]);
37
+ const formatting = carriedParagraphProperties({
38
+ source,
39
+ target,
40
+ ...keep === void 0 ? {} : { keep }
41
+ });
37
42
  if (JSON.stringify(previousFormatting) === JSON.stringify(formatting)) return {
38
43
  tr,
39
44
  changed: false,
@@ -152,4 +157,4 @@ const propertiesSetInBatch = (node, batchRevisionIds) => {
152
157
  return new Set([.../* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(now)])].filter((key) => JSON.stringify(before[key]) !== JSON.stringify(now[key])));
153
158
  };
154
159
  //#endregion
155
- export { carryParagraphProperties, paragraphLeftAfter, propertiesSetInBatch, recordReplacedParagraphProperties };
160
+ export { carriedParagraphProperties, carryParagraphProperties, paragraphLeftAfter, propertiesSetInBatch, recordReplacedParagraphProperties };
@@ -661,21 +661,23 @@ function handleSuggestionEnter(view, pluginState) {
661
661
  view.dispatch(tr.scrollIntoView());
662
662
  return true;
663
663
  }
664
- /** Find the adjacent visible unit without consuming a bookmark range marker. */
664
+ /** Find the adjacent visible unit without consuming retained deletions or bookmark markers. */
665
665
  function caretDeleteTarget(state, direction) {
666
666
  const { $from } = state.selection;
667
667
  const backward = direction === "backward";
668
668
  let from = backward ? $from.pos - 1 : $from.pos;
669
669
  let to = backward ? $from.pos : $from.pos + 1;
670
670
  while (from >= $from.start() && to <= $from.end()) {
671
- const adjacent = state.doc.resolve(from).nodeAfter;
672
- if (adjacent?.type.name !== "bookmarkBoundary") return {
671
+ const $adjacent = state.doc.resolve(from);
672
+ const adjacent = $adjacent.nodeAfter;
673
+ if (!adjacent || adjacent.type.name !== "bookmarkBoundary" && !adjacent.marks.some((mark) => mark.type === state.schema.marks["deletion"])) return {
673
674
  type: "inlineUnit",
674
675
  from,
675
676
  to
676
677
  };
677
- from += backward ? -adjacent.nodeSize : adjacent.nodeSize;
678
- to += backward ? -adjacent.nodeSize : adjacent.nodeSize;
678
+ const skipped = backward && adjacent.isText ? $adjacent.textOffset + 1 : adjacent.nodeSize;
679
+ from += backward ? -skipped : skipped;
680
+ to += backward ? -skipped : skipped;
679
681
  }
680
682
  return { type: "paragraphEdge" };
681
683
  }