@portone/docx-editor 0.6.0 → 0.6.1
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/CHANGELOG.md +24 -0
- package/dist/DocxEditor.js +84 -26
- package/dist/docx/exportDocx.d.ts +2 -1
- package/dist/docx/exportDocx.js +3 -9
- package/dist/docx/headersFooters.js +35 -23
- package/dist/docx/identities.d.ts +28 -0
- package/dist/docx/identities.js +42 -10
- package/dist/docx/importDocx.js +25 -26
- package/dist/docx/importParagraph.d.ts +1 -4
- package/dist/docx/importParagraph.js +4 -18
- package/dist/docx/invariants.d.ts +1 -1
- package/dist/docx/invariants.js +45 -4
- package/dist/docx/notes/newNote.d.ts +39 -0
- package/dist/docx/notes/newNote.js +122 -0
- package/dist/docx/notes/numbering.d.ts +27 -0
- package/dist/docx/notes/numbering.js +120 -0
- package/dist/docx/notes/reading.d.ts +58 -0
- package/dist/docx/notes/reading.js +98 -0
- package/dist/docx/notes/writing.d.ts +19 -0
- package/dist/docx/notes/writing.js +96 -0
- package/dist/docx/partPlan.d.ts +3 -0
- package/dist/docx/partPlanners.d.ts +13 -0
- package/dist/docx/partPlanners.js +48 -0
- package/dist/docx/sections.d.ts +5 -0
- package/dist/docx/sections.js +7 -2
- package/dist/docx/serializeParagraph.js +7 -2
- package/dist/docx/session.d.ts +5 -0
- package/dist/docx/session.js +6 -0
- package/dist/docx/storyParts.d.ts +112 -0
- package/dist/docx/storyParts.js +289 -0
- package/dist/editor/clipboard/htmlReader.d.ts +24 -4
- package/dist/editor/clipboard/htmlReader.js +39 -4
- package/dist/editor/clipboard/internalChannel.d.ts +30 -3
- package/dist/editor/clipboard/internalChannel.js +43 -3
- package/dist/editor/clipboard/normalizers.d.ts +35 -8
- package/dist/editor/clipboard/normalizers.js +60 -11
- package/dist/editor/clipboard/plugin.js +42 -22
- package/dist/editor/commands/index.d.ts +1 -0
- package/dist/editor/commands/index.js +18 -0
- package/dist/editor/commands/noteCommands.d.ts +47 -0
- package/dist/editor/commands/noteCommands.js +95 -0
- package/dist/editor/commands/noteQueries.d.ts +22 -3
- package/dist/editor/commands/noteQueries.js +21 -13
- package/dist/editor/createEditor.js +21 -3
- package/dist/editor/documentStyles.d.ts +3 -0
- package/dist/editor/documentStyles.js +2 -1
- package/dist/editor/editorDocument.d.ts +24 -0
- package/dist/editor/editorDocument.js +34 -4
- package/dist/editor/notes/noteSurface.d.ts +53 -0
- package/dist/editor/notes/noteSurface.js +246 -0
- package/dist/editor/plugins/compositionSelection.d.ts +20 -0
- package/dist/editor/plugins/compositionSelection.js +19 -0
- package/dist/editor/plugins/keymap.d.ts +14 -0
- package/dist/editor/plugins/keymap.js +13 -1
- package/dist/editor/plugins/noteLifecycle.d.ts +18 -0
- package/dist/editor/plugins/noteLifecycle.js +117 -0
- package/dist/editor/plugins/noteNavigation.d.ts +45 -0
- package/dist/editor/plugins/noteNavigation.js +86 -0
- package/dist/editor/plugins/noteNumbering.d.ts +13 -0
- package/dist/editor/plugins/noteNumbering.js +58 -0
- package/dist/editor/plugins/rowResize.js +2 -7
- package/dist/editor/plugins/tabLayout.js +2 -7
- package/dist/editor/stories/storyMarkup.d.ts +24 -0
- package/dist/editor/stories/storyMarkup.js +35 -0
- package/dist/editor/stories/storyState.d.ts +34 -0
- package/dist/editor/stories/storyState.js +74 -0
- package/dist/editor/stories/storyView.d.ts +115 -0
- package/dist/editor/stories/storyView.js +144 -0
- package/dist/numbering/spellers.d.ts +1 -1
- package/dist/numbering/spellers.js +6 -1
- package/dist/page/blockKinds.d.ts +23 -4
- package/dist/page/demands/footnoteDemands.d.ts +18 -0
- package/dist/page/demands/footnoteDemands.js +51 -0
- package/dist/page/demands/index.d.ts +43 -0
- package/dist/page/demands/index.js +8 -0
- package/dist/page/kinds/paragraphKind.js +7 -1
- package/dist/page/kinds/tableKind.js +9 -1
- package/dist/page/measureBlocks.d.ts +2 -0
- package/dist/page/measureBlocks.js +31 -10
- package/dist/page/pageDecorations.d.ts +9 -2
- package/dist/page/pageDecorations.js +18 -5
- package/dist/page/pageLayout.d.ts +77 -1
- package/dist/page/pageLayout.js +186 -9
- package/dist/page/usePageLayout.d.ts +57 -2
- package/dist/page/usePageLayout.js +50 -5
- package/dist/schema/attrRoles.js +3 -2
- package/dist/schema/docxSchema.js +5 -2
- package/dist/schema/editGuard.d.ts +1 -1
- package/dist/schema/guards.js +1 -2
- package/dist/schema/preservedGuards.d.ts +4 -6
- package/dist/schema/preservedGuards.js +0 -9
- package/dist/schema/stories.d.ts +34 -2
- package/dist/schema/stories.js +22 -5
- package/dist/styles/classNames.d.ts +21 -6
- package/dist/styles/classNames.js +21 -6
- package/dist/styles/inlineStyle.d.ts +7 -0
- package/dist/styles/inlineStyle.js +11 -5
- package/dist/styles/visualScale.d.ts +19 -0
- package/dist/styles/visualScale.js +26 -0
- package/dist/styles.css +89 -32
- package/dist/ui/InsertImageButton.d.ts +3 -1
- package/dist/ui/InsertImageButton.js +3 -2
- package/dist/ui/TableMenu.d.ts +5 -2
- package/dist/ui/TableMenu.js +18 -1
- package/dist/ui/TextMenu.d.ts +5 -2
- package/dist/ui/TextMenu.js +21 -6
- package/dist/ui/Toolbar.d.ts +17 -2
- package/dist/ui/Toolbar.js +23 -10
- package/dist/ui/comments/useCommentRailLayout.js +2 -4
- package/dist/ui/noteItems.d.ts +21 -0
- package/dist/ui/noteItems.js +30 -0
- package/dist/ui/notes/NoteAreas.d.ts +73 -0
- package/dist/ui/notes/NoteAreas.js +131 -0
- package/dist/ui/notes/NoteList.d.ts +25 -0
- package/dist/ui/notes/NoteList.js +58 -0
- package/dist/ui/notes/StoryRow.d.ts +75 -0
- package/dist/ui/notes/StoryRow.js +122 -0
- package/dist/ui/notes/noteBands.d.ts +100 -0
- package/dist/ui/notes/noteBands.js +142 -0
- package/dist/ui/notes/noteHeights.d.ts +10 -0
- package/dist/ui/notes/noteHeights.js +24 -0
- package/dist/ui/notes/noteSeparator.d.ts +12 -0
- package/dist/ui/notes/noteSeparator.js +9 -0
- package/dist/ui/notes/useStorySurface.d.ts +68 -0
- package/dist/ui/notes/useStorySurface.js +95 -0
- package/dist/ui/runCommand.d.ts +8 -1
- package/dist/ui/runCommand.js +2 -2
- package/dist/ui/shortcutLabels.d.ts +16 -0
- package/dist/ui/shortcutLabels.js +9 -0
- package/dist/ui/usePageRoom.d.ts +13 -0
- package/dist/ui/usePageRoom.js +31 -0
- package/package.json +1 -1
- package/dist/docx/notes.d.ts +0 -27
- package/dist/docx/notes.js +0 -55
- package/dist/ui/NotesPanel.d.ts +0 -6
- package/dist/ui/NotesPanel.js +0 -33
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// src/editor/stories/storyView.ts
|
|
2
|
+
import {
|
|
3
|
+
TextSelection
|
|
4
|
+
} from "prosemirror-state";
|
|
5
|
+
import { EditorView } from "prosemirror-view";
|
|
6
|
+
import { editingProtection, editsShut } from "../../schema/protectionState.js";
|
|
7
|
+
import { sameStory, storyNodeOf } from "../../schema/stories.js";
|
|
8
|
+
import { editorClassNames } from "../../styles/classNames.js";
|
|
9
|
+
import { runMarkView } from "../views/runMarkView.js";
|
|
10
|
+
import { storyEditorState } from "./storyState.js";
|
|
11
|
+
var SURFACE_CAPABILITIES = [
|
|
12
|
+
"list",
|
|
13
|
+
"table",
|
|
14
|
+
"image",
|
|
15
|
+
"link",
|
|
16
|
+
"note",
|
|
17
|
+
"comment"
|
|
18
|
+
];
|
|
19
|
+
var EVERY_CAPABILITY = new Set(
|
|
20
|
+
SURFACE_CAPABILITIES
|
|
21
|
+
);
|
|
22
|
+
var NO_CAPABILITY = /* @__PURE__ */ new Set();
|
|
23
|
+
function caretOf(state) {
|
|
24
|
+
return {
|
|
25
|
+
kind: "at",
|
|
26
|
+
anchor: state.selection.anchor,
|
|
27
|
+
head: state.selection.head
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function placed(state, at, to) {
|
|
31
|
+
const size = state.doc.content.size;
|
|
32
|
+
const $anchor = state.doc.resolve(Math.min(Math.max(at, 0), size));
|
|
33
|
+
const $head = state.doc.resolve(Math.min(Math.max(to, 0), size));
|
|
34
|
+
return state.apply(
|
|
35
|
+
state.tr.setSelection(TextSelection.between($anchor, $head))
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
function endComposition(view) {
|
|
39
|
+
if (!view.composing) return;
|
|
40
|
+
const marks = view.state.storedMarks ?? view.state.selection.$from.marks();
|
|
41
|
+
view.dispatch(
|
|
42
|
+
view.state.tr.setStoredMarks(marks).setMeta("addToHistory", false)
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
function createStoryView({
|
|
46
|
+
mount,
|
|
47
|
+
host,
|
|
48
|
+
key,
|
|
49
|
+
document,
|
|
50
|
+
fontFallbacks,
|
|
51
|
+
extensions,
|
|
52
|
+
caret = null,
|
|
53
|
+
onStateChange
|
|
54
|
+
}) {
|
|
55
|
+
const standing = () => host.shut(key) ? "readOnly" : editingProtection(host.state());
|
|
56
|
+
let protection = standing();
|
|
57
|
+
const keys = {
|
|
58
|
+
"Mod-z": () => host.undo(),
|
|
59
|
+
"Mod-y": () => host.redo(),
|
|
60
|
+
"Shift-Mod-z": () => host.redo(),
|
|
61
|
+
Escape: () => {
|
|
62
|
+
host.leave(key);
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
const built = (story) => storyEditorState({
|
|
67
|
+
story,
|
|
68
|
+
document,
|
|
69
|
+
protection,
|
|
70
|
+
keys,
|
|
71
|
+
plugins: extensions.plugins,
|
|
72
|
+
normalizers: extensions.normalizers,
|
|
73
|
+
takes: extensions.takes
|
|
74
|
+
});
|
|
75
|
+
const storyNow = () => storyNodeOf(host.state().doc, key);
|
|
76
|
+
const opening = storyNow();
|
|
77
|
+
if (opening === null) {
|
|
78
|
+
throw new Error(`the document holds no story named ${key}`);
|
|
79
|
+
}
|
|
80
|
+
const view = new EditorView(mount, {
|
|
81
|
+
state: built(opening),
|
|
82
|
+
// A standing that shuts the story shuts typing in it, and is read off the state so that a
|
|
83
|
+
// mode switched while the story is open takes effect without a new view
|
|
84
|
+
editable: (current) => !editsShut(current),
|
|
85
|
+
attributes: { class: editorClassNames.storyBody },
|
|
86
|
+
// The schema can only draw a run with the default fallbacks; this view draws the host's
|
|
87
|
+
markViews: { run: runMarkView(fontFallbacks) },
|
|
88
|
+
nodeViews: extensions.nodeViews,
|
|
89
|
+
dispatchTransaction(transaction) {
|
|
90
|
+
const next = view.state.applyTransaction(transaction);
|
|
91
|
+
if (next.state.doc === view.state.doc) {
|
|
92
|
+
show(next.state);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const before = view.state;
|
|
96
|
+
show(next.state);
|
|
97
|
+
if (host.write(key, next.state.doc)) return;
|
|
98
|
+
if (sameStory(storyNow(), next.state.doc)) return;
|
|
99
|
+
show(before);
|
|
100
|
+
endComposition(view);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
const show = (next) => {
|
|
104
|
+
view.updateState(next);
|
|
105
|
+
onStateChange?.(next);
|
|
106
|
+
};
|
|
107
|
+
const open = caret ?? null;
|
|
108
|
+
if (open === null) {
|
|
109
|
+
const end = view.state.doc.content.size;
|
|
110
|
+
show(placed(view.state, end, end));
|
|
111
|
+
} else if (open.kind === "at") {
|
|
112
|
+
show(placed(view.state, open.anchor, open.head));
|
|
113
|
+
} else {
|
|
114
|
+
const at = view.posAtCoords({ left: open.left, top: open.top });
|
|
115
|
+
const pos = at?.pos ?? view.state.doc.content.size;
|
|
116
|
+
show(placed(view.state, pos, pos));
|
|
117
|
+
}
|
|
118
|
+
const registerFocus = () => host.activate(key, view);
|
|
119
|
+
view.dom.addEventListener("focus", registerFocus);
|
|
120
|
+
return {
|
|
121
|
+
view,
|
|
122
|
+
sync() {
|
|
123
|
+
const story = storyNow();
|
|
124
|
+
if (story === null) return;
|
|
125
|
+
const wanted = standing();
|
|
126
|
+
if (wanted === protection && sameStory(story, view.state.doc)) return;
|
|
127
|
+
const { anchor, head } = view.state.selection;
|
|
128
|
+
protection = wanted;
|
|
129
|
+
show(placed(built(story), anchor, head));
|
|
130
|
+
},
|
|
131
|
+
destroy() {
|
|
132
|
+
view.dom.removeEventListener("focus", registerFocus);
|
|
133
|
+
host.activate(key, null);
|
|
134
|
+
view.destroy();
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
export {
|
|
139
|
+
EVERY_CAPABILITY,
|
|
140
|
+
NO_CAPABILITY,
|
|
141
|
+
SURFACE_CAPABILITIES,
|
|
142
|
+
caretOf,
|
|
143
|
+
createStoryView
|
|
144
|
+
};
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* because it has a speller here, so a marker can never be counted in a format nothing can write.
|
|
7
7
|
*/
|
|
8
8
|
/** A `w:numFmt` this editor spells out. Every other one is drawn as a decimal */
|
|
9
|
-
export type NumberFormat = "decimal" | "decimalZero" | "bullet" | "lowerLetter" | "upperLetter" | "lowerRoman" | "upperRoman" | "ganada" | "koreanDigital" | "chineseCounting";
|
|
9
|
+
export type NumberFormat = "decimal" | "decimalZero" | "bullet" | "lowerLetter" | "upperLetter" | "lowerRoman" | "upperRoman" | "ganada" | "koreanDigital" | "chineseCounting" | "chicago";
|
|
10
10
|
export declare function isNumberFormat(value: string | null): value is NumberFormat;
|
|
11
11
|
/**
|
|
12
12
|
* The count as this format writes it, and as a decimal where spelling it out would run past the
|
|
@@ -31,6 +31,7 @@ function repeated(symbols, count) {
|
|
|
31
31
|
return (symbols[index] ?? "").repeat(times);
|
|
32
32
|
}
|
|
33
33
|
var LATIN = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
34
|
+
var CHICAGO = "*\u2020\u2021\xA7";
|
|
34
35
|
var GANADA = "\uAC00\uB098\uB2E4\uB77C\uB9C8\uBC14\uC0AC\uC544\uC790\uCC28\uCE74\uD0C0\uD30C\uD558";
|
|
35
36
|
var KOREAN_DIGITS = "\uC601\uC77C\uC774\uC0BC\uC0AC\uC624\uC721\uCE60\uD314\uAD6C";
|
|
36
37
|
function digits(symbols, count) {
|
|
@@ -80,7 +81,11 @@ var NUMBER_SPELLERS = {
|
|
|
80
81
|
spell: (count) => digits(KOREAN_DIGITS, count),
|
|
81
82
|
countsPerChar: POSITIONAL
|
|
82
83
|
},
|
|
83
|
-
chineseCounting: { spell: chineseCounting, countsPerChar: POSITIONAL }
|
|
84
|
+
chineseCounting: { spell: chineseCounting, countsPerChar: POSITIONAL },
|
|
85
|
+
chicago: {
|
|
86
|
+
spell: (count) => repeated(CHICAGO, count),
|
|
87
|
+
countsPerChar: CHICAGO.length
|
|
88
|
+
}
|
|
84
89
|
};
|
|
85
90
|
function isNumberFormat(value) {
|
|
86
91
|
return value !== null && Object.hasOwn(NUMBER_SPELLERS, value);
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import type { Node as PMNode } from "prosemirror-model";
|
|
12
12
|
import type { Decoration, EditorView } from "prosemirror-view";
|
|
13
|
+
import type { PageDemand } from "./demands";
|
|
13
14
|
/** A place inside a block where the next page may, or must, start */
|
|
14
15
|
export interface BreakCandidate {
|
|
15
16
|
/**
|
|
@@ -41,13 +42,21 @@ export interface MeasuredBlock {
|
|
|
41
42
|
minFirstPiece: number;
|
|
42
43
|
/** The document asks for this block to stand on the same page as the start of the block after it */
|
|
43
44
|
keepWithNext: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* The places in the block that ask the page they land on for room at its foot, their offsets read
|
|
47
|
+
* like a candidate's with no space in the block. A block asking for none may leave it out
|
|
48
|
+
*/
|
|
49
|
+
demands?: readonly PageDemand[];
|
|
44
50
|
}
|
|
45
51
|
/** A page cut the layout decided on: the space opened before the continued piece */
|
|
46
52
|
export interface PageCut {
|
|
47
53
|
at: number;
|
|
48
54
|
height: number;
|
|
49
55
|
}
|
|
50
|
-
/**
|
|
56
|
+
/**
|
|
57
|
+
* One block as it stands on the sheet, handed to the kind about to measure it and then to every
|
|
58
|
+
* demand source (`page/demands`)
|
|
59
|
+
*/
|
|
51
60
|
export interface MeasureTarget {
|
|
52
61
|
view: EditorView;
|
|
53
62
|
node: PMNode;
|
|
@@ -72,6 +81,15 @@ export interface KindMeasure {
|
|
|
72
81
|
breakAfter: boolean;
|
|
73
82
|
/** Height the engine's own marks add inside this block, taken off its measured bottom */
|
|
74
83
|
appliedHeight: number;
|
|
84
|
+
/**
|
|
85
|
+
* What the engine's marks have opened at each place this block was cut, as the browser drew it,
|
|
86
|
+
* by the candidate's position. What is drawn there can come out taller than the cut asked for -
|
|
87
|
+
* a table's spacer row takes half of a collapsed border on each side - and everything else a
|
|
88
|
+
* kind reports is read off the sheet as drawn, so a place below a cut is taken back to its
|
|
89
|
+
* natural offset by this rather than by what the layout asked for (`page/measureBlocks`).
|
|
90
|
+
* A kind that opens nothing leaves it out
|
|
91
|
+
*/
|
|
92
|
+
opened?: ReadonlyMap<number, number>;
|
|
75
93
|
/**
|
|
76
94
|
* Whether the document asks for this block to stand on the same page as the start of the block
|
|
77
95
|
* after it. A kind whose blocks never do leaves it out
|
|
@@ -82,9 +100,10 @@ export interface KindMeasure {
|
|
|
82
100
|
* Everything the engine does with one shape of breakable block: how one is measured, and how the
|
|
83
101
|
* cuts the layout gave it are drawn.
|
|
84
102
|
*
|
|
85
|
-
* A shape the engine learns later
|
|
86
|
-
*
|
|
87
|
-
*
|
|
103
|
+
* A shape the engine learns later is one more kind rather than another branch in the measurer, in
|
|
104
|
+
* the decorations and in the plugin state at once. Room a place inside a block asks of the page it
|
|
105
|
+
* lands on is not a shape: a block has one kind while such a place can stand inside a block of any
|
|
106
|
+
* kind, so it is asked of every block by a demand source (`page/demands`) instead.
|
|
88
107
|
*/
|
|
89
108
|
export interface BlockKind {
|
|
90
109
|
readonly name: string;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The room a footnote reference asks of the page it lands on: its footnote, drawn at the foot of
|
|
3
|
+
* that page (`ui/notes`).
|
|
4
|
+
*
|
|
5
|
+
* The sheet is measured on every layout pass and nearly every block holds no reference, so which
|
|
6
|
+
* references a block holds is read off the block node and remembered against it. A node an edit
|
|
7
|
+
* did not touch is the same node afterwards, so a pass reads nothing off the page for a block with
|
|
8
|
+
* no reference and walks only the blocks an edit rebuilt.
|
|
9
|
+
*/
|
|
10
|
+
import type { DemandSource } from "./index";
|
|
11
|
+
/** The band footnotes are kept in, which the layout's caller hands the heights of */
|
|
12
|
+
export declare const FOOTNOTE_BAND = "footnote";
|
|
13
|
+
/**
|
|
14
|
+
* Where footnotes stand among the bands a page keeps (`DemandBand.order`). Word sets them at the
|
|
15
|
+
* very foot of the body, under anything else the page holds there, so this is the last place
|
|
16
|
+
*/
|
|
17
|
+
export declare const FOOTNOTE_BAND_ORDER = 0;
|
|
18
|
+
export declare const footnoteDemands: DemandSource;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// src/page/demands/footnoteDemands.ts
|
|
2
|
+
import { docxSchema } from "../../schema/index.js";
|
|
3
|
+
import { storyKey } from "../../schema/stories.js";
|
|
4
|
+
var FOOTNOTE_BAND = "footnote";
|
|
5
|
+
var FOOTNOTE_BAND_ORDER = 0;
|
|
6
|
+
var BAND_NOTE_KINDS = ["footnote"];
|
|
7
|
+
var NO_REFERENCES = [];
|
|
8
|
+
var NO_DEMANDS = [];
|
|
9
|
+
var held = /* @__PURE__ */ new WeakMap();
|
|
10
|
+
function footnoteReferencesIn(block) {
|
|
11
|
+
const remembered = held.get(block);
|
|
12
|
+
if (remembered) return remembered;
|
|
13
|
+
const found = [];
|
|
14
|
+
block.descendants((node, offset) => {
|
|
15
|
+
if (node.type !== docxSchema.nodes.noteReference) return true;
|
|
16
|
+
const id = node.attrs.id;
|
|
17
|
+
const kind = BAND_NOTE_KINDS.find(
|
|
18
|
+
(candidate) => candidate === node.attrs.kind
|
|
19
|
+
);
|
|
20
|
+
if (kind !== void 0 && typeof id === "string") {
|
|
21
|
+
found.push({ offset, key: storyKey(kind, id) });
|
|
22
|
+
}
|
|
23
|
+
return false;
|
|
24
|
+
});
|
|
25
|
+
const references = found.length === 0 ? NO_REFERENCES : found;
|
|
26
|
+
held.set(block, references);
|
|
27
|
+
return references;
|
|
28
|
+
}
|
|
29
|
+
var footnoteDemands = {
|
|
30
|
+
name: "footnote",
|
|
31
|
+
demandsIn({ view, node, pos, sheetY, top }) {
|
|
32
|
+
const references = footnoteReferencesIn(node);
|
|
33
|
+
if (references.length === 0) return NO_DEMANDS;
|
|
34
|
+
return references.flatMap(({ offset, key }) => {
|
|
35
|
+
const drawn = view.nodeDOM(pos + 1 + offset);
|
|
36
|
+
if (!(drawn instanceof Element)) return [];
|
|
37
|
+
return [
|
|
38
|
+
{
|
|
39
|
+
offset: sheetY(drawn.getBoundingClientRect().top) - top,
|
|
40
|
+
id: key,
|
|
41
|
+
band: FOOTNOTE_BAND
|
|
42
|
+
}
|
|
43
|
+
];
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
export {
|
|
48
|
+
FOOTNOTE_BAND,
|
|
49
|
+
FOOTNOTE_BAND_ORDER,
|
|
50
|
+
footnoteDemands
|
|
51
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Room a place inside a block asks the page it lands on to keep at the foot of its body.
|
|
3
|
+
*
|
|
4
|
+
* A block has one kind (`page/blockKinds`), and a place asking for room can stand inside a block of
|
|
5
|
+
* any kind: a paragraph, or a paragraph in a table cell. So what asks is not a kind but a source,
|
|
6
|
+
* and every registered source is asked about every block whatever its kind. The layout
|
|
7
|
+
* (`page/pageLayout`) knows a demand by its band alone, never by what put it there.
|
|
8
|
+
*/
|
|
9
|
+
import type { MeasureTarget } from "../blockKinds";
|
|
10
|
+
/** Room one place in a block asks the page it lands on to keep at the foot */
|
|
11
|
+
export interface PageDemand {
|
|
12
|
+
/**
|
|
13
|
+
* The place's top from the block's natural top. A source reads it off the sheet as drawn, the
|
|
14
|
+
* way a kind reads a candidate (`target.sheetY(viewportY) - target.top`), and the measurement
|
|
15
|
+
* takes back off the spaces the engine opened inside the block above that place
|
|
16
|
+
* (`page/measureBlocks`), so no source has to know what any kind draws
|
|
17
|
+
*/
|
|
18
|
+
readonly offset: number;
|
|
19
|
+
/** What is kept. One id is kept once on a page, however many places on it ask for it */
|
|
20
|
+
readonly id: string;
|
|
21
|
+
/** The band at the foot of the page that keeps it (`DemandBand`) */
|
|
22
|
+
readonly band: string;
|
|
23
|
+
}
|
|
24
|
+
export interface DemandSource {
|
|
25
|
+
readonly name: string;
|
|
26
|
+
demandsIn(target: MeasureTarget): readonly PageDemand[];
|
|
27
|
+
}
|
|
28
|
+
export interface DemandBand {
|
|
29
|
+
/**
|
|
30
|
+
* Where this band stands among the bands one page keeps, counted up from the foot of the body:
|
|
31
|
+
* 0 stands at the very foot and a higher number above it, between the text and the band below.
|
|
32
|
+
* Without it a page would stack its bands in the order its text happened to reach them, so the
|
|
33
|
+
* same two bands could come out one way round on one page and the other way round on the next.
|
|
34
|
+
* Two bands a page may hold together name two different places
|
|
35
|
+
*/
|
|
36
|
+
readonly order: number;
|
|
37
|
+
/** Height a page adds once when it holds any demand of this band */
|
|
38
|
+
readonly overhead: number;
|
|
39
|
+
/** The height each id takes in the band. An id not measured yet takes none */
|
|
40
|
+
readonly heights: ReadonlyMap<string, number>;
|
|
41
|
+
}
|
|
42
|
+
/** The sources the editor asks about every block, in the order their demands are listed */
|
|
43
|
+
export declare const DEFAULT_DEMAND_SOURCES: readonly DemandSource[];
|
|
@@ -28,9 +28,11 @@ var paragraphKind = {
|
|
|
28
28
|
const breaks = pageBreaksIn(node, pos);
|
|
29
29
|
const candidates = [];
|
|
30
30
|
let appliedHeight = 0;
|
|
31
|
+
const opened = /* @__PURE__ */ new Map();
|
|
31
32
|
dom.querySelectorAll(BREAK_SPACE).forEach((space, index) => {
|
|
32
33
|
const box = space.getBoundingClientRect();
|
|
33
34
|
const found = breaks.at(index);
|
|
35
|
+
const drawn = box.height / scale;
|
|
34
36
|
if (found) {
|
|
35
37
|
candidates.push({
|
|
36
38
|
at: found.at,
|
|
@@ -38,8 +40,9 @@ var paragraphKind = {
|
|
|
38
40
|
forced: true,
|
|
39
41
|
repeatHeight: 0
|
|
40
42
|
});
|
|
43
|
+
if (drawn > 0) opened.set(found.at, drawn);
|
|
41
44
|
}
|
|
42
|
-
appliedHeight +=
|
|
45
|
+
appliedHeight += drawn;
|
|
43
46
|
});
|
|
44
47
|
return {
|
|
45
48
|
candidates,
|
|
@@ -48,6 +51,7 @@ var paragraphKind = {
|
|
|
48
51
|
// still start a new page, but only after the whole block
|
|
49
52
|
breakAfter: dom.querySelectorAll(PAGE_BREAK_BR).length > candidates.length,
|
|
50
53
|
appliedHeight,
|
|
54
|
+
opened,
|
|
51
55
|
keepWithNext: toParagraphFormat(node.attrs.format)?.keepNext === true
|
|
52
56
|
};
|
|
53
57
|
},
|
|
@@ -59,6 +63,8 @@ var paragraphKind = {
|
|
|
59
63
|
into.push(
|
|
60
64
|
Decoration.inline(at, at + size, {
|
|
61
65
|
nodeName: "span",
|
|
66
|
+
// Chrome can enter the enlarged break and insert text outside the document model.
|
|
67
|
+
contenteditable: "false",
|
|
62
68
|
style: spaceStyle(height),
|
|
63
69
|
[editorAttributes.pageBreakSpace]: `${height}`
|
|
64
70
|
})
|
|
@@ -34,7 +34,8 @@ function rowEntries(view, tableNode, tablePos, tableDom, appliedRows, scale) {
|
|
|
34
34
|
node: row,
|
|
35
35
|
pos,
|
|
36
36
|
top: (rect.top - tableTop) / scale - appliedBefore,
|
|
37
|
-
height: rect.height / scale
|
|
37
|
+
height: rect.height / scale,
|
|
38
|
+
appliedAbove: appliedBefore
|
|
38
39
|
});
|
|
39
40
|
});
|
|
40
41
|
return entries;
|
|
@@ -132,6 +133,12 @@ var tableKind = {
|
|
|
132
133
|
});
|
|
133
134
|
}
|
|
134
135
|
}
|
|
136
|
+
const opened = /* @__PURE__ */ new Map();
|
|
137
|
+
rows.forEach((row, index) => {
|
|
138
|
+
const above = index === 0 ? 0 : rows[index - 1]?.appliedAbove ?? 0;
|
|
139
|
+
const here = row.appliedAbove - above;
|
|
140
|
+
if (here > 0) opened.set(row.pos, here);
|
|
141
|
+
});
|
|
135
142
|
const firstBodyBoundary = candidates.find(
|
|
136
143
|
(candidate) => candidate.offset > repeatHeaderHeight + 0.5
|
|
137
144
|
);
|
|
@@ -141,6 +148,7 @@ var tableKind = {
|
|
|
141
148
|
);
|
|
142
149
|
return {
|
|
143
150
|
candidates,
|
|
151
|
+
opened,
|
|
144
152
|
// The smallest useful first piece: the headers followed by one body row group
|
|
145
153
|
minFirstPiece: firstBodyBoundary?.offset ?? dom.getBoundingClientRect().height / scale - appliedHeight,
|
|
146
154
|
// A space cannot be opened inside a cell, so a break standing in one is answered after the
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* The engine's own marks are taken back off as they are read, so a block already pushed and a
|
|
8
8
|
* break already given its space read as they would with neither applied. A measurement that read
|
|
9
9
|
* them in would add to what is already there, and the layout would creep on every pass.
|
|
10
|
+
* Every demand source (`page/demands`) is asked about every block as well, after its kind, and the
|
|
11
|
+
* places it reads have the spaces the engine opened inside the block taken back off the same way.
|
|
10
12
|
* Where there is no layout (in tests) everything is 0, so the result is a single page.
|
|
11
13
|
*/
|
|
12
14
|
import type { EditorView } from "prosemirror-view";
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
// src/page/measureBlocks.ts
|
|
2
2
|
import { editorAttributes } from "../styles/classNames.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { visualScaleOf } from "../styles/visualScale.js";
|
|
4
|
+
import {
|
|
5
|
+
blockKindFor
|
|
6
|
+
} from "./blockKinds.js";
|
|
7
|
+
import { blockKindsOf, demandSourcesOf } from "./pageDecorations.js";
|
|
8
|
+
var NOTHING_OPENED = /* @__PURE__ */ new Map();
|
|
5
9
|
function pixels(value) {
|
|
6
10
|
const parsed = Number.parseFloat(value);
|
|
7
11
|
return Number.isFinite(parsed) ? parsed : 0;
|
|
8
12
|
}
|
|
9
|
-
function visualScale(element) {
|
|
10
|
-
const scale = pixels(getComputedStyle(element).zoom);
|
|
11
|
-
return scale > 0 ? scale : 1;
|
|
12
|
-
}
|
|
13
13
|
function appliedPush(element) {
|
|
14
14
|
return pixels(element.getAttribute(editorAttributes.pagePush) ?? "");
|
|
15
15
|
}
|
|
@@ -23,16 +23,28 @@ function drawnBlocks(view) {
|
|
|
23
23
|
});
|
|
24
24
|
return found;
|
|
25
25
|
}
|
|
26
|
+
function naturalOffset(candidates, opened, drawn) {
|
|
27
|
+
let shift = 0;
|
|
28
|
+
for (const candidate of candidates) {
|
|
29
|
+
const space = opened.get(candidate.at);
|
|
30
|
+
if (space === void 0) continue;
|
|
31
|
+
const start = candidate.offset + shift;
|
|
32
|
+
if (drawn < start) break;
|
|
33
|
+
shift += Math.min(space, drawn - start);
|
|
34
|
+
}
|
|
35
|
+
return drawn - shift;
|
|
36
|
+
}
|
|
26
37
|
function measureSheet(view, layer) {
|
|
27
38
|
const sheet = view.dom;
|
|
28
39
|
const sheetRect = sheet.getBoundingClientRect();
|
|
29
40
|
const layerRect = layer.getBoundingClientRect();
|
|
30
41
|
const style = getComputedStyle(sheet);
|
|
31
|
-
const scale =
|
|
42
|
+
const scale = visualScaleOf(layer);
|
|
32
43
|
const contentTop = pixels(style.paddingTop);
|
|
33
44
|
const contentBottom = pixels(style.paddingBottom);
|
|
34
45
|
const sheetY = (viewportY) => (viewportY - sheetRect.top) / scale;
|
|
35
46
|
const kinds = blockKindsOf(view.state);
|
|
47
|
+
const sources = demandSourcesOf(view.state);
|
|
36
48
|
const blocks = [];
|
|
37
49
|
let previousBottom = contentTop;
|
|
38
50
|
let applied = 0;
|
|
@@ -42,7 +54,7 @@ function measureSheet(view, layer) {
|
|
|
42
54
|
const above = applied;
|
|
43
55
|
const blockY = (viewportY) => sheetY(viewportY) - above;
|
|
44
56
|
const top = blockY(rect.top);
|
|
45
|
-
const
|
|
57
|
+
const target = {
|
|
46
58
|
view,
|
|
47
59
|
node,
|
|
48
60
|
pos,
|
|
@@ -50,7 +62,8 @@ function measureSheet(view, layer) {
|
|
|
50
62
|
sheetY: blockY,
|
|
51
63
|
top,
|
|
52
64
|
scale
|
|
53
|
-
}
|
|
65
|
+
};
|
|
66
|
+
const measured = blockKindFor(kinds, node).measure(target);
|
|
54
67
|
applied += measured.appliedHeight;
|
|
55
68
|
const bottom = sheetY(rect.bottom) - applied;
|
|
56
69
|
blocks.push({
|
|
@@ -61,7 +74,15 @@ function measureSheet(view, layer) {
|
|
|
61
74
|
breakAfter: measured.breakAfter,
|
|
62
75
|
candidates: measured.candidates,
|
|
63
76
|
minFirstPiece: measured.minFirstPiece,
|
|
64
|
-
keepWithNext: measured.keepWithNext ?? false
|
|
77
|
+
keepWithNext: measured.keepWithNext ?? false,
|
|
78
|
+
demands: sources.flatMap((source) => source.demandsIn(target)).map((demand) => ({
|
|
79
|
+
...demand,
|
|
80
|
+
offset: naturalOffset(
|
|
81
|
+
measured.candidates,
|
|
82
|
+
measured.opened ?? NOTHING_OPENED,
|
|
83
|
+
demand.offset
|
|
84
|
+
)
|
|
85
|
+
}))
|
|
65
86
|
});
|
|
66
87
|
previousBottom = bottom;
|
|
67
88
|
}
|
|
@@ -3,13 +3,15 @@
|
|
|
3
3
|
* own kind draws the cuts inside it as (`page/kinds`).
|
|
4
4
|
*
|
|
5
5
|
* The kinds the editor was built with are held here, so the measurement and the decorations read
|
|
6
|
-
* one registry and a block is measured by the same kind that draws it.
|
|
6
|
+
* one registry and a block is measured by the same kind that draws it. The demand sources
|
|
7
|
+
* (`page/demands`) the measurement asks about every block are held beside them.
|
|
7
8
|
* The document model is left untouched, so no trace of any of it is left in the exported XML or
|
|
8
9
|
* in the edit history.
|
|
9
10
|
*/
|
|
10
11
|
import { type EditorState, Plugin } from "prosemirror-state";
|
|
11
12
|
import { DecorationSet, type EditorView } from "prosemirror-view";
|
|
12
13
|
import { type BlockKind, type PageCut } from "./blockKinds";
|
|
14
|
+
import { type DemandSource } from "./demands";
|
|
13
15
|
import type { BlockPush } from "./pageLayout";
|
|
14
16
|
/** Everything one measurement has to say about the page (`page/pageLayout`) */
|
|
15
17
|
export interface PageMarksInput {
|
|
@@ -25,11 +27,16 @@ export interface PageMarksInput {
|
|
|
25
27
|
}
|
|
26
28
|
interface PageMarks extends PageMarksInput {
|
|
27
29
|
kinds: readonly BlockKind[];
|
|
30
|
+
sources: readonly DemandSource[];
|
|
28
31
|
decorations: DecorationSet;
|
|
29
32
|
}
|
|
30
|
-
export declare function pageDecorations(kinds?: readonly BlockKind[]): Plugin<PageMarks>;
|
|
33
|
+
export declare function pageDecorations(kinds?: readonly BlockKind[], sources?: readonly DemandSource[]): Plugin<PageMarks>;
|
|
31
34
|
/** The kinds the editor was built with, which the measurement reads to match them */
|
|
32
35
|
export declare function blockKindsOf(state: EditorState): readonly BlockKind[];
|
|
36
|
+
/** The demand sources the editor was built with, which the measurement asks about every block */
|
|
37
|
+
export declare function demandSourcesOf(state: EditorState): readonly DemandSource[];
|
|
38
|
+
/** The cuts the sheet is drawn with, whose spaces the measurement takes back off a demand's place */
|
|
39
|
+
export declare function pageCutsOf(state: EditorState): readonly PageCut[];
|
|
33
40
|
/** One transaction, or none when the same marks are already applied */
|
|
34
41
|
export declare function setPageMarks(view: EditorView, next: PageMarksInput): void;
|
|
35
42
|
export {};
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
import { Decoration, DecorationSet } from "prosemirror-view";
|
|
7
7
|
import { editorAttributes } from "../styles/classNames.js";
|
|
8
8
|
import { blockKindFor } from "./blockKinds.js";
|
|
9
|
+
import { DEFAULT_DEMAND_SOURCES } from "./demands/index.js";
|
|
9
10
|
import { DEFAULT_BLOCK_KINDS } from "./kinds/index.js";
|
|
10
11
|
var marksKey = new PluginKey("docxPageDecorations");
|
|
11
12
|
function pushStyle(marginTop) {
|
|
@@ -46,9 +47,10 @@ function decorationsFor(doc, kinds, pushes, cuts) {
|
|
|
46
47
|
});
|
|
47
48
|
return DecorationSet.create(doc, decorations);
|
|
48
49
|
}
|
|
49
|
-
function marksFor(doc, kinds, pushes, cuts) {
|
|
50
|
+
function marksFor(doc, { kinds, sources }, pushes, cuts) {
|
|
50
51
|
return {
|
|
51
52
|
kinds,
|
|
53
|
+
sources,
|
|
52
54
|
pushes,
|
|
53
55
|
cuts,
|
|
54
56
|
decorations: decorationsFor(doc, kinds, pushes, cuts)
|
|
@@ -75,18 +77,18 @@ function samePageMarks(a, b) {
|
|
|
75
77
|
return other !== void 0 && other.at === cut.at && other.height === cut.height;
|
|
76
78
|
});
|
|
77
79
|
}
|
|
78
|
-
function pageDecorations(kinds = DEFAULT_BLOCK_KINDS) {
|
|
80
|
+
function pageDecorations(kinds = DEFAULT_BLOCK_KINDS, sources = DEFAULT_DEMAND_SOURCES) {
|
|
79
81
|
return new Plugin({
|
|
80
82
|
key: marksKey,
|
|
81
83
|
state: {
|
|
82
|
-
init: (_config, state) => marksFor(state.doc, kinds, [], []),
|
|
84
|
+
init: (_config, state) => marksFor(state.doc, { kinds, sources }, [], []),
|
|
83
85
|
apply(tr, value) {
|
|
84
86
|
const next = tr.getMeta(marksKey);
|
|
85
87
|
if (next) return next;
|
|
86
88
|
if (!tr.docChanged) return value;
|
|
87
89
|
return marksFor(
|
|
88
90
|
tr.doc,
|
|
89
|
-
value
|
|
91
|
+
value,
|
|
90
92
|
value.pushes.map((push) => ({
|
|
91
93
|
...push,
|
|
92
94
|
pos: tr.mapping.map(push.pos)
|
|
@@ -106,6 +108,12 @@ function pageDecorations(kinds = DEFAULT_BLOCK_KINDS) {
|
|
|
106
108
|
function blockKindsOf(state) {
|
|
107
109
|
return marksKey.getState(state)?.kinds ?? DEFAULT_BLOCK_KINDS;
|
|
108
110
|
}
|
|
111
|
+
function demandSourcesOf(state) {
|
|
112
|
+
return marksKey.getState(state)?.sources ?? DEFAULT_DEMAND_SOURCES;
|
|
113
|
+
}
|
|
114
|
+
function pageCutsOf(state) {
|
|
115
|
+
return marksKey.getState(state)?.cuts ?? [];
|
|
116
|
+
}
|
|
109
117
|
function current(view) {
|
|
110
118
|
return marksKey.getState(view.state) ?? { pushes: [], cuts: [] };
|
|
111
119
|
}
|
|
@@ -116,7 +124,10 @@ function setPageMarks(view, next) {
|
|
|
116
124
|
marksKey,
|
|
117
125
|
marksFor(
|
|
118
126
|
view.state.doc,
|
|
119
|
-
|
|
127
|
+
{
|
|
128
|
+
kinds: blockKindsOf(view.state),
|
|
129
|
+
sources: demandSourcesOf(view.state)
|
|
130
|
+
},
|
|
120
131
|
next.pushes,
|
|
121
132
|
next.cuts
|
|
122
133
|
)
|
|
@@ -125,6 +136,8 @@ function setPageMarks(view, next) {
|
|
|
125
136
|
}
|
|
126
137
|
export {
|
|
127
138
|
blockKindsOf,
|
|
139
|
+
demandSourcesOf,
|
|
140
|
+
pageCutsOf,
|
|
128
141
|
pageDecorations,
|
|
129
142
|
setPageMarks
|
|
130
143
|
};
|