@stll/folio-core 0.49.0 → 0.51.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.
- package/dist/ai-edits/headless.d.ts +37 -1
- package/dist/ai-edits/headless.js +47 -16
- package/dist/ai-edits/word-diff.js +134 -1
- package/dist/compare/content-alignment.js +220 -5
- package/dist/compare/content.d.ts +1 -0
- package/dist/compare/content.js +51 -20
- package/dist/display-list/dom/renderDisplayListToDom.d.ts +10 -1
- package/dist/display-list/dom/renderDisplayListToDom.js +8 -7
- package/dist/display-list/html/renderDisplayListToHtml.d.ts +21 -0
- package/dist/display-list/html/renderDisplayListToHtml.js +126 -0
- package/dist/display-list/selectDisplayPages.d.ts +6 -0
- package/dist/display-list/selectDisplayPages.js +44 -0
- package/dist/docx/selectiveSave.d.ts +1 -1
- package/dist/docx/selectiveSave.js +16 -6
- package/dist/docx/structuralXmlPatch.d.ts +14 -0
- package/dist/docx/structuralXmlPatch.js +209 -0
- package/dist/export-pdf.d.ts +35 -2
- package/dist/export-pdf.js +51 -20
- package/dist/fonts/fontsourceFaces.d.ts +47 -0
- package/dist/fonts/fontsourceFaces.js +230 -0
- package/dist/layout-bridge/convert/flowBlockPostprocessing.d.ts +8 -0
- package/dist/layout-bridge/convert/flowBlockPostprocessing.js +106 -0
- package/dist/layout-bridge/convert/flowBorders.d.ts +17 -0
- package/dist/layout-bridge/convert/flowBorders.js +31 -0
- package/dist/layout-bridge/convert/flowConversionShared.d.ts +111 -0
- package/dist/layout-bridge/convert/flowConversionShared.js +23 -0
- package/dist/layout-bridge/convert/footnoteLayout.d.ts +2 -1
- package/dist/layout-bridge/convert/footnoteLayout.js +2 -1
- package/dist/layout-bridge/convert/headerFooterLayout.d.ts +2 -1
- package/dist/layout-bridge/convert/imageConversion.d.ts +33 -0
- package/dist/layout-bridge/convert/imageConversion.js +118 -0
- package/dist/layout-bridge/convert/listMarkers.d.ts +28 -0
- package/dist/layout-bridge/convert/listMarkers.js +98 -0
- package/dist/layout-bridge/convert/noteReferences.d.ts +14 -0
- package/dist/layout-bridge/convert/noteReferences.js +29 -0
- package/dist/layout-bridge/convert/pageBreakSplitting.d.ts +16 -0
- package/dist/layout-bridge/convert/pageBreakSplitting.js +140 -0
- package/dist/layout-bridge/convert/paragraphAttrs.d.ts +20 -0
- package/dist/layout-bridge/convert/paragraphAttrs.js +234 -0
- package/dist/layout-bridge/convert/paragraphConversion.d.ts +12 -0
- package/dist/layout-bridge/convert/paragraphConversion.js +112 -0
- package/dist/layout-bridge/convert/paragraphRuns.d.ts +15 -0
- package/dist/layout-bridge/convert/paragraphRuns.js +281 -0
- package/dist/layout-bridge/convert/runFormattingMerge.d.ts +19 -0
- package/dist/layout-bridge/convert/runFormattingMerge.js +167 -0
- package/dist/layout-bridge/convert/runMarkFormatting.d.ts +11 -0
- package/dist/layout-bridge/convert/runMarkFormatting.js +291 -0
- package/dist/layout-bridge/convert/sectionBoundaries.d.ts +35 -0
- package/dist/layout-bridge/convert/sectionBoundaries.js +114 -0
- package/dist/layout-bridge/convert/tableConversion.d.ts +12 -0
- package/dist/layout-bridge/convert/tableConversion.js +303 -0
- package/dist/layout-bridge/convert/textBoxFill.d.ts +20 -0
- package/dist/layout-bridge/convert/textBoxFill.js +42 -0
- package/dist/layout-bridge/convert/textFormattingConversion.d.ts +22 -0
- package/dist/layout-bridge/convert/textFormattingConversion.js +120 -0
- package/dist/layout-bridge/convert/toFlowBlocks.d.ts +5 -113
- package/dist/layout-bridge/convert/toFlowBlocks.js +11 -2082
- package/dist/layout-bridge/engine/hitTest.js +1 -1
- package/dist/layout-bridge/engine/measuring/index.d.ts +2 -1
- package/dist/layout-bridge/engine/measuring/index.js +2 -1
- package/dist/layout-bridge/engine/selectionRects.js +1 -1
- package/dist/layout-engine/imageLayout.d.ts +9 -0
- package/dist/layout-engine/imageLayout.js +48 -0
- package/dist/layout-engine/index.d.ts +3 -18
- package/dist/layout-engine/index.js +12 -937
- package/dist/layout-engine/layoutFlowShared.d.ts +6 -0
- package/dist/layout-engine/layoutFlowShared.js +8 -0
- package/dist/layout-engine/measure/crossRunWords.d.ts +45 -0
- package/dist/layout-engine/measure/crossRunWords.js +172 -0
- package/dist/layout-engine/measure/floatingLineClearance.d.ts +24 -0
- package/dist/layout-engine/measure/floatingLineClearance.js +44 -0
- package/dist/layout-engine/measure/inlineObjectMetrics.d.ts +6 -0
- package/dist/layout-engine/measure/inlineObjectMetrics.js +52 -0
- package/dist/layout-engine/measure/justification.d.ts +32 -0
- package/dist/layout-engine/measure/justification.js +83 -0
- package/dist/layout-engine/measure/lineFitting.d.ts +48 -0
- package/dist/layout-engine/measure/lineFitting.js +145 -0
- package/dist/layout-engine/measure/lineTypography.d.ts +44 -0
- package/dist/layout-engine/measure/lineTypography.js +124 -0
- package/dist/layout-engine/measure/measureBlocks.js +2 -1
- package/dist/layout-engine/measure/measureParagraph.d.ts +1 -20
- package/dist/layout-engine/measure/measureParagraph.js +32 -734
- package/dist/layout-engine/measure/paragraphMeasureShared.d.ts +109 -0
- package/dist/layout-engine/measure/paragraphMeasureShared.js +82 -0
- package/dist/layout-engine/measure/paragraphTabs.d.ts +18 -0
- package/dist/layout-engine/measure/paragraphTabs.js +78 -0
- package/dist/layout-engine/paragraphLayout.d.ts +23 -0
- package/dist/layout-engine/paragraphLayout.js +204 -0
- package/dist/layout-engine/sectionLayout.d.ts +45 -0
- package/dist/layout-engine/sectionLayout.js +216 -0
- package/dist/layout-engine/tableLayout.d.ts +17 -0
- package/dist/layout-engine/tableLayout.js +369 -0
- package/dist/layout-engine/textBoxLayout.d.ts +15 -0
- package/dist/layout-engine/textBoxLayout.js +137 -0
- package/dist/layout-painter/renderPage.js +1 -0
- package/dist/managers/editorShortcuts.d.ts +16 -3
- package/dist/managers/editorShortcuts.js +6 -4
- package/dist/paged-layout/sectionBlockWidths.d.ts +2 -1
- package/dist/paged-layout/sectionBlockWidths.js +1 -1
- package/dist/prosemirror/extensions/StarterKit.d.ts +3 -0
- package/dist/prosemirror/extensions/StarterKit.js +2 -1
- package/dist/prosemirror/extensions/core/HistoryExtension.d.ts +9 -1
- package/dist/prosemirror/extensions/core/HistoryExtension.js +4 -3
- package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.d.ts +12 -5
- package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +91 -33
- package/dist/server.d.ts +3 -2
- package/package.json +2 -2
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { emuToPixels } from "../utils/units.js";
|
|
2
|
+
import { bandFragmentX, bandTopContentY, isPageFrameRelativeAnchor } from "./textBoxFlow.js";
|
|
3
|
+
import { floatingTextBoxReservesBand } from "./types.js";
|
|
4
|
+
//#region src/layout-engine/textBoxLayout.ts
|
|
5
|
+
/**
|
|
6
|
+
* Places text boxes onto pages: inline and anchored placement, page-pinned
|
|
7
|
+
* wrap bands, and host-paragraph line offsets.
|
|
8
|
+
*/
|
|
9
|
+
const TEXT_BOX_ANCHOR_BLOCK_ID = Symbol.for("stll.textBoxAnchorBlockId");
|
|
10
|
+
const readTextBoxAnchorBlockId = (block) => Reflect.get(block, TEXT_BOX_ANCHOR_BLOCK_ID);
|
|
11
|
+
/**
|
|
12
|
+
* Layout a text box block onto pages.
|
|
13
|
+
*/
|
|
14
|
+
function layoutTextBox(block, measure, { paginator, sectionMarginTop, sectionPageHeight, sectionMarginBottom }) {
|
|
15
|
+
if (isPagePinnedBandTextBox(block)) {
|
|
16
|
+
const state = paginator.getCurrentState();
|
|
17
|
+
const bandTop = bandTopContentY(block.position?.vertical, {
|
|
18
|
+
pageHeight: sectionPageHeight,
|
|
19
|
+
marginTop: sectionMarginTop,
|
|
20
|
+
marginBottom: sectionMarginBottom,
|
|
21
|
+
boxHeight: measure.height
|
|
22
|
+
});
|
|
23
|
+
const x = bandFragmentX(block.position?.horizontal, {
|
|
24
|
+
pageWidth: state.page.size.w,
|
|
25
|
+
marginLeft: state.page.margins.left,
|
|
26
|
+
marginRight: state.page.margins.right,
|
|
27
|
+
activeColumnLeft: paginator.getColumnX(state.columnIndex),
|
|
28
|
+
activeColumnWidth: paginator.columnWidth,
|
|
29
|
+
boxWidth: measure.width
|
|
30
|
+
});
|
|
31
|
+
const fragment = {
|
|
32
|
+
kind: "textBox",
|
|
33
|
+
blockId: block.id,
|
|
34
|
+
x,
|
|
35
|
+
y: block.position?.vertical?.relativeTo === "page" ? sectionMarginTop + bandTop : state.topMargin + bandTop,
|
|
36
|
+
width: measure.width,
|
|
37
|
+
height: measure.height,
|
|
38
|
+
isPositioned: true,
|
|
39
|
+
...block.pmStart !== void 0 ? { pmStart: block.pmStart } : {},
|
|
40
|
+
...block.pmEnd !== void 0 ? { pmEnd: block.pmEnd } : {}
|
|
41
|
+
};
|
|
42
|
+
paginator.addUnflowedFragment(fragment);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (block.position !== void 0) {
|
|
46
|
+
const state = paginator.getCurrentState();
|
|
47
|
+
const x = bandFragmentX(block.position.horizontal, {
|
|
48
|
+
pageWidth: state.page.size.w,
|
|
49
|
+
marginLeft: state.page.margins.left,
|
|
50
|
+
marginRight: state.page.margins.right,
|
|
51
|
+
activeColumnLeft: paginator.getColumnX(state.columnIndex),
|
|
52
|
+
activeColumnWidth: paginator.columnWidth,
|
|
53
|
+
boxWidth: measure.width
|
|
54
|
+
});
|
|
55
|
+
const vertical = block.position.vertical;
|
|
56
|
+
const anchorBlockId = readTextBoxAnchorBlockId(block);
|
|
57
|
+
const anchorParagraph = vertical?.relativeTo === "paragraph" && typeof anchorBlockId === "string" ? state.page.fragments.find((fragment) => fragment.kind === "paragraph" && fragment.blockId === anchorBlockId) : void 0;
|
|
58
|
+
const bandGeometry = {
|
|
59
|
+
pageHeight: sectionPageHeight,
|
|
60
|
+
marginTop: sectionMarginTop,
|
|
61
|
+
marginBottom: sectionMarginBottom,
|
|
62
|
+
boxHeight: measure.height
|
|
63
|
+
};
|
|
64
|
+
let y;
|
|
65
|
+
if (vertical?.relativeTo === "page") y = sectionMarginTop + bandTopContentY(vertical, bandGeometry);
|
|
66
|
+
else if (isPageFrameRelativeAnchor(vertical?.relativeTo)) y = state.topMargin + bandTopContentY(vertical, {
|
|
67
|
+
pageHeight: sectionPageHeight,
|
|
68
|
+
marginTop: sectionMarginTop,
|
|
69
|
+
marginBottom: sectionMarginBottom,
|
|
70
|
+
boxHeight: measure.height
|
|
71
|
+
});
|
|
72
|
+
else y = (anchorParagraph?.y ?? state.cursorY) + emuToPixels(vertical?.posOffset ?? 0);
|
|
73
|
+
const fragment = {
|
|
74
|
+
kind: "textBox",
|
|
75
|
+
blockId: block.id,
|
|
76
|
+
x,
|
|
77
|
+
y,
|
|
78
|
+
width: measure.width,
|
|
79
|
+
height: measure.height,
|
|
80
|
+
isPositioned: true,
|
|
81
|
+
...block.pmStart !== void 0 ? { pmStart: block.pmStart } : {},
|
|
82
|
+
...block.pmEnd !== void 0 ? { pmEnd: block.pmEnd } : {}
|
|
83
|
+
};
|
|
84
|
+
paginator.addUnflowedFragment(fragment);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const effect = block.effectExtent;
|
|
88
|
+
const occupiedHeight = measure.height + (effect?.top ?? 0) + (effect?.bottom ?? 0);
|
|
89
|
+
const occupiedWidth = measure.width + (effect?.left ?? 0) + (effect?.right ?? 0);
|
|
90
|
+
const host = block.hostParagraph;
|
|
91
|
+
const fragment = {
|
|
92
|
+
kind: "textBox",
|
|
93
|
+
blockId: block.id,
|
|
94
|
+
x: 0,
|
|
95
|
+
y: 0,
|
|
96
|
+
width: measure.width,
|
|
97
|
+
height: measure.height,
|
|
98
|
+
...block.pmStart !== void 0 ? { pmStart: block.pmStart } : {},
|
|
99
|
+
...block.pmEnd !== void 0 ? { pmEnd: block.pmEnd } : {}
|
|
100
|
+
};
|
|
101
|
+
const result = paginator.addFragment(fragment, occupiedHeight, host?.spacing?.before ?? 0, host?.spacing?.after ?? 0);
|
|
102
|
+
fragment.x = result.x + hostParagraphLineOffset(host, paginator.columnWidth, occupiedWidth) + (effect?.left ?? 0);
|
|
103
|
+
fragment.y = result.y + (effect?.top ?? 0);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Where a line holding only an object of `width` starts within a column:
|
|
107
|
+
* after the host paragraph's start and first-line indents, then aligned in
|
|
108
|
+
* what remains before its end indent.
|
|
109
|
+
*/
|
|
110
|
+
function hostParagraphLineOffset(host, columnWidth, width) {
|
|
111
|
+
if (!host) return 0;
|
|
112
|
+
const indent = host.indent;
|
|
113
|
+
const start = (indent?.left ?? 0) + (indent?.firstLine ?? 0) - (indent?.hanging ?? 0);
|
|
114
|
+
const slack = Math.max(0, columnWidth - start - (indent?.right ?? 0) - width);
|
|
115
|
+
switch (host.alignment) {
|
|
116
|
+
case "center": return start + slack / 2;
|
|
117
|
+
case "right": return start + slack;
|
|
118
|
+
case "left":
|
|
119
|
+
case "justify":
|
|
120
|
+
case void 0: return start;
|
|
121
|
+
default:
|
|
122
|
+
host.alignment;
|
|
123
|
+
return start;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* A topAndBottom text box whose vertical anchor pins it to the page frame
|
|
128
|
+
* (page/margin/margin-strip) — it floats to a fixed page position rather than
|
|
129
|
+
* flowing in document order. Must agree with the measure pass's band extraction
|
|
130
|
+
* (extractFloatingZones), which uses the same predicate. eigenpal #694.
|
|
131
|
+
*/
|
|
132
|
+
function isPagePinnedBandTextBox(block) {
|
|
133
|
+
if (!floatingTextBoxReservesBand(block)) return false;
|
|
134
|
+
return isPageFrameRelativeAnchor(block.position?.vertical?.relativeTo);
|
|
135
|
+
}
|
|
136
|
+
//#endregion
|
|
137
|
+
export { layoutTextBox };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import "../layout-engine/measure/floatingLineClearance.js";
|
|
1
2
|
import { resolveFloatingTableX } from "../layout-engine/measure/floatingTablePosition.js";
|
|
2
3
|
import { rectsToFloatingZones } from "../layout-engine/measure/floatingZones.js";
|
|
3
4
|
import { DEFAULT_FONT_FAMILY } from "../layout-engine/measure/measureHelpers.js";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { HistoryShortcutOwner } from "../prosemirror/extensions/core/HistoryExtension.js";
|
|
1
2
|
import { EditorState, Transaction } from "prosemirror-state";
|
|
2
3
|
//#region src/managers/editorShortcuts.d.ts
|
|
3
4
|
declare function isMacPlatform(): boolean;
|
|
@@ -17,19 +18,31 @@ type EditorKeydownIntent = {
|
|
|
17
18
|
} | {
|
|
18
19
|
type: "none";
|
|
19
20
|
};
|
|
21
|
+
/**
|
|
22
|
+
* An editor shortcut the host binds itself. The editor leaves its keys alone,
|
|
23
|
+
* so the host's binding (an IDE's undo stack, its own print command) is the
|
|
24
|
+
* only one that runs.
|
|
25
|
+
* - `"history"`: undo and redo (Mod-z, Mod-y, Mod-Shift-z).
|
|
26
|
+
* - `"print"`: Cmd/Ctrl+P.
|
|
27
|
+
*/
|
|
28
|
+
type HostShortcut = "history" | "print";
|
|
29
|
+
/** Who answers the undo and redo keys when the host owns `hostShortcuts`. */
|
|
30
|
+
declare const historyShortcutOwner: (hostShortcuts: readonly HostShortcut[]) => HistoryShortcutOwner;
|
|
20
31
|
type ClassifyEditorKeydownOptions = {
|
|
21
32
|
/** Whether the platform uses Cmd (Mac) rather than Ctrl as the primary modifier. */
|
|
22
33
|
isMac: boolean;
|
|
23
34
|
/** Whether focus is in a non-editor input/textarea/contenteditable. */
|
|
24
35
|
isInputLike: boolean;
|
|
36
|
+
/** Shortcuts the host binds itself; their keys map to `none`. */
|
|
37
|
+
hostShortcuts: readonly HostShortcut[];
|
|
25
38
|
};
|
|
26
39
|
/**
|
|
27
40
|
* Map a keydown to an editor intent:
|
|
28
41
|
* - Cmd/Ctrl+F or Cmd/Ctrl+H → open find
|
|
29
|
-
* - Cmd/Ctrl+P (no auto-repeat) → custom print
|
|
42
|
+
* - Cmd/Ctrl+P (no auto-repeat) → custom print, unless the host owns print
|
|
30
43
|
* - Delete/Backspace (no modifiers, focus not in an input) → delete selected table
|
|
31
44
|
*/
|
|
32
|
-
declare function classifyEditorKeydown(e: KeyboardEvent, { isMac, isInputLike }: ClassifyEditorKeydownOptions): EditorKeydownIntent;
|
|
45
|
+
declare function classifyEditorKeydown(e: Pick<KeyboardEvent, "key" | "metaKey" | "ctrlKey" | "shiftKey" | "altKey" | "repeat">, { isMac, isInputLike, hostShortcuts }: ClassifyEditorKeydownOptions): EditorKeydownIntent;
|
|
33
46
|
/**
|
|
34
47
|
* Which key presses the editor's page-level shortcuts answer.
|
|
35
48
|
* - `"document"`: every press on the page.
|
|
@@ -69,4 +82,4 @@ declare function isKeydownInShortcutScope(event: {
|
|
|
69
82
|
*/
|
|
70
83
|
declare function deleteSelectedTable(state: EditorState, dispatch: (tr: Transaction) => void): boolean;
|
|
71
84
|
//#endregion
|
|
72
|
-
export { ClassifyEditorKeydownOptions, EditorKeydownIntent, IsKeydownInShortcutScopeOptions, KeyboardShortcutScope, ShortcutScopeRoot, classifyEditorKeydown, deleteSelectedTable, isFocusInInputLike, isKeydownInShortcutScope, isMacPlatform };
|
|
85
|
+
export { ClassifyEditorKeydownOptions, EditorKeydownIntent, HostShortcut, IsKeydownInShortcutScopeOptions, KeyboardShortcutScope, ShortcutScopeRoot, classifyEditorKeydown, deleteSelectedTable, historyShortcutOwner, isFocusInInputLike, isKeydownInShortcutScope, isMacPlatform };
|
|
@@ -16,19 +16,21 @@ function isFocusInInputLike(target, editorDom) {
|
|
|
16
16
|
if (target.isContentEditable && target !== editorDom) return true;
|
|
17
17
|
return false;
|
|
18
18
|
}
|
|
19
|
+
/** Who answers the undo and redo keys when the host owns `hostShortcuts`. */
|
|
20
|
+
const historyShortcutOwner = (hostShortcuts) => hostShortcuts.includes("history") ? "host" : "editor";
|
|
19
21
|
/**
|
|
20
22
|
* Map a keydown to an editor intent:
|
|
21
23
|
* - Cmd/Ctrl+F or Cmd/Ctrl+H → open find
|
|
22
|
-
* - Cmd/Ctrl+P (no auto-repeat) → custom print
|
|
24
|
+
* - Cmd/Ctrl+P (no auto-repeat) → custom print, unless the host owns print
|
|
23
25
|
* - Delete/Backspace (no modifiers, focus not in an input) → delete selected table
|
|
24
26
|
*/
|
|
25
|
-
function classifyEditorKeydown(e, { isMac, isInputLike }) {
|
|
27
|
+
function classifyEditorKeydown(e, { isMac, isInputLike, hostShortcuts }) {
|
|
26
28
|
const cmdOrCtrl = isMac ? e.metaKey : e.ctrlKey;
|
|
27
29
|
if (!cmdOrCtrl && !e.shiftKey && !e.altKey && (e.key === "Delete" || e.key === "Backspace") && !isInputLike) return { type: "deleteSelectedTable" };
|
|
28
30
|
if (cmdOrCtrl && !e.shiftKey && !e.altKey) {
|
|
29
31
|
const key = e.key.toLowerCase();
|
|
30
32
|
if (key === "f" || key === "h") return { type: "openFind" };
|
|
31
|
-
if (key === "p" && !e.repeat) return { type: "print" };
|
|
33
|
+
if (key === "p" && !e.repeat && !hostShortcuts.includes("print")) return { type: "print" };
|
|
32
34
|
}
|
|
33
35
|
return { type: "none" };
|
|
34
36
|
}
|
|
@@ -76,4 +78,4 @@ function deleteSelectedTable(state, dispatch) {
|
|
|
76
78
|
return false;
|
|
77
79
|
}
|
|
78
80
|
//#endregion
|
|
79
|
-
export { classifyEditorKeydown, deleteSelectedTable, isFocusInInputLike, isKeydownInShortcutScope, isMacPlatform };
|
|
81
|
+
export { classifyEditorKeydown, deleteSelectedTable, historyShortcutOwner, isFocusInInputLike, isKeydownInShortcutScope, isMacPlatform };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { FlowBlock } from "../layout-engine/types.js";
|
|
2
2
|
import { SectionStartAdvance } from "../layout-engine/section-breaks.js";
|
|
3
|
-
import { SectionLayoutConfig } from "../layout-engine/
|
|
3
|
+
import { SectionLayoutConfig } from "../layout-engine/sectionLayout.js";
|
|
4
|
+
import "../layout-engine/index.js";
|
|
4
5
|
//#region src/paged-layout/sectionBlockWidths.d.ts
|
|
5
6
|
type ComputePerBlockMeasureInput = {
|
|
6
7
|
blocks: FlowBlock[];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { collectSectionConfigs } from "../layout-engine/index.js";
|
|
2
1
|
import { hasPageBreakBefore } from "../layout-engine/keep-together.js";
|
|
3
2
|
import { calculateColumnLefts, calculateColumnWidths } from "../layout-engine/paginator.js";
|
|
4
3
|
import { physicalColumnRegionIsShared, sectionStartAdvanceOf } from "../layout-engine/section-breaks.js";
|
|
4
|
+
import { collectSectionConfigs } from "../layout-engine/sectionLayout.js";
|
|
5
5
|
//#region src/paged-layout/sectionBlockWidths.ts
|
|
6
6
|
function resolvePreBlockPageTransition(block, config) {
|
|
7
7
|
if (!hasPageBreakBefore(block)) return { type: "none" };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AnyExtension } from "./types.js";
|
|
2
|
+
import { HistoryShortcutOwner } from "./core/HistoryExtension.js";
|
|
2
3
|
import { SelectionChangeCallback } from "../plugins/selectionTracker.js";
|
|
3
4
|
//#region src/prosemirror/extensions/StarterKit.d.ts
|
|
4
5
|
type StarterKitOptions = {
|
|
@@ -8,6 +9,8 @@ type StarterKitOptions = {
|
|
|
8
9
|
historyDepth?: number;
|
|
9
10
|
/** History new group delay (default: 500) */
|
|
10
11
|
historyNewGroupDelay?: number;
|
|
12
|
+
/** Who answers the undo and redo keys (default: `"editor"`). */
|
|
13
|
+
historyShortcuts?: HistoryShortcutOwner;
|
|
11
14
|
/** Selection change callback */
|
|
12
15
|
onSelectionChange?: SelectionChangeCallback;
|
|
13
16
|
};
|
|
@@ -61,7 +61,8 @@ function createStarterKit(options = {}) {
|
|
|
61
61
|
add("paragraph", ParagraphExtension());
|
|
62
62
|
add("history", HistoryExtension({
|
|
63
63
|
...options.historyDepth !== void 0 ? { depth: options.historyDepth } : {},
|
|
64
|
-
...options.historyNewGroupDelay !== void 0 ? { newGroupDelay: options.historyNewGroupDelay } : {}
|
|
64
|
+
...options.historyNewGroupDelay !== void 0 ? { newGroupDelay: options.historyNewGroupDelay } : {},
|
|
65
|
+
...options.historyShortcuts !== void 0 ? { shortcuts: options.historyShortcuts } : {}
|
|
65
66
|
}));
|
|
66
67
|
for (const name of MARK_NESTING_ORDER) add(name, MARK_EXTENSIONS[name]());
|
|
67
68
|
add("bookmarkBoundary", BookmarkBoundaryExtension({ getInternalClipboardToken }));
|
|
@@ -3,10 +3,18 @@ import { Extension } from "../types.js";
|
|
|
3
3
|
/**
|
|
4
4
|
* History Extension — undo/redo via prosemirror-history
|
|
5
5
|
*/
|
|
6
|
+
/**
|
|
7
|
+
* Who answers the undo and redo keys (Mod-z, Mod-y, Mod-Shift-z).
|
|
8
|
+
* - `"editor"`: the extension binds them. Default.
|
|
9
|
+
* - `"host"`: nothing binds them. The host keeps its own undo stack and runs
|
|
10
|
+
* the `undo` / `redo` commands itself, so one press never undoes twice.
|
|
11
|
+
*/
|
|
12
|
+
type HistoryShortcutOwner = "editor" | "host";
|
|
6
13
|
type HistoryOptions = {
|
|
7
14
|
depth: number;
|
|
8
15
|
newGroupDelay: number;
|
|
16
|
+
shortcuts: HistoryShortcutOwner;
|
|
9
17
|
};
|
|
10
18
|
declare const HistoryExtension: (options?: Partial<HistoryOptions> | undefined) => Extension;
|
|
11
19
|
//#endregion
|
|
12
|
-
export { HistoryExtension };
|
|
20
|
+
export { HistoryExtension, HistoryShortcutOwner };
|
|
@@ -4,7 +4,8 @@ const HistoryExtension = createExtension({
|
|
|
4
4
|
name: "history",
|
|
5
5
|
defaultOptions: {
|
|
6
6
|
depth: 100,
|
|
7
|
-
newGroupDelay: 500
|
|
7
|
+
newGroupDelay: 500,
|
|
8
|
+
shortcuts: "editor"
|
|
8
9
|
},
|
|
9
10
|
onSchemaReady(_ctx, options) {
|
|
10
11
|
return {
|
|
@@ -16,11 +17,11 @@ const HistoryExtension = createExtension({
|
|
|
16
17
|
undo: () => undo,
|
|
17
18
|
redo: () => redo
|
|
18
19
|
},
|
|
19
|
-
keyboardShortcuts: {
|
|
20
|
+
...options.shortcuts === "editor" && { keyboardShortcuts: {
|
|
20
21
|
"Mod-z": undo,
|
|
21
22
|
"Mod-y": redo,
|
|
22
23
|
"Mod-Shift-z": redo
|
|
23
|
-
}
|
|
24
|
+
} }
|
|
24
25
|
};
|
|
25
26
|
}
|
|
26
27
|
});
|
|
@@ -3,15 +3,15 @@ import { RemovedSectionReference, TrackedSectionEndpointRemoval } from "../../..
|
|
|
3
3
|
import { EditorState, PluginKey, Transaction } from "prosemirror-state";
|
|
4
4
|
import { Node } from "prosemirror-model";
|
|
5
5
|
//#region src/prosemirror/extensions/features/ParagraphChangeTrackerExtension.d.ts
|
|
6
|
-
declare const paragraphChangeTrackerKey: PluginKey<
|
|
6
|
+
declare const paragraphChangeTrackerKey: PluginKey<InternalParagraphChangeTrackerState>;
|
|
7
7
|
type ParagraphChangeTrackerState = {
|
|
8
8
|
/** Set of paraIds that were modified since last clear */
|
|
9
9
|
changedParaIds: Set<string>;
|
|
10
|
-
/** Whether
|
|
10
|
+
/** Whether paragraph order, membership, or block structure changed. */
|
|
11
11
|
structuralChange: boolean;
|
|
12
|
-
/** Whether
|
|
12
|
+
/** Whether edited paragraphs still lack IDs or source identity was unavailable. */
|
|
13
13
|
hasUntrackedChanges: boolean;
|
|
14
|
-
/**
|
|
14
|
+
/** Paragraph count in the current tracked document. */
|
|
15
15
|
paragraphCount: number;
|
|
16
16
|
/** Cached section-endpoint count used to invalidate stale save authorization. */
|
|
17
17
|
sectionEndpointCount: number;
|
|
@@ -20,6 +20,13 @@ type ParagraphChangeTrackerState = {
|
|
|
20
20
|
/** Exact tracked-resolution transition that may reduce the saved section count. */
|
|
21
21
|
sectionEndpointRemoval: TrackedSectionEndpointRemoval | null;
|
|
22
22
|
};
|
|
23
|
+
type InternalParagraphChangeTrackerState = ParagraphChangeTrackerState & {
|
|
24
|
+
/** Edited paragraph positions retained across allocator-only transactions. */
|
|
25
|
+
affectedParagraphPositions: Set<number>;
|
|
26
|
+
/** Editing an already unidentified source paragraph cannot become selective. */
|
|
27
|
+
hasUntrackedSourceChanges: boolean;
|
|
28
|
+
blockStructureFingerprint: string;
|
|
29
|
+
};
|
|
23
30
|
/**
|
|
24
31
|
* Get the change tracker state from an EditorState
|
|
25
32
|
*/
|
|
@@ -29,7 +36,7 @@ declare function getChangeTrackerState(state: EditorState): ParagraphChangeTrack
|
|
|
29
36
|
*/
|
|
30
37
|
declare function getChangedParagraphIds(state: EditorState): Set<string>;
|
|
31
38
|
/**
|
|
32
|
-
* Check if
|
|
39
|
+
* Check if paragraph membership, order, or block structure changed
|
|
33
40
|
*/
|
|
34
41
|
declare function hasStructuralChanges(state: EditorState): boolean;
|
|
35
42
|
/**
|
|
@@ -18,9 +18,20 @@ const isTrackedSectionEndpointRemovalMeta = (value) => typeof value === "object"
|
|
|
18
18
|
function countDocumentStructure(doc) {
|
|
19
19
|
let paragraphs = 0;
|
|
20
20
|
let sectionEndpoints = 0;
|
|
21
|
+
const blockRecords = [];
|
|
21
22
|
const endpointRecords = [];
|
|
22
23
|
const visit = (parent, parentPath) => {
|
|
23
24
|
parent.forEach((node, _offset, index) => {
|
|
25
|
+
const path = parentPath.length === 0 ? `${index}` : `${parentPath}.${index}`;
|
|
26
|
+
blockRecords.push(node.type.name === "paragraph" ? [
|
|
27
|
+
path,
|
|
28
|
+
node.type.name,
|
|
29
|
+
node.attrs["paraId"] ?? null
|
|
30
|
+
] : [
|
|
31
|
+
path,
|
|
32
|
+
node.type.name,
|
|
33
|
+
canonicalJson(node.attrs)
|
|
34
|
+
]);
|
|
24
35
|
if (node.type.name === "paragraph") {
|
|
25
36
|
paragraphs++;
|
|
26
37
|
const sectionProperties = sectionPropertiesOf(node);
|
|
@@ -34,34 +45,36 @@ function countDocumentStructure(doc) {
|
|
|
34
45
|
}
|
|
35
46
|
return;
|
|
36
47
|
}
|
|
37
|
-
if (node.childCount > 0)
|
|
38
|
-
const path = parentPath.length === 0 ? `${index}` : `${parentPath}.${index}`;
|
|
39
|
-
visit(node, path);
|
|
40
|
-
}
|
|
48
|
+
if (node.childCount > 0) visit(node, path);
|
|
41
49
|
});
|
|
42
50
|
};
|
|
43
51
|
visit(doc, "");
|
|
44
52
|
return {
|
|
53
|
+
blockStructureFingerprint: JSON.stringify(blockRecords),
|
|
45
54
|
paragraphs,
|
|
46
55
|
sectionEndpoints,
|
|
47
56
|
sectionEndpointFingerprint: canonicalJson(endpointRecords)
|
|
48
57
|
};
|
|
49
58
|
}
|
|
59
|
+
const isUsableParaId = (value) => typeof value === "string" && value.length > 0 && value !== "00000000";
|
|
50
60
|
/**
|
|
51
61
|
* Collect paraIds of all paragraphs that overlap with the given range
|
|
52
62
|
*/
|
|
53
63
|
function collectAffectedParaIds(doc, from, to) {
|
|
54
64
|
const ids = /* @__PURE__ */ new Set();
|
|
65
|
+
const positions = /* @__PURE__ */ new Set();
|
|
55
66
|
let hasUntracked = false;
|
|
56
|
-
doc.nodesBetween(from, to, (node) => {
|
|
67
|
+
doc.nodesBetween(from, to, (node, pos) => {
|
|
57
68
|
if (node.type.name === "paragraph") {
|
|
69
|
+
positions.add(pos);
|
|
58
70
|
const paraId = node.attrs["paraId"];
|
|
59
|
-
if (paraId) ids.add(paraId);
|
|
71
|
+
if (isUsableParaId(paraId)) ids.add(paraId);
|
|
60
72
|
else hasUntracked = true;
|
|
61
73
|
}
|
|
62
74
|
});
|
|
63
75
|
return {
|
|
64
76
|
ids,
|
|
77
|
+
positions,
|
|
65
78
|
hasUntracked
|
|
66
79
|
};
|
|
67
80
|
}
|
|
@@ -75,28 +88,40 @@ function collectAffectedParaIdsFromMarkLikeStep(doc, from, to) {
|
|
|
75
88
|
const hi = Math.max(from, to);
|
|
76
89
|
const primary = collectAffectedParaIds(doc, lo, hi > lo ? hi : lo + 1);
|
|
77
90
|
if (primary.ids.size > 0 || primary.hasUntracked) return primary;
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
return {
|
|
89
|
-
ids: /* @__PURE__ */ new Set(),
|
|
90
|
-
hasUntracked: true
|
|
91
|
-
};
|
|
92
|
-
}
|
|
91
|
+
const $p = doc.resolve(lo);
|
|
92
|
+
for (let depth = $p.depth; depth > 0; depth--) {
|
|
93
|
+
const node = $p.node(depth);
|
|
94
|
+
if (node.type.name === "paragraph") {
|
|
95
|
+
const paraId = node.attrs["paraId"];
|
|
96
|
+
return {
|
|
97
|
+
ids: new Set(isUsableParaId(paraId) ? [paraId] : []),
|
|
98
|
+
positions: /* @__PURE__ */ new Set([$p.before(depth)]),
|
|
99
|
+
hasUntracked: !isUsableParaId(paraId)
|
|
100
|
+
};
|
|
93
101
|
}
|
|
94
|
-
}
|
|
102
|
+
}
|
|
95
103
|
return {
|
|
96
104
|
ids: /* @__PURE__ */ new Set(),
|
|
105
|
+
positions: /* @__PURE__ */ new Set(),
|
|
97
106
|
hasUntracked: false
|
|
98
107
|
};
|
|
99
108
|
}
|
|
109
|
+
/** Map an edited paragraph's interior so markup replacement retains its ownership. */
|
|
110
|
+
const mapParagraphPosition = (tr, position) => {
|
|
111
|
+
const mapped = tr.mapping.mapResult(position + 1, 1);
|
|
112
|
+
if (mapped.deletedAcross) return null;
|
|
113
|
+
const $position = tr.doc.resolve(mapped.pos);
|
|
114
|
+
for (let depth = $position.depth; depth > 0; depth--) if ($position.node(depth).type.name === "paragraph") return $position.before(depth);
|
|
115
|
+
return null;
|
|
116
|
+
};
|
|
117
|
+
const mapAffectedParagraphPositions = (tr, positions) => {
|
|
118
|
+
const mappedPositions = /* @__PURE__ */ new Set();
|
|
119
|
+
for (const position of positions) {
|
|
120
|
+
const mapped = mapParagraphPosition(tr, position);
|
|
121
|
+
if (mapped !== null) mappedPositions.add(mapped);
|
|
122
|
+
}
|
|
123
|
+
return mappedPositions;
|
|
124
|
+
};
|
|
100
125
|
function mapStepPosition(remap, pos, assoc) {
|
|
101
126
|
return remap.map(pos, assoc);
|
|
102
127
|
}
|
|
@@ -107,6 +132,9 @@ function createParagraphChangeTrackerPlugin() {
|
|
|
107
132
|
init(_config, state) {
|
|
108
133
|
const counts = countDocumentStructure(state.doc);
|
|
109
134
|
return {
|
|
135
|
+
affectedParagraphPositions: /* @__PURE__ */ new Set(),
|
|
136
|
+
hasUntrackedSourceChanges: false,
|
|
137
|
+
blockStructureFingerprint: counts.blockStructureFingerprint,
|
|
110
138
|
changedParaIds: /* @__PURE__ */ new Set(),
|
|
111
139
|
structuralChange: false,
|
|
112
140
|
hasUntrackedChanges: false,
|
|
@@ -123,6 +151,9 @@ function createParagraphChangeTrackerPlugin() {
|
|
|
123
151
|
if (meta === CLEAR_META) {
|
|
124
152
|
const counts = countDocumentStructure(tr.doc);
|
|
125
153
|
return {
|
|
154
|
+
affectedParagraphPositions: /* @__PURE__ */ new Set(),
|
|
155
|
+
hasUntrackedSourceChanges: false,
|
|
156
|
+
blockStructureFingerprint: counts.blockStructureFingerprint,
|
|
126
157
|
changedParaIds: /* @__PURE__ */ new Set(),
|
|
127
158
|
structuralChange: false,
|
|
128
159
|
hasUntrackedChanges: false,
|
|
@@ -133,13 +164,22 @@ function createParagraphChangeTrackerPlugin() {
|
|
|
133
164
|
};
|
|
134
165
|
}
|
|
135
166
|
if (meta === IGNORE_META) {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
167
|
+
if (!tr.docChanged) return prevState;
|
|
168
|
+
const counts = countDocumentStructure(tr.doc);
|
|
169
|
+
const affectedParagraphPositions = mapAffectedParagraphPositions(tr, prevState.affectedParagraphPositions);
|
|
170
|
+
const changedParaIds = /* @__PURE__ */ new Set();
|
|
171
|
+
let unresolvedChanges = prevState.hasUntrackedSourceChanges;
|
|
172
|
+
for (const position of affectedParagraphPositions) {
|
|
173
|
+
const paraId = tr.doc.nodeAt(position)?.attrs["paraId"];
|
|
174
|
+
if (isUsableParaId(paraId)) changedParaIds.add(paraId);
|
|
175
|
+
else unresolvedChanges = true;
|
|
176
|
+
}
|
|
141
177
|
return {
|
|
142
178
|
...prevState,
|
|
179
|
+
affectedParagraphPositions,
|
|
180
|
+
changedParaIds,
|
|
181
|
+
hasUntrackedChanges: unresolvedChanges,
|
|
182
|
+
blockStructureFingerprint: counts.blockStructureFingerprint,
|
|
143
183
|
paragraphCount: counts.paragraphs,
|
|
144
184
|
sectionEndpointCount: counts.sectionEndpoints,
|
|
145
185
|
sectionEndpointRemoval: counts.sectionEndpointFingerprint === prevState.sectionEndpointFingerprint ? prevState.sectionEndpointRemoval : null,
|
|
@@ -162,6 +202,9 @@ function createParagraphChangeTrackerPlugin() {
|
|
|
162
202
|
};
|
|
163
203
|
} else sectionEndpointRemoval = null;
|
|
164
204
|
const newState = {
|
|
205
|
+
affectedParagraphPositions: mapAffectedParagraphPositions(tr, prevState.affectedParagraphPositions),
|
|
206
|
+
hasUntrackedSourceChanges: prevState.hasUntrackedSourceChanges,
|
|
207
|
+
blockStructureFingerprint: counts.blockStructureFingerprint,
|
|
165
208
|
changedParaIds: new Set(prevState.changedParaIds),
|
|
166
209
|
structuralChange: prevState.structuralChange || meta === STRUCTURAL_META,
|
|
167
210
|
hasUntrackedChanges: prevState.hasUntrackedChanges,
|
|
@@ -176,12 +219,13 @@ function createParagraphChangeTrackerPlugin() {
|
|
|
176
219
|
const from = mapStepPosition(remap, range.from, 1);
|
|
177
220
|
const to = mapStepPosition(remap, range.to, -1);
|
|
178
221
|
if (to <= from) continue;
|
|
179
|
-
const { ids, hasUntracked } = collectAffectedParaIdsFromMarkLikeStep(tr.doc, from, to);
|
|
222
|
+
const { ids, positions, hasUntracked } = collectAffectedParaIdsFromMarkLikeStep(tr.doc, from, to);
|
|
223
|
+
for (const position of positions) newState.affectedParagraphPositions.add(position);
|
|
180
224
|
for (const id of ids) newState.changedParaIds.add(id);
|
|
181
225
|
if (hasUntracked) newState.hasUntrackedChanges = true;
|
|
182
226
|
}
|
|
183
227
|
}
|
|
184
|
-
if (prevState.
|
|
228
|
+
if (prevState.blockStructureFingerprint !== counts.blockStructureFingerprint) newState.structuralChange = true;
|
|
185
229
|
for (let stepIndex = 0; stepIndex < tr.steps.length; stepIndex++) {
|
|
186
230
|
const step = tr.steps[stepIndex];
|
|
187
231
|
const remap = tr.mapping.slice(stepIndex + 1);
|
|
@@ -189,7 +233,8 @@ function createParagraphChangeTrackerPlugin() {
|
|
|
189
233
|
const from = mapStepPosition(remap, step.from, 1);
|
|
190
234
|
const to = mapStepPosition(remap, step.to, -1);
|
|
191
235
|
if (to <= from) continue;
|
|
192
|
-
const { ids, hasUntracked } = collectAffectedParaIdsFromMarkLikeStep(tr.doc, from, to);
|
|
236
|
+
const { ids, positions, hasUntracked } = collectAffectedParaIdsFromMarkLikeStep(tr.doc, from, to);
|
|
237
|
+
for (const position of positions) newState.affectedParagraphPositions.add(position);
|
|
193
238
|
for (const id of ids) newState.changedParaIds.add(id);
|
|
194
239
|
if (hasUntracked) newState.hasUntrackedChanges = true;
|
|
195
240
|
continue;
|
|
@@ -199,7 +244,8 @@ function createParagraphChangeTrackerPlugin() {
|
|
|
199
244
|
const node = tr.doc.nodeAt(pos);
|
|
200
245
|
if (!node) continue;
|
|
201
246
|
const end = pos + node.nodeSize;
|
|
202
|
-
const { ids, hasUntracked } = collectAffectedParaIds(tr.doc, pos, end);
|
|
247
|
+
const { ids, positions, hasUntracked } = collectAffectedParaIds(tr.doc, pos, end);
|
|
248
|
+
for (const position of positions) newState.affectedParagraphPositions.add(position);
|
|
203
249
|
for (const id of ids) newState.changedParaIds.add(id);
|
|
204
250
|
if (hasUntracked) newState.hasUntrackedChanges = true;
|
|
205
251
|
continue;
|
|
@@ -208,11 +254,23 @@ function createParagraphChangeTrackerPlugin() {
|
|
|
208
254
|
const from = mapStepPosition(remap, newStart, 1);
|
|
209
255
|
const to = mapStepPosition(remap, newEnd, -1);
|
|
210
256
|
if (to < from) return;
|
|
211
|
-
const { ids, hasUntracked } = collectAffectedParaIds(tr.doc, from, to);
|
|
257
|
+
const { ids, positions, hasUntracked } = collectAffectedParaIds(tr.doc, from, to);
|
|
258
|
+
for (const position of positions) newState.affectedParagraphPositions.add(position);
|
|
212
259
|
for (const id of ids) newState.changedParaIds.add(id);
|
|
213
260
|
if (hasUntracked) newState.hasUntrackedChanges = true;
|
|
214
261
|
});
|
|
215
262
|
}
|
|
263
|
+
if (newState.hasUntrackedChanges || prevState.blockStructureFingerprint !== counts.blockStructureFingerprint) tr.before.descendants((node, position) => {
|
|
264
|
+
if (node.type.name !== "paragraph") return true;
|
|
265
|
+
if (!isUsableParaId(node.attrs["paraId"])) {
|
|
266
|
+
const mapped = mapParagraphPosition(tr, position);
|
|
267
|
+
if (mapped === null || newState.affectedParagraphPositions.has(mapped)) {
|
|
268
|
+
newState.hasUntrackedSourceChanges = true;
|
|
269
|
+
newState.hasUntrackedChanges = true;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return false;
|
|
273
|
+
});
|
|
216
274
|
return newState;
|
|
217
275
|
}
|
|
218
276
|
}
|
|
@@ -231,7 +289,7 @@ function getChangedParagraphIds(state) {
|
|
|
231
289
|
return getChangeTrackerState(state)?.changedParaIds ?? /* @__PURE__ */ new Set();
|
|
232
290
|
}
|
|
233
291
|
/**
|
|
234
|
-
* Check if
|
|
292
|
+
* Check if paragraph membership, order, or block structure changed
|
|
235
293
|
*/
|
|
236
294
|
function hasStructuralChanges(state) {
|
|
237
295
|
return getChangeTrackerState(state)?.structuralChange ?? false;
|