@stll/folio-core 0.22.3 → 0.23.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 (40) hide show
  1. package/dist/ai-edits/apply.d.ts +1 -1
  2. package/dist/document-operations.d.ts +4 -4
  3. package/dist/document-operations.js +17 -1
  4. package/dist/docx/rezip.d.ts +1 -1
  5. package/dist/docx/rezip.js +12 -31
  6. package/dist/layout-bridge/convert/toFlowBlocks.js +35 -61
  7. package/dist/layout-painter/imageLayout.d.ts +1 -1
  8. package/dist/managers/AutoSaveManager.js +8 -40
  9. package/dist/managers/TableSelectionManager.d.ts +1 -1
  10. package/dist/managers/autoSaveCodec.d.ts +17 -0
  11. package/dist/managers/autoSaveCodec.js +109 -0
  12. package/dist/prosemirror/attrs/index.js +273 -25
  13. package/dist/prosemirror/commands/comments.js +3 -3
  14. package/dist/prosemirror/commands/formatting.js +19 -19
  15. package/dist/prosemirror/commands/index.d.ts +2 -2
  16. package/dist/prosemirror/commands/paragraph.js +32 -32
  17. package/dist/prosemirror/commands/propertyChangeScope.d.ts +4 -2
  18. package/dist/prosemirror/commands/propertyChangeScope.js +8 -8
  19. package/dist/prosemirror/commands/table.d.ts +10 -52
  20. package/dist/prosemirror/commands/table.js +34 -34
  21. package/dist/prosemirror/conversion/fromProseDoc.js +20 -17
  22. package/dist/prosemirror/conversion/sdtAttrs.d.ts +2 -1
  23. package/dist/prosemirror/conversion/sdtAttrs.js +9 -4
  24. package/dist/prosemirror/extensions/ExtensionManager.d.ts +6 -3
  25. package/dist/prosemirror/extensions/ExtensionManager.js +5 -3
  26. package/dist/prosemirror/extensions/core/ParagraphExtension.d.ts +1 -1
  27. package/dist/prosemirror/extensions/features/ListExtension.js +4 -4
  28. package/dist/prosemirror/extensions/nodes/ShapeExtension.d.ts +1 -1
  29. package/dist/prosemirror/extensions/nodes/TextBoxExtension.d.ts +1 -1
  30. package/dist/prosemirror/extensions/types.d.ts +134 -1
  31. package/dist/prosemirror/index.d.ts +2 -2
  32. package/dist/prosemirror/plugins/selectionTracker.js +25 -99
  33. package/dist/prosemirror/revisionCarriers.js +3 -1
  34. package/dist/prosemirror/schema/index.d.ts +2 -2
  35. package/dist/prosemirror/schema/index.js +87 -0
  36. package/dist/prosemirror/schema/nodes.d.ts +13 -2
  37. package/dist/prosemirror/selectionState.d.ts +11 -5
  38. package/dist/prosemirror/selectionState.js +94 -57
  39. package/dist/utils/tableOperations.d.ts +7 -6
  40. package/package.json +3 -3
@@ -1,4 +1,5 @@
1
- import { collectMarksInRange } from "../selectionMarks.js";
1
+ import { expectCommentMarkAttrs, expectTrackedChangeMarkAttrs } from "../attrs/index.js";
2
+ import { extractSelectionSnapshot } from "../selectionState.js";
2
3
  import { Plugin, PluginKey } from "prosemirror-state";
3
4
  //#region src/prosemirror/plugins/selectionTracker.ts
4
5
  /**
@@ -18,56 +19,41 @@ const selectionTrackerKey = new PluginKey("selectionTracker");
18
19
  * Extract selection context from editor state
19
20
  */
