@portone/docx-editor 0.6.0 → 0.6.2
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 +40 -0
- package/dist/DocxEditor.js +84 -26
- package/dist/core.d.ts +4 -3
- package/dist/docx/exportDocx.d.ts +3 -2
- package/dist/docx/exportDocx.js +6 -10
- package/dist/docx/headersFooters.js +35 -23
- package/dist/docx/identities.d.ts +30 -0
- package/dist/docx/identities.js +43 -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 +13 -10
- package/dist/docx/invariants.js +300 -116
- package/dist/docx/newLists.js +12 -3
- 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 +113 -0
- package/dist/docx/notes/reading.d.ts +58 -0
- package/dist/docx/notes/reading.js +98 -0
- package/dist/docx/notes/references.d.ts +33 -0
- package/dist/docx/notes/references.js +37 -0
- package/dist/docx/notes/writing.d.ts +19 -0
- package/dist/docx/notes/writing.js +95 -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/serializePreserved.d.ts +5 -1
- package/dist/docx/serializePreserved.js +24 -7
- package/dist/docx/session.d.ts +5 -0
- package/dist/docx/session.js +6 -0
- package/dist/docx/storyParts.d.ts +133 -0
- package/dist/docx/storyParts.js +314 -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/exportQueries.d.ts +4 -3
- package/dist/editor/commands/index.d.ts +2 -1
- 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/index.d.ts +2 -2
- package/dist/numbering/spellers.d.ts +1 -1
- package/dist/numbering/spellers.js +6 -1
- package/dist/ooxml/errors.d.ts +135 -2
- package/dist/ooxml/errors.js +6 -0
- 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 +42 -2
- package/dist/schema/stories.js +28 -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
package/dist/ooxml/errors.d.ts
CHANGED
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Messages are English one-liners meant for developers. The `code` is the stable,
|
|
5
5
|
* machine-readable part: consumers switch on it to show their own localized text,
|
|
6
|
-
* so a code never changes meaning even when a message is reworded.
|
|
6
|
+
* so a code never changes meaning even when a message is reworded. An export refusal carries an
|
|
7
|
+
* `ExportProblemReason` besides, which tells the several situations one code covers apart and
|
|
8
|
+
* names what each is about, so a host can say which content to put back.
|
|
7
9
|
*/
|
|
8
10
|
/**
|
|
9
11
|
* Why a document could not be opened.
|
|
@@ -33,8 +35,139 @@ export declare class DocxImportError extends Error {
|
|
|
33
35
|
* - `invalid-table`: the table grid is inconsistent, e.g. a vertical merge outliving its rows
|
|
34
36
|
*/
|
|
35
37
|
export type DocxExportErrorCode = "missing-content-types" | "unsupported-content" | "lost-original" | "malformed-xml" | "invalid-table";
|
|
38
|
+
/**
|
|
39
|
+
* The kinds of side story a refusal can be about, which are the kinds a document holds beside its
|
|
40
|
+
* body (`schema/stories`). Spelled here because the reasons stand below the schema layer.
|
|
41
|
+
*/
|
|
42
|
+
export type ExportStoryKind = "comment" | "footnote" | "endnote" | "header" | "footer";
|
|
43
|
+
/**
|
|
44
|
+
* One side story, as the document holds it.
|
|
45
|
+
*
|
|
46
|
+
* A reason names it wherever the content the refusal is about may stand outside the body, and the
|
|
47
|
+
* refusal is then about that story rather than the document: `story` is null for the body itself.
|
|
48
|
+
*/
|
|
49
|
+
export interface ExportProblemStory {
|
|
50
|
+
readonly kind: ExportStoryKind;
|
|
51
|
+
/** A note's number, a comment's id, or the path of the part a header or footer stands in */
|
|
52
|
+
readonly id: string;
|
|
53
|
+
}
|
|
54
|
+
/** A part the export writes beside the body, named after what it holds */
|
|
55
|
+
export type ExportPartName = "media" | "numbering" | "comments" | "commentsExtended" | "people" | "footnotes" | "endnotes";
|
|
56
|
+
/**
|
|
57
|
+
* What a refusal is actually about.
|
|
58
|
+
*
|
|
59
|
+
* A `code` groups several situations - `unsupported-content` alone covers a list nothing defines,
|
|
60
|
+
* a block standing twice, and a change to a story no writer carries - and the `message` telling
|
|
61
|
+
* them apart is an English one-liner for a developer. This says which situation was met and names
|
|
62
|
+
* what it is about, so a host can write the sentence its own user reads. Switch on `kind`.
|
|
63
|
+
*/
|
|
64
|
+
export type ExportProblemReason =
|
|
65
|
+
/** A preserved fragment holding bookmark markup could not be parsed (`malformed-xml`) */
|
|
66
|
+
{
|
|
67
|
+
readonly kind: "unreadable-preserved-xml";
|
|
68
|
+
readonly story: ExportProblemStory | null;
|
|
69
|
+
}
|
|
70
|
+
/** A bookmark marker carries no id to pair it by (`malformed-xml`) */
|
|
71
|
+
| {
|
|
72
|
+
readonly kind: "unnamed-bookmark";
|
|
73
|
+
readonly marker: "start" | "end";
|
|
74
|
+
readonly story: ExportProblemStory | null;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* A bookmark marker whose partner is gone (`malformed-xml`): a `start` with no end after it, or
|
|
78
|
+
* an `end` with no start before it.
|
|
79
|
+
*/
|
|
80
|
+
| {
|
|
81
|
+
readonly kind: "unmatched-bookmark";
|
|
82
|
+
readonly id: string;
|
|
83
|
+
readonly marker: "start" | "end";
|
|
84
|
+
readonly story: ExportProblemStory | null;
|
|
85
|
+
}
|
|
86
|
+
/** One bookmark id is started twice (`malformed-xml`) */
|
|
87
|
+
| {
|
|
88
|
+
readonly kind: "repeated-bookmark-start";
|
|
89
|
+
readonly id: string;
|
|
90
|
+
readonly story: ExportProblemStory | null;
|
|
91
|
+
}
|
|
92
|
+
/** A part cannot be rewritten around its root element (`malformed-xml`) */
|
|
93
|
+
| {
|
|
94
|
+
readonly kind: "unwritable-part-root";
|
|
95
|
+
readonly part: ExportPartName;
|
|
96
|
+
}
|
|
97
|
+
/** A cell's vertical merge covers rows its table does not have (`invalid-table`) */
|
|
98
|
+
| {
|
|
99
|
+
readonly kind: "vertical-merge-past-table";
|
|
100
|
+
readonly story: ExportProblemStory | null;
|
|
101
|
+
}
|
|
102
|
+
/** A node that is written from its original XML alone no longer holds it (`lost-original`) */
|
|
103
|
+
| {
|
|
104
|
+
readonly kind: "lost-preserved-xml";
|
|
105
|
+
readonly node: string;
|
|
106
|
+
readonly story: ExportProblemStory | null;
|
|
107
|
+
}
|
|
108
|
+
/** A placeholder pasted in from another opened document, whose XML this session never read (`lost-original`) */
|
|
109
|
+
| {
|
|
110
|
+
readonly kind: "preserved-from-another-document";
|
|
111
|
+
readonly node: string;
|
|
112
|
+
/** The session the placeholder was opened in */
|
|
113
|
+
readonly sessionId: string;
|
|
114
|
+
readonly story: ExportProblemStory | null;
|
|
115
|
+
}
|
|
116
|
+
/** One preserved block stands in two places, and it has one original XML to be written (`unsupported-content`) */
|
|
117
|
+
| {
|
|
118
|
+
readonly kind: "duplicate-preserved-block";
|
|
119
|
+
readonly node: string;
|
|
120
|
+
readonly story: ExportProblemStory | null;
|
|
121
|
+
}
|
|
122
|
+
/** A paragraph is in a list neither the file nor the editor's register defines (`unsupported-content`) */
|
|
123
|
+
| {
|
|
124
|
+
readonly kind: "undefined-list";
|
|
125
|
+
readonly numId: number;
|
|
126
|
+
readonly story: ExportProblemStory | null;
|
|
127
|
+
}
|
|
128
|
+
/** A story was changed and no part writer carries that change into the file (`unsupported-content`) */
|
|
129
|
+
| {
|
|
130
|
+
readonly kind: "unwritten-story-change";
|
|
131
|
+
readonly story: ExportProblemStory;
|
|
132
|
+
readonly change: "added" | "edited" | "removed";
|
|
133
|
+
}
|
|
134
|
+
/** A story was added under an id its part identifies entries by no whole number of (`unsupported-content`) */
|
|
135
|
+
| {
|
|
136
|
+
readonly kind: "story-id-not-a-number";
|
|
137
|
+
readonly story: ExportProblemStory;
|
|
138
|
+
}
|
|
139
|
+
/** A part the writer has to add cannot be declared, the package having no content types part (`missing-content-types`) */
|
|
140
|
+
| {
|
|
141
|
+
readonly kind: "missing-content-types";
|
|
142
|
+
readonly part: ExportPartName;
|
|
143
|
+
};
|
|
144
|
+
/** One reason the document cannot be written back, with the code `exportDocx` would throw it under */
|
|
145
|
+
export interface ExportProblem {
|
|
146
|
+
readonly code: DocxExportErrorCode;
|
|
147
|
+
/** An English one-liner for a developer; reworded freely. Read `code` and `reason` instead */
|
|
148
|
+
readonly message: string;
|
|
149
|
+
/** Which of the situations the code covers was met, and what it is about */
|
|
150
|
+
readonly reason: ExportProblemReason;
|
|
151
|
+
/**
|
|
152
|
+
* Where the problem stands in the document: the block or marker it is about, or, for a footnote
|
|
153
|
+
* or an endnote, the first reference to that note in the body, the note's own text standing
|
|
154
|
+
* nowhere there. Absent for a problem of the package, of the session, of a header, footer or
|
|
155
|
+
* comment story, and of a note the body refers to nowhere.
|
|
156
|
+
*/
|
|
157
|
+
readonly pos?: number;
|
|
158
|
+
}
|
|
159
|
+
/** What a `DocxExportError` may be given beyond its code and message */
|
|
160
|
+
export interface DocxExportErrorOptions extends ErrorOptions {
|
|
161
|
+
/** The entry `exportProblems` reports for this refusal, where an invariant predicted it */
|
|
162
|
+
readonly problem?: ExportProblem;
|
|
163
|
+
}
|
|
36
164
|
/** Thrown when an edited document cannot be written back out without losing or corrupting content */
|
|
37
165
|
export declare class DocxExportError extends Error {
|
|
38
166
|
readonly code: DocxExportErrorCode;
|
|
39
|
-
|
|
167
|
+
/**
|
|
168
|
+
* What `exportProblems` reports for this refusal, carrying its `reason` and `pos`. Absent for a
|
|
169
|
+
* refusal met while writing that no invariant predicted.
|
|
170
|
+
*/
|
|
171
|
+
readonly problem?: ExportProblem;
|
|
172
|
+
constructor(code: DocxExportErrorCode, message: string, options?: DocxExportErrorOptions);
|
|
40
173
|
}
|
package/dist/ooxml/errors.js
CHANGED
|
@@ -9,10 +9,16 @@ var DocxImportError = class extends Error {
|
|
|
9
9
|
};
|
|
10
10
|
var DocxExportError = class extends Error {
|
|
11
11
|
code;
|
|
12
|
+
/**
|
|
13
|
+
* What `exportProblems` reports for this refusal, carrying its `reason` and `pos`. Absent for a
|
|
14
|
+
* refusal met while writing that no invariant predicted.
|
|
15
|
+
*/
|
|
16
|
+
problem;
|
|
12
17
|
constructor(code, message, options) {
|
|
13
18
|
super(message, options);
|
|
14
19
|
this.name = "DocxExportError";
|
|
15
20
|
this.code = code;
|
|
21
|
+
this.problem = options?.problem;
|
|
16
22
|
}
|
|
17
23
|
};
|
|
18
24
|
export {
|
|
@@ -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
|
};
|