20
21
  function extractSelectionContext(state) {
21
- const { selection, doc } = state;
22
- const { from, to, empty } = selection;
23
- const $from = doc.resolve(from);
24
- let startParagraphIndex = 0;
25
- let endParagraphIndex = 0;
26
- doc.forEach((_node, offset, index) => {
27
- if (offset > to) return;
28
- if (offset <= from) startParagraphIndex = index;
29
- if (offset <= to) endParagraphIndex = index;
30
- });
31
- const textFormatting = extractTextFormatting(state);
32
- const paragraph = $from.parent;
33
- const paragraphFormatting = {};
34
- if (paragraph.type.name === "paragraph") {
35
- if (paragraph.attrs["alignment"]) paragraphFormatting.alignment = paragraph.attrs["alignment"];
36
- if (typeof paragraph.attrs["lineSpacing"] === "number") {
37
- paragraphFormatting.lineSpacing = paragraph.attrs["lineSpacing"];
38
- paragraphFormatting.lineSpacingRule = paragraph.attrs["lineSpacingRule"];
39
- }
40
- if (typeof paragraph.attrs["snapToGrid"] === "boolean") paragraphFormatting.snapToGrid = paragraph.attrs["snapToGrid"];
41
- if (paragraph.attrs["indentLeft"]) paragraphFormatting.indentLeft = paragraph.attrs["indentLeft"];
42
- if (paragraph.attrs["indentRight"]) paragraphFormatting.indentRight = paragraph.attrs["indentRight"];
43
- if (paragraph.attrs["indentFirstLine"]) paragraphFormatting.indentFirstLine = paragraph.attrs["indentFirstLine"];
44
- if (paragraph.attrs["hangingIndent"]) paragraphFormatting.hangingIndent = paragraph.attrs["hangingIndent"];
45
- if (paragraph.attrs["tabs"]) paragraphFormatting.tabs = paragraph.attrs["tabs"];
46
- if (paragraph.attrs["numPr"]) paragraphFormatting.numPr = paragraph.attrs["numPr"];
47
- if (paragraph.attrs["styleId"]) paragraphFormatting.styleId = paragraph.attrs["styleId"];
48
- }
49
- const numPr = paragraph.attrs["numPr"];
22
+ const { selection } = state;
23
+ const { empty } = selection;
24
+ const snapshot = extractSelectionSnapshot(state);
25
+ const paragraphFormatting = snapshot.styleId === null ? snapshot.paragraphFormatting : {
26
+ ...snapshot.paragraphFormatting,
27
+ styleId: snapshot.styleId
28
+ };
29
+ const numPr = snapshot.paragraphFormatting.numPr;
50
30
  const inList = !!numPr?.numId;
51
31
  let listType;
52
32
  if (numPr?.numId === 1) listType = "bullet";
53
33
  else if (numPr?.numId) listType = "numbered";
54
34
  const listLevel = numPr?.ilvl;
55
- const allMarks = state.storedMarks || (empty ? $from.marks() : []);
35
+ const allMarks = state.storedMarks || (empty ? selection.$from.marks() : []);
56
36
  const activeCommentIds = [];
57
37
  let inInsertion = false;
58
38
  let inDeletion = false;
59
39
  for (const mark of allMarks) {
60
- if (mark.type.name === "comment" && mark.attrs["commentId"]) activeCommentIds.push(mark.attrs["commentId"]);
61
- if (mark.type.name === "insertion") inInsertion = true;
62
- if (mark.type.name === "deletion") inDeletion = true;
40
+ if (mark.type.name === "comment") activeCommentIds.push(expectCommentMarkAttrs(mark).commentId);
41
+ if (mark.type.name === "insertion") {
42
+ expectTrackedChangeMarkAttrs(mark);
43
+ inInsertion = true;
44
+ }
45
+ if (mark.type.name === "deletion") {
46
+ expectTrackedChangeMarkAttrs(mark);
47
+ inDeletion = true;
48
+ }
63
49
  }
64
50
  return {
65
- hasSelection: !empty,
66
- isMultiParagraph: startParagraphIndex !== endParagraphIndex,
67
- textFormatting,
51
+ hasSelection: snapshot.hasSelection,
52
+ isMultiParagraph: snapshot.isMultiParagraph,
53
+ textFormatting: snapshot.textFormatting,
68
54
  paragraphFormatting,
69
- startParagraphIndex,
70
- endParagraphIndex,
55
+ startParagraphIndex: snapshot.startParagraphIndex,
56
+ endParagraphIndex: snapshot.endParagraphIndex,
71
57
  inList,
72
58
  ...listType !== void 0 ? { listType } : {},
73
59
  ...listLevel !== void 0 ? { listLevel } : {},
@@ -77,66 +63,6 @@ function extractSelectionContext(state) {
77
63
  };
78
64
  }
79
65
  /**
80
- * Extract text formatting from current selection/cursor marks
81
- */
82
- function extractTextFormatting(state) {
83
- const { selection, doc } = state;
84
- const { from, to, empty, $from } = selection;
85
- const marks = empty ? state.storedMarks || $from.marks() : collectMarksInRange({
86
- doc,
87
- from,
88
- to
89
- });
90
- const formatting = {};
91
- for (const mark of marks) switch (mark.type.name) {
92
- case "bold":
93
- formatting.bold = true;
94
- break;
95
- case "italic":
96
- formatting.italic = true;
97
- break;
98
- case "underline":
99
- if (mark.attrs["style"] !== "none") formatting.underline = {
100
- style: mark.attrs["style"] || "single",
101
- color: mark.attrs["color"]
102
- };
103
- break;
104
- case "strike":
105
- if (mark.attrs["double"]) formatting.doubleStrike = true;
106
- else formatting.strike = true;
107
- break;
108
- case "textColor":
109
- formatting.color = {
110
- rgb: mark.attrs["rgb"],
111
- themeColor: mark.attrs["themeColor"],
112
- themeTint: mark.attrs["themeTint"],
113
- themeShade: mark.attrs["themeShade"]
114
- };
115
- break;
116
- case "highlight":
117
- formatting.highlight = mark.attrs["color"];
118
- break;
119
- case "fontSize":
120
- formatting.fontSize = mark.attrs["size"];
121
- break;
122
- case "fontFamily":
123
- formatting.fontFamily = {
124
- ascii: mark.attrs["ascii"],
125
- hAnsi: mark.attrs["hAnsi"],
126
- asciiTheme: mark.attrs["asciiTheme"]
127
- };
128
- break;
129
- case "superscript":
130
- formatting.vertAlign = "superscript";
131
- break;
132
- case "subscript":
133
- formatting.vertAlign = "subscript";
134
- break;
135
- default: break;
136
- }
137
- return formatting;
138
- }
139
- /**
140
66
  * Create selection tracker plugin
141
67
  */
142
68
  function createSelectionTrackerPlugin(onSelectionChange) {
@@ -1,3 +1,4 @@
1
+ import { expectParagraphAttrs } from "./attrs/index.js";
1
2
  //#region src/prosemirror/revisionCarriers.ts
2
3
  const isObjectRecord = (value) => typeof value === "object" && value !== null;
3
4
  const revisionMetadata = (value) => {
@@ -34,6 +35,7 @@ const getFolioNodeRevisionCarriers = (node, nodePos) => {
34
35
  if (node.type.name === "paragraph") {
35
36
  const from = nodePos + node.nodeSize - 1;
36
37
  const to = nodePos + node.nodeSize;
38
+ const paragraphAttrs = expectParagraphAttrs(node);
37
39
  const paragraphMark = node.attrs["pPrMark"];
38
40
  if (isObjectRecord(paragraphMark) && (paragraphMark["kind"] === "ins" || paragraphMark["kind"] === "del")) {
39
41
  const metadata = revisionMetadata(paragraphMark["info"]);
@@ -47,7 +49,7 @@ const getFolioNodeRevisionCarriers = (node, nodePos) => {
47
49
  }
48
50
  appendPropertyCarriers({
49
51
  carriers,
50
- changes: node.attrs["_propertyChanges"],
52
+ changes: paragraphAttrs._propertyChanges,
51
53
  type: "paragraphPropertiesChanged",
52
54
  node,
53
55
  from,
@@ -1,6 +1,6 @@
1
1
  import { CharacterSpacingAttrs, CharacterStyleAttrs, CommentAttrs, EmphasisMarkAttrs, FontFamilyAttrs, FontSizeAttrs, FootnoteRefAttrs, HighlightAttrs, HyperlinkAttrs, LanguageAttrs, RunFormattingOverrideAttrs, RunPropertyChangeMarkAttrs, RunShadingAttrs, StrikeAttrs, TextColorAttrs, TextEffectAttrs, TrackedChangeMarkAttrs, UnderlineAttrs } from "./marks.js";
2
+ import { BlockSdtAttrs, FieldAttrs, HardBreakAttrs, ImageAttrs, ImagePositionAttrs, MathAttrs, ParagraphAttrs, ParagraphPropertyChangeAttrs, SdtAttrs, ShapeAttrs, SymbolAttrs, TabAttrs, TableAttrs, TableCellAttrs, TableRowAttrs, TextBoxAttrs } from "./nodes.js";
2
3
  import { ExtensionManager } from "../extensions/ExtensionManager.js";
3
- import { BlockSdtAttrs, FieldAttrs, HardBreakAttrs, ImageAttrs, ImagePositionAttrs, MathAttrs, ParagraphAttrs, SdtAttrs, ShapeAttrs, SymbolAttrs, TabAttrs, TableAttrs, TableCellAttrs, TableRowAttrs, TextBoxAttrs } from "./nodes.js";
4
4
  //#region src/prosemirror/schema/index.d.ts
5
5
  declare const singletonManager: ExtensionManager;
6
6
  declare const schema: import("prosemirror-model").Schema<any, any>;
@@ -11,4 +11,4 @@ type DocxSchema = typeof schema;
11
11
  type DocxNode = ReturnType<typeof schema.node>;
12
12
  type DocxMark = ReturnType<typeof schema.mark>;
13
13
  //#endregion
14
- export { type BlockSdtAttrs, type CharacterSpacingAttrs, type CharacterStyleAttrs, type CommentAttrs, DocxMark, DocxNode, DocxSchema, type EmphasisMarkAttrs, type FieldAttrs, type FontFamilyAttrs, type FontSizeAttrs, type FootnoteRefAttrs, type HardBreakAttrs, type HighlightAttrs, type HyperlinkAttrs, type ImageAttrs, type ImagePositionAttrs, type LanguageAttrs, type MathAttrs, type ParagraphAttrs, type RunFormattingOverrideAttrs, type RunPropertyChangeMarkAttrs, type RunShadingAttrs, type SdtAttrs, type ShapeAttrs, type StrikeAttrs, type SymbolAttrs, type TabAttrs, type TableAttrs, type TableCellAttrs, type TableRowAttrs, type TextBoxAttrs, type TextColorAttrs, type TextEffectAttrs, type TrackedChangeMarkAttrs, type UnderlineAttrs, schema, singletonManager };
14
+ export { type BlockSdtAttrs, type CharacterSpacingAttrs, type CharacterStyleAttrs, type CommentAttrs, DocxMark, DocxNode, DocxSchema, type EmphasisMarkAttrs, type FieldAttrs, type FontFamilyAttrs, type FontSizeAttrs, type FootnoteRefAttrs, type HardBreakAttrs, type HighlightAttrs, type HyperlinkAttrs, type ImageAttrs, type ImagePositionAttrs, type LanguageAttrs, type MathAttrs, type ParagraphAttrs, type ParagraphPropertyChangeAttrs, type RunFormattingOverrideAttrs, type RunPropertyChangeMarkAttrs, type RunShadingAttrs, type SdtAttrs, type ShapeAttrs, type StrikeAttrs, type SymbolAttrs, type TabAttrs, type TableAttrs, type TableCellAttrs, type TableRowAttrs, type TextBoxAttrs, type TextColorAttrs, type TextEffectAttrs, type TrackedChangeMarkAttrs, type UnderlineAttrs, schema, singletonManager };
@@ -14,6 +14,93 @@ import { createStarterKit } from "../extensions/StarterKit.js";
14
14
  const mgr = new ExtensionManager(createStarterKit());
15
15
  mgr.buildSchema();
16
16
  mgr.initializeRuntime();
17
+ const completeCommandNames = (names) => names;
18
+ const requiredCommands = completeCommandNames([
19
+ "toggleBold",
20
+ "toggleItalic",
21
+ "toggleUnderline",
22
+ "toggleStrike",
23
+ "toggleSuperscript",
24
+ "toggleSubscript",
25
+ "setTextColor",
26
+ "clearTextColor",
27
+ "setHighlight",
28
+ "clearHighlight",
29
+ "setFontSize",
30
+ "clearFontSize",
31
+ "setFontFamily",
32
+ "clearFontFamily",
33
+ "setUnderlineStyle",
34
+ "setHyperlink",
35
+ "removeHyperlink",
36
+ "insertHyperlink",
37
+ "setAlignment",
38
+ "alignLeft",
39
+ "alignCenter",
40
+ "alignRight",
41
+ "alignJustify",
42
+ "setLineSpacing",
43
+ "singleSpacing",
44
+ "oneAndHalfSpacing",
45
+ "doubleSpacing",
46
+ "increaseIndent",
47
+ "decreaseIndent",
48
+ "setIndentLeft",
49
+ "setIndentRight",
50
+ "setIndentFirstLine",
51
+ "toggleBulletList",
52
+ "toggleNumberedList",
53
+ "increaseListLevel",
54
+ "decreaseListLevel",
55
+ "removeList",
56
+ "setSpaceBefore",
57
+ "setSpaceAfter",
58
+ "applyStyle",
59
+ "clearStyle",
60
+ "insertSectionBreak",
61
+ "removeSectionBreak",
62
+ "addTabStop",
63
+ "removeTabStop",
64
+ "toggleBidi",
65
+ "setRtl",
66
+ "setLtr",
67
+ "setTabs",
68
+ "generateTOC",
69
+ "insertTable",
70
+ "addRowAbove",
71
+ "addRowBelow",
72
+ "deleteRow",
73
+ "addColumnLeft",
74
+ "addColumnRight",
75
+ "deleteColumn",
76
+ "deleteTable",
77
+ "selectTable",
78
+ "selectRow",
79
+ "selectColumn",
80
+ "mergeCells",
81
+ "splitCell",
82
+ "setCellBorder",
83
+ "setTableBorderPreset",
84
+ "setTableBorders",
85
+ "removeTableBorders",
86
+ "setAllTableBorders",
87
+ "setOutsideTableBorders",
88
+ "setInsideTableBorders",
89
+ "setCellVerticalAlign",
90
+ "setCellMargins",
91
+ "setCellTextDirection",
92
+ "toggleNoWrap",
93
+ "setRowHeight",
94
+ "toggleHeaderRow",
95
+ "distributeColumns",
96
+ "autoFitContents",
97
+ "setTableProperties",
98
+ "applyTableStyle",
99
+ "setCellFillColor",
100
+ "setTableBorderColor",
101
+ "setTableBorderWidth"
102
+ ]);
103
+ for (const commandName of requiredCommands) mgr.requireCommand(commandName);
17
104
  const singletonManager = mgr;
18
105
  const schema = mgr.getSchema();
19
106
  //#endregion
@@ -177,7 +177,7 @@ type ParagraphAttrs = {
177
177
  * them in UI today, but stripping them on every edit would corrupt the
178
178
  * `w:pPrChange` history Word relies on for "show previous formatting"
179
179
  * and for reverting an accepted property change. */
180
- _propertyChanges?: document_d_exports.ParagraphPropertyChange[];
180
+ _propertyChanges?: ParagraphPropertyChangeAttrs[];
181
181
  /** Paragraph-mark insertion / deletion (`<w:pPr><w:rPr><w:ins/>` /
182
182
  * `<w:del/>`). Word emits this when the paragraph break itself was
183
183
  * authored in track-changes mode — pressing Enter mid-paragraph
@@ -192,6 +192,17 @@ type ParagraphAttrs = {
192
192
  */
193
193
  _suggestedInsert?: SuggestedStructuralMarker | null;
194
194
  };
195
+ /**
196
+ * ProseMirror property-change attrs may also carry the editor's list-marker
197
+ * snapshot fields alongside the canonical paragraph formatting fields.
198
+ * Keeping that shape typed here lets layout consume validated attrs directly.
199
+ */
200
+ type ParagraphPropertyChangeAttrs = Omit<document_d_exports.ParagraphPropertyChange, "previousFormatting" | "currentFormatting"> & {
201
+ previousFormatting?: Omit<document_d_exports.ParagraphFormatting, "numPr"> & {
202
+ numPr?: document_d_exports.ParagraphFormatting["numPr"] | null;
203
+ } & Partial<Pick<ParagraphAttrs, "listIsBullet" | "listIsLegal" | "listNumFmt" | "listMarker" | "listMarkerHidden" | "listMarkerFontFamily" | "listMarkerFontSize" | "listMarkerBold" | "listMarkerAlignment" | "listMarkerSuffix" | "listLevelNumFmts" | "listLevelStarts" | "listAbstractNumId" | "listStartOverride" | "lineSpacingExplicit" | "direction" | "_autospacingBase">>;
204
+ currentFormatting?: document_d_exports.ParagraphFormatting;
205
+ };
195
206
  /**
196
207
  * Image position for floating images (horizontal and vertical positioning)
197
208
  */
@@ -689,4 +700,4 @@ type TableCellAttrs = {
689
700
  _docxVMergeContinuationCells?: document_d_exports.TableCell[];
690
701
  };
691
702
  //#endregion
692
- export { BlockSdtAttrs, FieldAttrs, HardBreakAttrs, ImageAttrs, ImagePositionAttrs, MathAttrs, ParagraphAttrs, SdtAttrs, ShapeAttrs, SuggestedStructuralMarker, SymbolAttrs, TabAttrs, TableAttrs, TableCellAttrs, TableRowAttrs, TextBoxAnchorAttrs, TextBoxAttrs };
703
+ export { BlockSdtAttrs, FieldAttrs, HardBreakAttrs, ImageAttrs, ImagePositionAttrs, MathAttrs, ParagraphAttrs, ParagraphPropertyChangeAttrs, SdtAttrs, ShapeAttrs, SuggestedStructuralMarker, SymbolAttrs, TabAttrs, TableAttrs, TableCellAttrs, TableRowAttrs, TextBoxAnchorAttrs, TextBoxAttrs };
@@ -2,9 +2,11 @@ import { document_d_exports } from "../types/document.js";
2
2
  import { EditorState } from "prosemirror-state";
3
3
  //#region src/prosemirror/selectionState.d.ts
4
4
  /**
5
- * Selection state for toolbar integration
5
+ * Canonical selection projection shared by toolbar and selection-change APIs.
6
+ * Adapter-specific context belongs on top of this snapshot rather than in a
7
+ * second formatting extractor.
6
8
  */
7
- type SelectionState = {
9
+ type SelectionSnapshot = {
8
10
  /** Whether there's an active selection (not just cursor) */
9
11
  hasSelection: boolean;
10
12
  /** Whether selection spans multiple paragraphs */
@@ -20,10 +22,14 @@ type SelectionState = {
20
22
  /** End paragraph index */
21
23
  endParagraphIndex: number;
22
24
  };
25
+ /** Compatibility name for the public toolbar selection contract. */
26
+ type SelectionState = SelectionSnapshot;
23
27
  /**
24
- * Extract selection state from editor state.
25
- * Used by PagedEditor integration in DocxEditor for toolbar state.
28
+ * Extract the canonical selection snapshot from editor state.
29
+ * Used by both the toolbar callback and the selection tracker plugin.
26
30
  */
31
+ declare function extractSelectionSnapshot(state: EditorState): SelectionSnapshot;
32
+ /** Compatibility entry point used by existing React and Vue integrations. */
27
33
  declare function extractSelectionState(state: EditorState): SelectionState | null;
28
34
  //#endregion
29
- export { SelectionState, extractSelectionState };
35
+ export { SelectionSnapshot, SelectionState, extractSelectionSnapshot, extractSelectionState };
@@ -1,100 +1,137 @@
1
+ import { FONT_THEME_VALUES } from "../types/documentEnumValues.js";
2
+ import { expectFontFamilyMarkAttrs, expectFontSizeMarkAttrs, expectHighlightMarkAttrs, expectParagraphAttrs, expectStrikeMarkAttrs, expectTextColorMarkAttrs, expectUnderlineMarkAttrs } from "./attrs/index.js";
1
3
  import { directionIsRtl } from "./paragraphDirection.js";
2
4
  import { collectMarksInRange } from "./selectionMarks.js";
3
5
  //#region src/prosemirror/selectionState.ts
6
+ const isFontTheme = (value) => value !== void 0 && FONT_THEME_VALUES.some((theme) => theme === value);
4
7
  /**
5
- * Extract selection state from editor state.
6
- * Used by PagedEditor integration in DocxEditor for toolbar state.
8
+ * Extract the canonical selection snapshot from editor state.
9
+ * Used by both the toolbar callback and the selection tracker plugin.
7
10
  */
8
- function extractSelectionState(state) {
11
+ function extractSelectionSnapshot(state) {
9
12
  const { selection, doc } = state;
10
13
  const { from, to, empty } = selection;
11
14
  const $from = doc.resolve(from);
15
+ const { startParagraphIndex, endParagraphIndex } = paragraphIndexes(doc, from, to);
16
+ const paragraph = $from.parent;
17
+ const paragraphAttrs = paragraph.type.name === "paragraph" ? expectParagraphAttrs(paragraph) : null;
18
+ const marks = selectionMarks(state);
19
+ let textFormatting = extractTextFormatting(marks);
20
+ if (paragraph.type.name === "paragraph" && paragraph.textContent.length === 0 && marks.length === 0 && paragraphAttrs?.defaultTextFormatting) textFormatting = { ...paragraphAttrs.defaultTextFormatting };
21
+ const paragraphFormatting = {};
22
+ let styleId = null;
23
+ if (paragraphAttrs) {
24
+ if (paragraphAttrs.alignment !== void 0) paragraphFormatting.alignment = paragraphAttrs.alignment;
25
+ if (paragraphAttrs.lineSpacing !== void 0) {
26
+ paragraphFormatting.lineSpacing = paragraphAttrs.lineSpacing;
27
+ if (paragraphAttrs.lineSpacingRule !== void 0) paragraphFormatting.lineSpacingRule = paragraphAttrs.lineSpacingRule;
28
+ }
29
+ if (paragraphAttrs.snapToGrid !== void 0) paragraphFormatting.snapToGrid = paragraphAttrs.snapToGrid;
30
+ if (paragraphAttrs.numPr !== void 0) paragraphFormatting.numPr = paragraphAttrs.numPr;
31
+ if (paragraphAttrs.indentLeft !== void 0) paragraphFormatting.indentLeft = paragraphAttrs.indentLeft;
32
+ if (paragraphAttrs.indentRight !== void 0) paragraphFormatting.indentRight = paragraphAttrs.indentRight;
33
+ if (paragraphAttrs.indentFirstLine !== void 0) paragraphFormatting.indentFirstLine = paragraphAttrs.indentFirstLine;
34
+ if (paragraphAttrs.hangingIndent !== void 0) paragraphFormatting.hangingIndent = paragraphAttrs.hangingIndent;
35
+ if (paragraphAttrs.tabs !== void 0) paragraphFormatting.tabs = paragraphAttrs.tabs;
36
+ if (directionIsRtl(paragraphAttrs.direction)) paragraphFormatting.bidi = true;
37
+ if (paragraphAttrs.styleId !== void 0) styleId = paragraphAttrs.styleId;
38
+ }
39
+ return {
40
+ hasSelection: !empty,
41
+ isMultiParagraph: startParagraphIndex !== endParagraphIndex,
42
+ textFormatting,
43
+ paragraphFormatting,
44
+ styleId,
45
+ startParagraphIndex,
46
+ endParagraphIndex
47
+ };
48
+ }
49
+ /** Compatibility entry point used by existing React and Vue integrations. */
50
+ function extractSelectionState(state) {
51
+ return extractSelectionSnapshot(state);
52
+ }
53
+ function paragraphIndexes(doc, from, to) {
12
54
  let startParagraphIndex = 0;
13
55
  let endParagraphIndex = 0;
14
56
  doc.forEach((_node, offset, index) => {
15
57
  if (offset <= from) startParagraphIndex = index;
16
58
  if (offset <= to) endParagraphIndex = index;
17
59
  });
18
- let textFormatting = {};
19
- const paragraph = $from.parent;
20
- const isEmptyParagraph = paragraph.type.name === "paragraph" && paragraph.textContent.length === 0;
21
- const paragraphDefaultFormatting = paragraph.attrs["defaultTextFormatting"];
22
- const marks = empty ? state.storedMarks || selection.$from.marks() : collectMarksInRange({
60
+ return {
61
+ startParagraphIndex,
62
+ endParagraphIndex
63
+ };
64
+ }
65
+ function selectionMarks(state) {
66
+ const { selection, doc } = state;
67
+ const { from, to, empty } = selection;
68
+ return empty ? state.storedMarks || selection.$from.marks() : collectMarksInRange({
23
69
  doc,
24
70
  from,
25
71
  to
26
72
  });
27
- if (isEmptyParagraph && marks.length === 0 && paragraphDefaultFormatting) textFormatting = { ...paragraphDefaultFormatting };
73
+ }
74
+ function extractTextFormatting(marks) {
75
+ const formatting = {};
28
76
  for (const mark of marks) switch (mark.type.name) {
29
77
  case "bold":
30
- textFormatting.bold = true;
78
+ formatting.bold = true;
31
79
  break;
32
80
  case "italic":
33
- textFormatting.italic = true;
81
+ formatting.italic = true;
34
82
  break;
35
- case "underline":
36
- if (mark.attrs["style"] !== "none") textFormatting.underline = {
37
- style: mark.attrs["style"] || "single",
38
- color: mark.attrs["color"]
83
+ case "underline": {
84
+ const attrs = expectUnderlineMarkAttrs(mark);
85
+ if (attrs.style !== "none") formatting.underline = {
86
+ style: attrs.style ?? "single",
87
+ ...attrs.color !== void 0 ? { color: attrs.color } : {}
39
88
  };
40
89
  break;
90
+ }
41
91
  case "strike":
42
- if (mark.attrs["double"]) textFormatting.doubleStrike = true;
43
- else textFormatting.strike = true;
92
+ if (expectStrikeMarkAttrs(mark).double) formatting.doubleStrike = true;
93
+ else formatting.strike = true;
44
94
  break;
45
- case "textColor":
46
- textFormatting.color = {
47
- rgb: mark.attrs["rgb"],
48
- themeColor: mark.attrs["themeColor"]
95
+ case "textColor": {
96
+ const attrs = expectTextColorMarkAttrs(mark);
97
+ formatting.color = {
98
+ ...attrs.rgb !== void 0 ? { rgb: attrs.rgb } : {},
99
+ ...attrs.themeColor !== void 0 ? { themeColor: attrs.themeColor } : {},
100
+ ...attrs.themeTint !== void 0 ? { themeTint: attrs.themeTint } : {},
101
+ ...attrs.themeShade !== void 0 ? { themeShade: attrs.themeShade } : {}
49
102
  };
50
103
  break;
104
+ }
51
105
  case "highlight":
52
- textFormatting.highlight = mark.attrs["color"];
106
+ formatting.highlight = expectHighlightMarkAttrs(mark).color;
53
107
  break;
54
108
  case "fontSize":
55
- textFormatting.fontSize = mark.attrs["size"];
109
+ formatting.fontSize = expectFontSizeMarkAttrs(mark).size;
56
110
  break;
57
- case "fontFamily":
58
- textFormatting.fontFamily = {
59
- ascii: mark.attrs["ascii"],
60
- hAnsi: mark.attrs["hAnsi"]
111
+ case "fontFamily": {
112
+ const attrs = expectFontFamilyMarkAttrs(mark);
113
+ formatting.fontFamily = {
114
+ ...attrs.ascii !== void 0 ? { ascii: attrs.ascii } : {},
115
+ ...attrs.hAnsi !== void 0 ? { hAnsi: attrs.hAnsi } : {},
116
+ ...attrs.eastAsia !== void 0 ? { eastAsia: attrs.eastAsia } : {},
117
+ ...attrs.cs !== void 0 ? { cs: attrs.cs } : {},
118
+ ...attrs.hint !== void 0 ? { hint: attrs.hint } : {},
119
+ ...isFontTheme(attrs.asciiTheme) ? { asciiTheme: attrs.asciiTheme } : {},
120
+ ...attrs.hAnsiTheme !== void 0 ? { hAnsiTheme: attrs.hAnsiTheme } : {},
121
+ ...attrs.eastAsiaTheme !== void 0 ? { eastAsiaTheme: attrs.eastAsiaTheme } : {},
122
+ ...attrs.csTheme !== void 0 ? { csTheme: attrs.csTheme } : {}
61
123
  };
62
124
  break;
125
+ }
63
126
  case "superscript":
64
- textFormatting.vertAlign = "superscript";
127
+ formatting.vertAlign = "superscript";
65
128
  break;
66
129
  case "subscript":
67
- textFormatting.vertAlign = "subscript";
130
+ formatting.vertAlign = "subscript";
68
131
  break;
69
132
  default: break;
70
133
  }
71
- const paragraphFormatting = {};
72
- let styleId = null;
73
- if (paragraph.type.name === "paragraph") {
74
- if (paragraph.attrs["alignment"]) paragraphFormatting.alignment = paragraph.attrs["alignment"];
75
- if (typeof paragraph.attrs["lineSpacing"] === "number") {
76
- paragraphFormatting.lineSpacing = paragraph.attrs["lineSpacing"];
77
- paragraphFormatting.lineSpacingRule = paragraph.attrs["lineSpacingRule"];
78
- }
79
- if (typeof paragraph.attrs["snapToGrid"] === "boolean") paragraphFormatting.snapToGrid = paragraph.attrs["snapToGrid"];
80
- if (paragraph.attrs["numPr"]) paragraphFormatting.numPr = paragraph.attrs["numPr"];
81
- if (paragraph.attrs["indentLeft"]) paragraphFormatting.indentLeft = paragraph.attrs["indentLeft"];
82
- if (paragraph.attrs["indentRight"]) paragraphFormatting.indentRight = paragraph.attrs["indentRight"];
83
- if (paragraph.attrs["indentFirstLine"]) paragraphFormatting.indentFirstLine = paragraph.attrs["indentFirstLine"];
84
- if (paragraph.attrs["hangingIndent"]) paragraphFormatting.hangingIndent = paragraph.attrs["hangingIndent"];
85
- if (paragraph.attrs["tabs"]) paragraphFormatting.tabs = paragraph.attrs["tabs"];
86
- if (directionIsRtl(paragraph.attrs["direction"])) paragraphFormatting.bidi = true;
87
- if (paragraph.attrs["styleId"]) styleId = paragraph.attrs["styleId"];
88
- }
89
- return {
90
- hasSelection: !empty,
91
- isMultiParagraph: startParagraphIndex !== endParagraphIndex,
92
- textFormatting,
93
- paragraphFormatting,
94
- styleId,
95
- startParagraphIndex,
96
- endParagraphIndex
97
- };
134
+ return formatting;
98
135
  }
99
136
  //#endregion
100
- export { extractSelectionState };
137
+ export { extractSelectionSnapshot, extractSelectionState };
@@ -1,5 +1,10 @@
1
1
  import { document_d_exports } from "../types/document.js";
2
2
  //#region src/utils/tableOperations.d.ts
3
+ type TablePropertiesCommand = {
4
+ width?: number | null;
5
+ widthType?: document_d_exports.TableWidthType | null;
6
+ justification?: "left" | "center" | "right" | null;
7
+ };
3
8
  type TableAction = "addRowAbove" | "addRowBelow" | "addColumnLeft" | "addColumnRight" | "deleteRow" | "deleteColumn" | "mergeCells" | "splitCell" | "deleteTable" | "selectTable" | "selectRow" | "selectColumn" | "borderAll" | "borderOutside" | "borderInside" | "borderNone" | "borderTop" | "borderBottom" | "borderLeft" | "borderRight" | {
4
9
  type: "cellFillColor";
5
10
  color: string | null;
@@ -43,11 +48,7 @@ type TableAction = "addRowAbove" | "addRowBelow" | "addColumnLeft" | "addColumnR
43
48
  type: "autoFitContents";
44
49
  } | {
45
50
  type: "tableProperties";
46
- props: {
47
- width?: number | null;
48
- widthType?: string | null;
49
- justification?: "left" | "center" | "right" | null;
50
- };
51
+ props: TablePropertiesCommand;
51
52
  } | {
52
53
  type: "openTableProperties";
53
54
  } | {
@@ -83,4 +84,4 @@ declare const deleteColumn: (table: document_d_exports.Table, columnIndex: numbe
83
84
  declare const mergeCells: (table: document_d_exports.Table, selection: TableSelection) => document_d_exports.Table;
84
85
  declare const splitCell: (table: document_d_exports.Table, rowIndex: number, columnIndex: number) => document_d_exports.Table;
85
86
  //#endregion
86
- export { TableAction, TableContext, TableSelection, addColumn, addRow, createTableContext, deleteColumn, deleteRow, getCellAt, getColumnCount, mergeCells, splitCell };
87
+ export { TableAction, TableContext, TablePropertiesCommand, TableSelection, addColumn, addRow, createTableContext, deleteColumn, deleteRow, getCellAt, getColumnCount, mergeCells, splitCell };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stll/folio-core",
3
- "version": "0.22.3",
3
+ "version": "0.23.0",
4
4
  "description": "Headless, framework-neutral core of folio: the OOXML (.docx) parser, document model, ProseMirror integration, and page-layout engine. No React.",
5
5
  "keywords": [
6
6
  "document-model",
@@ -113,13 +113,13 @@
113
113
  "perf": "bun scripts/profile-editor.ts"
114
114
  },
115
115
  "dependencies": {
116
- "@stll/docx-core": "^0.15.1",
116
+ "@stll/docx-core": "^0.15.2",
117
117
  "@stll/docx-utils": "^0.1.0",
118
118
  "@stll/template-conditions": "^0.1.0",
119
119
  "better-result": "3.0.1",
120
120
  "csstype": "^3.1.3",
121
121
  "dompurify": "^3.4.13",
122
- "fast-xml-parser": "^5.9.3",
122
+ "fast-xml-parser": "^5.10.1",
123
123
  "hyphen": "1.14.1",
124
124
  "jszip": "3.10.1",
125
125
  "marked": "^18.0.5",