@portone/docx-editor 0.4.0 → 0.5.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 +201 -0
- package/CONTRIBUTING.md +2 -0
- package/assets/editor.png +0 -0
- package/dist/DocxEditor.d.ts +1 -1
- package/dist/DocxEditor.js +44 -25
- package/dist/docx/cloning.js +4 -9
- package/dist/docx/commentOnlyChange.d.ts +5 -3
- package/dist/docx/comments/grammar.d.ts +27 -10
- package/dist/docx/comments/grammar.js +18 -67
- package/dist/docx/comments/model.d.ts +5 -6
- package/dist/docx/comments/model.js +0 -9
- package/dist/docx/comments/parts.d.ts +16 -10
- package/dist/docx/comments/parts.js +65 -13
- package/dist/docx/comments/people.d.ts +11 -3
- package/dist/docx/comments/people.js +20 -6
- package/dist/docx/comments/policy.js +7 -1
- package/dist/docx/comments/reading.d.ts +4 -2
- package/dist/docx/comments/reading.js +1 -14
- package/dist/docx/comments/writing.js +65 -28
- package/dist/docx/exportDocx.js +10 -14
- package/dist/docx/exportRefs.d.ts +8 -3
- package/dist/docx/exportRefs.js +2 -1
- package/dist/docx/fidelity.js +24 -10
- package/dist/docx/fields.d.ts +42 -0
- package/dist/docx/fields.js +76 -0
- package/dist/docx/formatting/styles.d.ts +0 -2
- package/dist/docx/formatting/styles.js +2 -15
- package/dist/docx/headersFooters.d.ts +89 -20
- package/dist/docx/headersFooters.js +163 -172
- package/dist/docx/identities.js +17 -26
- package/dist/docx/importDocx.d.ts +1 -1
- package/dist/docx/importDocx.js +100 -48
- package/dist/docx/importParagraph.d.ts +18 -5
- package/dist/docx/importParagraph.js +75 -128
- package/dist/docx/importPolicy.d.ts +70 -0
- package/dist/docx/importPolicy.js +195 -0
- package/dist/docx/importPreserved.d.ts +30 -0
- package/dist/docx/importPreserved.js +54 -0
- package/dist/docx/importTable.js +56 -12
- package/dist/docx/invariants.js +16 -18
- package/dist/docx/notes.d.ts +6 -1
- package/dist/docx/notes.js +6 -16
- package/dist/docx/pageGeometry.d.ts +5 -14
- package/dist/docx/pageGeometry.js +0 -12
- package/dist/docx/protectionPolicy.d.ts +13 -5
- package/dist/docx/protectionPolicy.js +32 -22
- package/dist/docx/scan.d.ts +18 -8
- package/dist/docx/scan.js +17 -11
- package/dist/docx/sections.d.ts +126 -0
- package/dist/docx/sections.js +207 -0
- package/dist/docx/serializeBlock.d.ts +13 -4
- package/dist/docx/serializeBlock.js +16 -18
- package/dist/docx/serializeParagraph.d.ts +9 -4
- package/dist/docx/serializeParagraph.js +23 -58
- package/dist/docx/serializePreserved.d.ts +14 -0
- package/dist/docx/serializePreserved.js +24 -0
- package/dist/docx/serializeStory.d.ts +17 -0
- package/dist/docx/serializeStory.js +17 -0
- package/dist/docx/serializeTable.js +10 -13
- package/dist/docx/session.d.ts +23 -6
- package/dist/docx/session.js +26 -5
- package/dist/docx/story.d.ts +140 -0
- package/dist/docx/story.js +237 -0
- package/dist/docx/storyProjection.js +6 -1
- package/dist/docx/tableTemplate.d.ts +25 -0
- package/dist/docx/tableTemplate.js +30 -16
- package/dist/docx/wrappers.d.ts +67 -0
- package/dist/docx/wrappers.js +117 -0
- package/dist/editor/clipboard/blockReaders.d.ts +74 -0
- package/dist/editor/clipboard/blockReaders.js +193 -0
- package/dist/editor/clipboard/htmlReader.d.ts +26 -0
- package/dist/editor/clipboard/htmlReader.js +376 -0
- package/dist/editor/clipboard/inlineFormatting.d.ts +0 -7
- package/dist/editor/clipboard/inlineFormatting.js +1 -7
- package/dist/editor/clipboard/internalChannel.d.ts +57 -0
- package/dist/editor/clipboard/internalChannel.js +58 -0
- package/dist/editor/clipboard/normalizers.d.ts +99 -0
- package/dist/editor/clipboard/normalizers.js +199 -0
- package/dist/editor/clipboard/parser.d.ts +34 -0
- package/dist/editor/clipboard/parser.js +58 -0
- package/dist/editor/clipboard/plugin.d.ts +21 -0
- package/dist/editor/clipboard/plugin.js +188 -0
- package/dist/editor/clipboard/readContext.d.ts +34 -0
- package/dist/editor/clipboard/readContext.js +25 -0
- package/dist/editor/clipboard/readers.d.ts +33 -0
- package/dist/editor/clipboard/readers.js +37 -0
- package/dist/editor/clipboard/source.d.ts +12 -0
- package/dist/editor/clipboard/source.js +21 -0
- package/dist/editor/commands/comments/editing.d.ts +26 -6
- package/dist/editor/commands/comments/editing.js +177 -106
- package/dist/editor/commands/comments/model.d.ts +34 -17
- package/dist/editor/commands/comments/model.js +11 -0
- package/dist/editor/commands/comments/reading.d.ts +5 -3
- package/dist/editor/commands/comments/reading.js +8 -58
- package/dist/editor/commands/index.d.ts +2 -2
- package/dist/editor/commands/index.js +2 -0
- package/dist/editor/commands/linkCommands.js +20 -5
- package/dist/editor/commands/listCommands.d.ts +6 -0
- package/dist/editor/commands/listCommands.js +3 -2
- package/dist/editor/commands/lockCommands.js +59 -16
- package/dist/editor/commands/noteQueries.d.ts +22 -5
- package/dist/editor/commands/noteQueries.js +34 -6
- package/dist/editor/commands/paragraphCommands.js +2 -2
- package/dist/editor/createEditor.js +13 -3
- package/dist/editor/documentStyles.d.ts +26 -4
- package/dist/editor/documentStyles.js +9 -4
- package/dist/editor/imageFiles.d.ts +2 -2
- package/dist/editor/imageFiles.js +2 -0
- package/dist/editor/insertTable.d.ts +1 -1
- package/dist/editor/insertTable.js +2 -2
- package/dist/editor/plainText.d.ts +12 -0
- package/dist/editor/plainText.js +2 -1
- package/dist/editor/plugins/columnResize.js +6 -3
- package/dist/editor/plugins/commentComposer.d.ts +28 -0
- package/dist/editor/plugins/commentComposer.js +46 -0
- package/dist/editor/plugins/commentDecorations.d.ts +29 -2
- package/dist/editor/plugins/commentDecorations.js +89 -15
- package/dist/editor/plugins/documentProjection.d.ts +31 -0
- package/dist/editor/plugins/documentProjection.js +22 -0
- package/dist/editor/plugins/imagePaste.js +15 -8
- package/dist/editor/plugins/linkPanel.d.ts +7 -3
- package/dist/editor/plugins/linkPanel.js +13 -27
- package/dist/editor/plugins/panelState.d.ts +67 -0
- package/dist/editor/plugins/panelState.js +54 -0
- package/dist/editor/plugins/tableContextMenu.d.ts +1 -1
- package/dist/editor/plugins/tableContextMenu.js +30 -47
- package/dist/editor/plugins/textContextMenu.d.ts +1 -1
- package/dist/editor/plugins/textContextMenu.js +30 -47
- package/dist/ooxml/fragment.d.ts +11 -0
- package/dist/ooxml/fragment.js +14 -4
- package/dist/ooxml/image.d.ts +3 -4
- package/dist/ooxml/image.js +50 -5
- package/dist/ooxml/names.d.ts +6 -0
- package/dist/ooxml/names.js +2 -0
- package/dist/ooxml/props.d.ts +10 -0
- package/dist/ooxml/props.js +28 -4
- package/dist/ooxml/rangeMarkers.d.ts +5 -0
- package/dist/ooxml/rangeMarkers.js +24 -0
- package/dist/page/PageGuides.d.ts +7 -3
- package/dist/page/PageGuides.js +13 -24
- package/dist/page/pageLayout.d.ts +64 -5
- package/dist/page/pageLayout.js +90 -29
- package/dist/page/usePageLayout.d.ts +14 -4
- package/dist/page/usePageLayout.js +19 -20
- package/dist/schema/attrRoles.js +46 -23
- package/dist/schema/clipboard.d.ts +72 -0
- package/dist/schema/clipboard.js +288 -0
- package/dist/schema/docxSchema.d.ts +9 -1
- package/dist/schema/docxSchema.js +217 -165
- package/dist/schema/editGuard.d.ts +1 -1
- package/dist/schema/guards.js +2 -2
- package/dist/schema/index.d.ts +1 -0
- package/dist/schema/locks.d.ts +3 -0
- package/dist/schema/locks.js +29 -12
- package/dist/schema/preservedFragments.d.ts +12 -0
- package/dist/schema/preservedFragments.js +38 -0
- package/dist/schema/preservedGuards.d.ts +9 -7
- package/dist/schema/preservedGuards.js +112 -11
- package/dist/schema/protection.d.ts +34 -5
- package/dist/schema/protection.js +42 -14
- package/dist/schema/rendering.d.ts +14 -0
- package/dist/schema/rendering.js +27 -12
- package/dist/schema/stories.d.ts +49 -0
- package/dist/schema/stories.js +78 -0
- package/dist/schema/wrappers.d.ts +58 -0
- package/dist/schema/wrappers.js +77 -0
- package/dist/styles/classNames.d.ts +3 -5
- package/dist/styles/classNames.js +3 -5
- package/dist/styles.css +74 -12
- package/dist/table/resize.d.ts +5 -3
- package/dist/ui/CommentsPanel.d.ts +1 -3
- package/dist/ui/CommentsPanel.js +24 -15
- package/dist/ui/NotesPanel.js +2 -2
- package/dist/ui/TextMenu.d.ts +1 -3
- package/dist/ui/TextMenu.js +10 -6
- package/dist/ui/comments/CommentComposer.d.ts +6 -1
- package/dist/ui/comments/CommentComposer.js +26 -3
- package/package.json +5 -4
- package/dist/editor/externalClipboard.d.ts +0 -22
- package/dist/editor/externalClipboard.js +0 -486
|
@@ -7,9 +7,17 @@
|
|
|
7
7
|
* the places where Word actually breaks may differ.
|
|
8
8
|
*
|
|
9
9
|
* The size of a page is the document's own (`docx/pageGeometry`), not a fixed A4: a Letter
|
|
10
|
-
* document breaks at Letter's height and fits its tables to Letter's width.
|
|
10
|
+
* document breaks at Letter's height and fits its tables to Letter's width. A document written in
|
|
11
|
+
* several sections breaks each block at the height of the section that block sits in
|
|
12
|
+
* (`docx/sections`), and a section opens a page of its own unless it declares `continuous`, which
|
|
13
|
+
* carries it on down the page the section before it ends on.
|
|
14
|
+
*
|
|
15
|
+
* The width is the exception: one sheet is drawn at one width (`styles/editor.css`), which is the
|
|
16
|
+
* first section's, so a section on wider paper is paginated on its own height but drawn on the
|
|
17
|
+
* paper the sheet holds.
|
|
11
18
|
*/
|
|
12
19
|
import { type PageGeometry } from "../docx/pageGeometry";
|
|
20
|
+
import type { DocumentSection, SectionProperties } from "../docx/sections";
|
|
13
21
|
import type { MeasuredBlock, PageCut } from "./blockKinds";
|
|
14
22
|
export type { MeasuredBlock };
|
|
15
23
|
/** The paper measured in the pixels the sheet is drawn with */
|
|
@@ -39,6 +47,35 @@ export declare const PAGE_SPLIT_PX = 24;
|
|
|
39
47
|
export declare function pagePixels(geometry: PageGeometry): PagePixels;
|
|
40
48
|
/** The paper a document that names none is drawn on */
|
|
41
49
|
export declare const A4_PAGE_PIXELS: PagePixels;
|
|
50
|
+
/** The paper one section of a document is laid out on */
|
|
51
|
+
export interface SectionPixels {
|
|
52
|
+
/**
|
|
53
|
+
* The last position this section covers. A section is closed by the paragraph carrying its
|
|
54
|
+
* `w:sectPr` (§17.6.17), so the block after that one opens the section that follows, and the
|
|
55
|
+
* section the body itself closes reaches past every block.
|
|
56
|
+
*/
|
|
57
|
+
untilPos: number;
|
|
58
|
+
pixels: PagePixels;
|
|
59
|
+
/**
|
|
60
|
+
* How this section starts (§17.6.22). `continuous` carries it on down the page the section
|
|
61
|
+
* before it ends on; every other kind opens a page.
|
|
62
|
+
*/
|
|
63
|
+
type: SectionProperties["type"];
|
|
64
|
+
}
|
|
65
|
+
/** The one section a document naming none is laid out on */
|
|
66
|
+
export declare const A4_SECTION_PIXELS: readonly SectionPixels[];
|
|
67
|
+
/**
|
|
68
|
+
* The paper of every section of a document, in the pixels the sheet is drawn with.
|
|
69
|
+
*
|
|
70
|
+
* Reading the sections walks every block (`docx/sections`), so this runs once per document change
|
|
71
|
+
* rather than once per measurement.
|
|
72
|
+
*/
|
|
73
|
+
export declare function sectionPixels(sections: readonly DocumentSection[]): SectionPixels[];
|
|
74
|
+
/**
|
|
75
|
+
* The paper the block at this position is laid out on, which is the paper of every page that
|
|
76
|
+
* block stands on. A4 where the list names no section at all.
|
|
77
|
+
*/
|
|
78
|
+
export declare function sectionPaperAt(sections: readonly SectionPixels[], pos: number): PagePixels;
|
|
42
79
|
/**
|
|
43
80
|
* The paper the document names, as the CSS variables the sheet is drawn from.
|
|
44
81
|
*
|
|
@@ -74,6 +111,17 @@ export interface PageStart {
|
|
|
74
111
|
page: number;
|
|
75
112
|
/** Where this page's body starts, measured from the top of the body */
|
|
76
113
|
bodyStart: number;
|
|
114
|
+
/**
|
|
115
|
+
* The position of the block this page opens with, which is the block the text crossing into it
|
|
116
|
+
* belongs to. 0 for a document holding no block at all
|
|
117
|
+
*/
|
|
118
|
+
pos: number;
|
|
119
|
+
/**
|
|
120
|
+
* The place this page takes within its own section, counted from 1 again at every section it
|
|
121
|
+
* opens. A document read with no section table at all is one section, so this counts the whole
|
|
122
|
+
* of it
|
|
123
|
+
*/
|
|
124
|
+
pageInSection: number;
|
|
77
125
|
/**
|
|
78
126
|
* Whether text crosses over from the previous page, so this page continues with no top
|
|
79
127
|
* margin
|
|
@@ -90,12 +138,21 @@ export interface PageLayout {
|
|
|
90
138
|
pages: PageStart[];
|
|
91
139
|
/** The body height with the last page filled out in full */
|
|
92
140
|
bodyHeight: number;
|
|
141
|
+
/**
|
|
142
|
+
* The margin under that last page, which is its own section's. The sheet is padded from the
|
|
143
|
+
* first section's paper alone (`editor/createEditor`), so a document ending on a deeper bottom
|
|
144
|
+
* margin needs the sheet drawn down to this.
|
|
145
|
+
*/
|
|
146
|
+
marginBottom: number;
|
|
93
147
|
}
|
|
94
148
|
export interface PageLayoutInput {
|
|
95
149
|
blocks: readonly MeasuredBlock[];
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
150
|
+
/**
|
|
151
|
+
* The paper of each section in document order (`sectionPixels`). A block is laid out on the
|
|
152
|
+
* paper of the section it sits in, and the section a block opens starts a page of its own
|
|
153
|
+
* unless that section is `continuous`.
|
|
154
|
+
*/
|
|
155
|
+
sections: readonly SectionPixels[];
|
|
99
156
|
}
|
|
100
157
|
/**
|
|
101
158
|
* Distributes the blocks across pages.
|
|
@@ -110,5 +167,7 @@ export interface PageLayoutInput {
|
|
|
110
167
|
* a row boundary in a table - is taken only where the piece after it would run off the page.
|
|
111
168
|
* A block kept with the next one is pushed together with what it is kept with: the blocks kept
|
|
112
169
|
* after it and the first piece of the block the keeps end at.
|
|
170
|
+
* Each block is measured against the paper of its own section, and the first block of a section
|
|
171
|
+
* opens a page of its own unless the section it opens declares `continuous`.
|
|
113
172
|
*/
|
|
114
|
-
export declare function pageLayout({ blocks,
|
|
173
|
+
export declare function pageLayout({ blocks, sections }: PageLayoutInput): PageLayout;
|
package/dist/page/pageLayout.js
CHANGED
|
@@ -24,6 +24,23 @@ function pagePixels(geometry) {
|
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
26
|
var A4_PAGE_PIXELS = pagePixels(A4_PORTRAIT);
|
|
27
|
+
var A4_SECTION_PIXELS = [
|
|
28
|
+
{ untilPos: Number.POSITIVE_INFINITY, pixels: A4_PAGE_PIXELS, type: null }
|
|
29
|
+
];
|
|
30
|
+
function sectionPixels(sections) {
|
|
31
|
+
return sections.map(({ anchor, props }) => ({
|
|
32
|
+
untilPos: anchor.kind === "paragraph" ? anchor.pos : Number.POSITIVE_INFINITY,
|
|
33
|
+
pixels: pagePixels(props.geometry),
|
|
34
|
+
type: props.type
|
|
35
|
+
}));
|
|
36
|
+
}
|
|
37
|
+
function sectionIndexAt(sections, pos) {
|
|
38
|
+
const reaching = sections.findIndex((section) => pos <= section.untilPos);
|
|
39
|
+
return reaching === -1 ? sections.length - 1 : reaching;
|
|
40
|
+
}
|
|
41
|
+
function sectionPaperAt(sections, pos) {
|
|
42
|
+
return sections[sectionIndexAt(sections, pos)]?.pixels ?? A4_PAGE_PIXELS;
|
|
43
|
+
}
|
|
27
44
|
function px(value) {
|
|
28
45
|
return `${Math.round(value * 100) / 100}px`;
|
|
29
46
|
}
|
|
@@ -41,15 +58,29 @@ var TOLERANCE_PX = 0.5;
|
|
|
41
58
|
function round(value) {
|
|
42
59
|
return Math.round(value * 100) / 100;
|
|
43
60
|
}
|
|
44
|
-
function
|
|
45
|
-
|
|
61
|
+
function withinSections(pages, sections) {
|
|
62
|
+
let section = null;
|
|
63
|
+
let within = 0;
|
|
64
|
+
return pages.map((page) => {
|
|
65
|
+
const index = sectionIndexAt(sections, page.pos);
|
|
66
|
+
within = index === section ? within + 1 : 1;
|
|
67
|
+
section = index;
|
|
68
|
+
return { ...page, pageInSection: within };
|
|
69
|
+
});
|
|
46
70
|
}
|
|
47
|
-
function
|
|
71
|
+
function keepsWithNext(block, next, opensPage) {
|
|
72
|
+
return block.keepWithNext && !opensPage && block.candidates.length === 0 && !block.breakAfter && !next.breakBefore;
|
|
73
|
+
}
|
|
74
|
+
function keptExtents(blocks, laid) {
|
|
75
|
+
const fitsPage = (extent, index) => {
|
|
76
|
+
const height = laid[index]?.paper.bodyHeight ?? 0;
|
|
77
|
+
return extent <= height + TOLERANCE_PX;
|
|
78
|
+
};
|
|
48
79
|
const extents = /* @__PURE__ */ new Map();
|
|
49
80
|
let run = [];
|
|
50
81
|
const settle = () => {
|
|
51
82
|
const head = run.at(-1);
|
|
52
|
-
if (head && fitsPage(head.extent)) {
|
|
83
|
+
if (head && fitsPage(head.extent, head.index)) {
|
|
53
84
|
for (const { index, extent } of run) {
|
|
54
85
|
extents.set(index, extent);
|
|
55
86
|
}
|
|
@@ -59,7 +90,8 @@ function keptExtents(blocks, fitsPage) {
|
|
|
59
90
|
for (let index = blocks.length - 2; index >= 0; index -= 1) {
|
|
60
91
|
const block = blocks[index];
|
|
61
92
|
const next = blocks[index + 1];
|
|
62
|
-
|
|
93
|
+
const opensPage = laid[index + 1]?.opensPage === true;
|
|
94
|
+
if (block && next && keepsWithNext(block, next, opensPage)) {
|
|
63
95
|
const below = run.at(-1)?.extent ?? next.minFirstPiece;
|
|
64
96
|
run.push({ index, extent: block.height + next.gap + below });
|
|
65
97
|
} else {
|
|
@@ -69,44 +101,67 @@ function keptExtents(blocks, fitsPage) {
|
|
|
69
101
|
settle();
|
|
70
102
|
return extents;
|
|
71
103
|
}
|
|
72
|
-
function pageLayout({
|
|
73
|
-
blocks,
|
|
74
|
-
pageBodyHeight,
|
|
75
|
-
pageStep
|
|
76
|
-
}) {
|
|
104
|
+
function pageLayout({ blocks, sections }) {
|
|
77
105
|
const pushes = [];
|
|
78
106
|
const cuts = [];
|
|
79
107
|
const splits = [];
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
108
|
+
let blockPos = blocks[0]?.pos ?? 0;
|
|
109
|
+
const opened = [
|
|
110
|
+
{ page: 1, bodyStart: 0, pos: blockPos, crossed: false }
|
|
111
|
+
];
|
|
112
|
+
const roomy = sections.find(
|
|
113
|
+
(section) => section.pixels.bodyHeight > 0
|
|
114
|
+
)?.pixels;
|
|
115
|
+
if (roomy === void 0) {
|
|
116
|
+
return {
|
|
117
|
+
pushes,
|
|
118
|
+
cuts,
|
|
119
|
+
splits,
|
|
120
|
+
pages: withinSections(opened, sections),
|
|
121
|
+
bodyHeight: 0,
|
|
122
|
+
marginBottom: 0
|
|
123
|
+
};
|
|
84
124
|
}
|
|
125
|
+
const blockSections = blocks.map(
|
|
126
|
+
(block) => sectionIndexAt(sections, block.pos)
|
|
127
|
+
);
|
|
128
|
+
const laid = blockSections.map((section, index) => {
|
|
129
|
+
const pixels = sections[section]?.pixels;
|
|
130
|
+
return {
|
|
131
|
+
paper: pixels && pixels.bodyHeight > 0 ? pixels : roomy,
|
|
132
|
+
opensPage: index > 0 && section !== blockSections[index - 1] && sections[section]?.type !== "continuous"
|
|
133
|
+
};
|
|
134
|
+
});
|
|
85
135
|
let pageStart = 0;
|
|
86
136
|
let cursor = 0;
|
|
87
|
-
|
|
137
|
+
let paper = laid[0]?.paper ?? roomy;
|
|
138
|
+
const stepTo = (opening) => paper.pageStep - paper.marginTop + opening.marginTop;
|
|
139
|
+
const split = (y, crossed, forced, opening = paper) => {
|
|
88
140
|
const page = splits.length + 2;
|
|
89
141
|
splits.push({ y: round(y), page, forced, crossed });
|
|
90
|
-
pageStart = crossed ? y : y +
|
|
91
|
-
|
|
142
|
+
pageStart = crossed ? y : y + stepTo(opening);
|
|
143
|
+
opened.push({ page, bodyStart: round(pageStart), pos: blockPos, crossed });
|
|
92
144
|
};
|
|
93
145
|
const crossTo = (y) => {
|
|
94
|
-
while (y > pageStart +
|
|
95
|
-
split(pageStart +
|
|
146
|
+
while (y > pageStart + paper.bodyHeight + TOLERANCE_PX) {
|
|
147
|
+
split(pageStart + paper.bodyHeight, true, false);
|
|
96
148
|
}
|
|
97
149
|
};
|
|
98
|
-
const
|
|
99
|
-
const kept = keptExtents(blocks, fitsPage);
|
|
150
|
+
const kept = keptExtents(blocks, laid);
|
|
100
151
|
let breakAfterPrevious = false;
|
|
101
152
|
for (const [index, block] of blocks.entries()) {
|
|
102
|
-
|
|
153
|
+
blockPos = block.pos;
|
|
154
|
+
const opening = laid[index]?.paper ?? paper;
|
|
155
|
+
const startsSection = laid[index]?.opensPage === true;
|
|
156
|
+
const pageEnd = pageStart + paper.bodyHeight;
|
|
103
157
|
const top = cursor + block.gap;
|
|
104
|
-
const startsPage = (block.breakBefore || breakAfterPrevious) && top > pageStart + TOLERANCE_PX;
|
|
158
|
+
const startsPage = (block.breakBefore || breakAfterPrevious || startsSection) && top > pageStart + TOLERANCE_PX;
|
|
105
159
|
const first = kept.get(index) ?? block.minFirstPiece;
|
|
106
160
|
const overflows = top + first > pageEnd + TOLERANCE_PX;
|
|
107
|
-
const fits =
|
|
108
|
-
const push = startsPage || overflows && fits ? Math.max(0, pageEnd +
|
|
109
|
-
if (push > 0 || startsPage) split(pageEnd, false, startsPage);
|
|
161
|
+
const fits = first <= opening.bodyHeight + TOLERANCE_PX;
|
|
162
|
+
const push = startsPage || overflows && fits ? Math.max(0, pageEnd + stepTo(opening) - top) : 0;
|
|
163
|
+
if (push > 0 || startsPage) split(pageEnd, false, startsPage, opening);
|
|
164
|
+
paper = opening;
|
|
110
165
|
if (push > 0) {
|
|
111
166
|
pushes.push({
|
|
112
167
|
pos: block.pos,
|
|
@@ -115,6 +170,7 @@ function pageLayout({
|
|
|
115
170
|
});
|
|
116
171
|
}
|
|
117
172
|
const contentTop = top + push;
|
|
173
|
+
const pageBodyHeight = paper.bodyHeight;
|
|
118
174
|
let added = 0;
|
|
119
175
|
let pieceStart = 0;
|
|
120
176
|
for (let index2 = 0; index2 <= block.candidates.length; index2 += 1) {
|
|
@@ -144,14 +200,19 @@ function pageLayout({
|
|
|
144
200
|
pushes,
|
|
145
201
|
cuts,
|
|
146
202
|
splits,
|
|
147
|
-
pages,
|
|
148
|
-
|
|
203
|
+
pages: withinSections(opened, sections),
|
|
204
|
+
// The last page is filled out in full on the paper of the section it opens with
|
|
205
|
+
bodyHeight: round(pageStart + paper.bodyHeight),
|
|
206
|
+
marginBottom: paper.marginBottom
|
|
149
207
|
};
|
|
150
208
|
}
|
|
151
209
|
export {
|
|
152
210
|
A4_PAGE_PIXELS,
|
|
211
|
+
A4_SECTION_PIXELS,
|
|
153
212
|
PAGE_SPLIT_PX,
|
|
154
213
|
pageGeometryStyle,
|
|
155
214
|
pageLayout,
|
|
156
|
-
pagePixels
|
|
215
|
+
pagePixels,
|
|
216
|
+
sectionPaperAt,
|
|
217
|
+
sectionPixels
|
|
157
218
|
};
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import type { EditorView } from "prosemirror-view";
|
|
12
12
|
import { type RefObject } from "react";
|
|
13
|
-
import type
|
|
13
|
+
import { type SectionPixels } from "./pageLayout";
|
|
14
14
|
/** One place where a page parts from the next. The position is measured on the sheet */
|
|
15
15
|
export interface PageMark {
|
|
16
16
|
page: number;
|
|
@@ -22,6 +22,13 @@ export interface PageMark {
|
|
|
22
22
|
/** The paper area of one visual page, used to place its header and footer stories. */
|
|
23
23
|
export interface PageFace {
|
|
24
24
|
page: number;
|
|
25
|
+
/** The position of the block this page opens with, which says which section it belongs to */
|
|
26
|
+
pos: number;
|
|
27
|
+
/**
|
|
28
|
+
* The place this page takes within that section, counted from 1 again at every section, which is
|
|
29
|
+
* what its header and footer variant is chosen by (`docx/headersFooters`)
|
|
30
|
+
*/
|
|
31
|
+
pageInSection: number;
|
|
25
32
|
headerTop: number;
|
|
26
33
|
footerTop: number;
|
|
27
34
|
left: number;
|
|
@@ -43,8 +50,11 @@ interface PageLayoutOptions {
|
|
|
43
50
|
enabled: boolean;
|
|
44
51
|
/** A value that differs every time the text changes */
|
|
45
52
|
revision: unknown;
|
|
46
|
-
/**
|
|
47
|
-
|
|
53
|
+
/**
|
|
54
|
+
* The paper of each section of the open document (`page/pageLayout`), read once per document
|
|
55
|
+
* change by the caller. A4 where a document names none, and where none is open
|
|
56
|
+
*/
|
|
57
|
+
sections?: readonly SectionPixels[];
|
|
48
58
|
}
|
|
49
|
-
export declare function usePageLayout({ view, layer, enabled, revision,
|
|
59
|
+
export declare function usePageLayout({ view, layer, enabled, revision, sections, }: PageLayoutOptions): PageOverlay | null;
|
|
50
60
|
export {};
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import {
|
|
3
3
|
useCallback,
|
|
4
4
|
useEffect,
|
|
5
|
-
useMemo,
|
|
6
5
|
useRef,
|
|
7
6
|
useState
|
|
8
7
|
} from "react";
|
|
@@ -10,10 +9,10 @@ import { editorCssVariables } from "../styles/classNames.js";
|
|
|
10
9
|
import { measureSheet } from "./measureBlocks.js";
|
|
11
10
|
import { setPageMarks } from "./pageDecorations.js";
|
|
12
11
|
import {
|
|
13
|
-
|
|
12
|
+
A4_SECTION_PIXELS,
|
|
14
13
|
PAGE_SPLIT_PX,
|
|
15
14
|
pageLayout,
|
|
16
|
-
|
|
15
|
+
sectionPaperAt
|
|
17
16
|
} from "./pageLayout.js";
|
|
18
17
|
function useFrameThrottle(run, hold) {
|
|
19
18
|
const latest = useRef({ run, hold });
|
|
@@ -52,46 +51,46 @@ function usePageLayout({
|
|
|
52
51
|
layer,
|
|
53
52
|
enabled,
|
|
54
53
|
revision,
|
|
55
|
-
|
|
54
|
+
sections
|
|
56
55
|
}) {
|
|
57
56
|
const [overlay, setOverlay] = useState(null);
|
|
58
|
-
const
|
|
59
|
-
() => geometry ? pagePixels(geometry) : A4_PAGE_PIXELS,
|
|
60
|
-
[geometry]
|
|
61
|
-
);
|
|
57
|
+
const papers = sections === void 0 || sections.length === 0 ? A4_SECTION_PIXELS : sections;
|
|
62
58
|
const remeasure = useFrameThrottle(
|
|
63
59
|
() => {
|
|
64
60
|
const box = layer.current;
|
|
65
61
|
if (!view || !box || !enabled) return;
|
|
66
62
|
const measured = measureSheet(view, box);
|
|
67
|
-
const layout = pageLayout({
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
pageStep: page.pageStep
|
|
71
|
-
});
|
|
63
|
+
const layout = pageLayout({ blocks: measured.blocks, sections: papers });
|
|
64
|
+
const paperOf = (pos) => sectionPaperAt(papers, pos);
|
|
65
|
+
const sheet = paperOf(0);
|
|
72
66
|
setPageMarks(view, { pushes: layout.pushes, cuts: layout.cuts });
|
|
73
|
-
const sheetHeight = measured.contentTop + layout.bodyHeight + measured.contentBottom;
|
|
67
|
+
const sheetHeight = measured.contentTop + layout.bodyHeight + Math.max(measured.contentBottom, layout.marginBottom);
|
|
74
68
|
box.style.setProperty(editorCssVariables.sheetHeight, `${sheetHeight}px`);
|
|
75
69
|
const next = {
|
|
76
70
|
left: measured.left,
|
|
77
71
|
top: measured.top,
|
|
78
72
|
width: measured.width,
|
|
79
73
|
sheetHeight,
|
|
74
|
+
// A gap opens below the page that ends at this place, so it is that page's own margin
|
|
75
|
+
// the band is drawn under
|
|
80
76
|
marks: layout.splits.map((split) => ({
|
|
81
77
|
page: split.page,
|
|
82
|
-
top: measured.contentTop + split.y + (split.crossed ? 0 : page.marginBottom),
|
|
78
|
+
top: measured.contentTop + split.y + (split.crossed ? 0 : paperOf(layout.pages[split.page - 2]?.pos ?? 0).marginBottom),
|
|
83
79
|
height: split.crossed ? 0 : PAGE_SPLIT_PX,
|
|
84
80
|
crossed: split.crossed
|
|
85
81
|
})),
|
|
86
82
|
// A page crossed into has no margin, so the place it was split at is its top corner
|
|
87
83
|
pages: layout.pages.map((start) => {
|
|
88
|
-
const
|
|
84
|
+
const paper = paperOf(start.pos);
|
|
85
|
+
const paperTop = measured.contentTop + start.bodyStart - (start.crossed ? 0 : paper.marginTop);
|
|
89
86
|
return {
|
|
90
87
|
page: start.page,
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
88
|
+
pos: start.pos,
|
|
89
|
+
pageInSection: start.pageInSection,
|
|
90
|
+
headerTop: paperTop + paper.marginTop / 2,
|
|
91
|
+
footerTop: paperTop + paper.pageHeight - paper.marginBottom / 2,
|
|
92
|
+
left: sheet.marginLeft,
|
|
93
|
+
width: sheet.bodyWidth,
|
|
95
94
|
crossed: start.crossed
|
|
96
95
|
};
|
|
97
96
|
})
|
package/dist/schema/attrRoles.js
CHANGED
|
@@ -4,7 +4,13 @@ var NODE_ATTR_ROLES = {
|
|
|
4
4
|
doc: {
|
|
5
5
|
// The definitions of the lists started while editing. The export writes numbering.xml from
|
|
6
6
|
// them and nothing derives them again, so they are a source value the document node holds
|
|
7
|
-
newLists: { role: "source", class: "model" }
|
|
7
|
+
newLists: { role: "source", class: "model" },
|
|
8
|
+
// The section that closes the body, as the document wrote it. The export writes it back out
|
|
9
|
+
// between the blocks and the tail, so a change to it is a change to the document
|
|
10
|
+
sectPr: { role: "source", class: "preserved" },
|
|
11
|
+
// What each side story currently says. The comment writer puts a comment's back into the
|
|
12
|
+
// Comments part, so a change to one is a change to the document
|
|
13
|
+
stories: { role: "source", class: "model" }
|
|
8
14
|
},
|
|
9
15
|
paragraph: {
|
|
10
16
|
srcId: { role: "session", class: "identity" },
|
|
@@ -25,13 +31,18 @@ var NODE_ATTR_ROLES = {
|
|
|
25
31
|
styleInside: { role: "display", class: "derived" },
|
|
26
32
|
styleCellMargins: { role: "display", class: "derived" },
|
|
27
33
|
styleConditions: { role: "display", class: "derived" },
|
|
28
|
-
styleBands: { role: "display", class: "derived" }
|
|
34
|
+
styleBands: { role: "display", class: "derived" },
|
|
35
|
+
// The markers a table and a row carry are content the writer puts back exactly where it
|
|
36
|
+
// stood, so a change to one is a change to the table rather than a display refresh
|
|
37
|
+
leadingXml: { role: "source", class: "preserved" }
|
|
29
38
|
},
|
|
30
39
|
tableRow: {
|
|
31
40
|
trAttrs: { role: "source", class: "preserved" },
|
|
32
41
|
tblPrEx: { role: "source", class: "preserved" },
|
|
33
42
|
trPr: { role: "source", class: "preserved" },
|
|
34
|
-
format: { role: "display", class: "derived" }
|
|
43
|
+
format: { role: "display", class: "derived" },
|
|
44
|
+
leadingXml: { role: "source", class: "preserved" },
|
|
45
|
+
trailingXml: { role: "source", class: "preserved" }
|
|
35
46
|
},
|
|
36
47
|
tableCell: {
|
|
37
48
|
colspan: { role: "source", class: "model" },
|
|
@@ -47,19 +58,15 @@ var NODE_ATTR_ROLES = {
|
|
|
47
58
|
// it out of the comparison would let a step that unlocks a cell pass as a re-derivation
|
|
48
59
|
sdtPrefix: { role: "source", class: "preserved" },
|
|
49
60
|
sdtContentsLocked: { role: "source", class: "derived" },
|
|
50
|
-
sdtDeletionLocked: { role: "source", class: "derived" }
|
|
61
|
+
sdtDeletionLocked: { role: "source", class: "derived" },
|
|
62
|
+
trailingXml: { role: "source", class: "preserved" }
|
|
51
63
|
},
|
|
52
64
|
rawBlock: {
|
|
53
65
|
xml: { role: "source", class: "preserved" },
|
|
54
|
-
name: { role: "source", class: "identity" }
|
|
55
|
-
},
|
|
56
|
-
docxRaw: {
|
|
57
|
-
srcId: { role: "session", class: "identity" },
|
|
58
|
-
name: { role: "session", class: "identity" }
|
|
59
|
-
},
|
|
60
|
-
bookmarkBlock: {
|
|
61
66
|
srcId: { role: "session", class: "identity" },
|
|
62
|
-
name: { role: "session", class: "identity" }
|
|
67
|
+
name: { role: "session", class: "identity" },
|
|
68
|
+
display: { role: "session", class: "derived" },
|
|
69
|
+
guarded: { role: "session", class: "derived" }
|
|
63
70
|
},
|
|
64
71
|
hardBreak: { brAttrs: { role: "source", class: "preserved" } },
|
|
65
72
|
image: {
|
|
@@ -84,14 +91,11 @@ var NODE_ATTR_ROLES = {
|
|
|
84
91
|
authorId: { role: "source", class: "model" },
|
|
85
92
|
initials: { role: "source", class: "model" },
|
|
86
93
|
date: { role: "source", class: "model" },
|
|
87
|
-
text: { role: "source", class: "model" },
|
|
88
|
-
commentXml: { role: "source", class: "preserved" },
|
|
89
94
|
paraId: { role: "source", class: "identity" },
|
|
90
95
|
resolved: { role: "source", class: "model" },
|
|
91
96
|
extensionXml: { role: "source", class: "preserved" },
|
|
92
97
|
replies: { role: "source", class: "model" },
|
|
93
|
-
// Read to decide whether the comment
|
|
94
|
-
imported: { role: "session", class: "derived" },
|
|
98
|
+
// Read to decide whether the extended comment part is rewritten at all; it is never written
|
|
95
99
|
threadImported: { role: "session", class: "derived" }
|
|
96
100
|
},
|
|
97
101
|
noteReference: {
|
|
@@ -99,12 +103,32 @@ var NODE_ATTR_ROLES = {
|
|
|
99
103
|
id: { role: "source", class: "identity" },
|
|
100
104
|
customMarkFollows: { role: "source", class: "model" },
|
|
101
105
|
referenceXml: { role: "source", class: "preserved" },
|
|
102
|
-
//
|
|
106
|
+
// Worked out from the notes part as the document was opened, and the writer puts back the
|
|
103
107
|
// reference alone: a note renumbered around an edit is the same reference it was
|
|
104
|
-
label: { role: "display", class: "derived" }
|
|
105
|
-
text: { role: "display", class: "derived" }
|
|
108
|
+
label: { role: "display", class: "derived" }
|
|
106
109
|
},
|
|
107
|
-
|
|
110
|
+
rawRunContent: {
|
|
111
|
+
xml: { role: "source", class: "preserved" },
|
|
112
|
+
// What the fragment is and how it is drawn are read off the preservation table when the
|
|
113
|
+
// document is opened and never worked out again, so they travel with the fragment itself
|
|
114
|
+
element: { role: "session", class: "identity" },
|
|
115
|
+
display: { role: "session", class: "derived" },
|
|
116
|
+
text: { role: "session", class: "derived" },
|
|
117
|
+
guarded: { role: "session", class: "derived" }
|
|
118
|
+
},
|
|
119
|
+
rawInline: {
|
|
120
|
+
xml: { role: "source", class: "preserved" },
|
|
121
|
+
element: { role: "session", class: "identity" },
|
|
122
|
+
display: { role: "session", class: "derived" },
|
|
123
|
+
text: { role: "session", class: "derived" },
|
|
124
|
+
guarded: { role: "session", class: "derived" }
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
var WRAPPER_ATTR_ROLES = {
|
|
128
|
+
// Which wrapper of its kind this is, counted through the document as it was opened
|
|
129
|
+
key: { role: "session", class: "identity" },
|
|
130
|
+
// How deep inside the other wrappers this one stood in the file
|
|
131
|
+
depth: { role: "session", class: "identity" }
|
|
108
132
|
};
|
|
109
133
|
var MARK_ATTR_ROLES = {
|
|
110
134
|
run: {
|
|
@@ -114,15 +138,14 @@ var MARK_ATTR_ROLES = {
|
|
|
114
138
|
},
|
|
115
139
|
sdt: {
|
|
116
140
|
sdtPrefix: { role: "source", class: "preserved" },
|
|
117
|
-
|
|
118
|
-
sdtKey: { role: "session", class: "identity" },
|
|
141
|
+
...WRAPPER_ATTR_ROLES,
|
|
119
142
|
contentsLocked: { role: "source", class: "derived" },
|
|
120
143
|
deletionLocked: { role: "source", class: "derived" }
|
|
121
144
|
},
|
|
122
145
|
link: {
|
|
123
146
|
linkPrefix: { role: "source", class: "preserved" },
|
|
124
147
|
href: { role: "source", class: "model" },
|
|
125
|
-
|
|
148
|
+
...WRAPPER_ATTR_ROLES
|
|
126
149
|
},
|
|
127
150
|
tab: { tabAttrs: { role: "source", class: "preserved" } }
|
|
128
151
|
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What each node and mark looks like once it has left the editor.
|
|
3
|
+
*
|
|
4
|
+
* A node draws itself twice. `toDOM` draws the page, where the editor reads its own attributes
|
|
5
|
+
* back (an IME composition, a browser edit), so everything the document is written from stands on
|
|
6
|
+
* the element. A copy lands in Word, in a spreadsheet, in a mail: none of them can read a `w:pPr`,
|
|
7
|
+
* and none of them should be handed a comment's author or the body of a footnote. So the shape a
|
|
8
|
+
* copy takes is declared here, beside the schema, rather than worked out afterwards by taking the
|
|
9
|
+
* page's drawing apart.
|
|
10
|
+
*
|
|
11
|
+
* The two cannot drift apart unnoticed: `Readonly<Record<DocxNodeName, ...>>` refuses to compile
|
|
12
|
+
* until a node added to the schema says what it leaves as, and `clipboard.test.ts` asks the schema
|
|
13
|
+
* itself the same question at runtime.
|
|
14
|
+
*/
|
|
15
|
+
import { type DOMOutputSpec, DOMSerializer, type Mark, type Node as PMNode, type Schema, type Slice } from "prosemirror-model";
|
|
16
|
+
import { type docxSchema } from "./docxSchema";
|
|
17
|
+
/**
|
|
18
|
+
* The paragraph style a copy carries, which is the one thing a paste needs that the drawing does
|
|
19
|
+
* not already say. It is the style's id alone, where the editor draws the whole `w:pPr`.
|
|
20
|
+
*/
|
|
21
|
+
export declare const COPIED_STYLE_ATTRIBUTE = "data-style";
|
|
22
|
+
/**
|
|
23
|
+
* The address a link may carry, whichever way it travels.
|
|
24
|
+
*
|
|
25
|
+
* A copy writes one out and a paste reads one in, and an address this rule turns down is one
|
|
26
|
+
* neither end should act on, so both ask here.
|
|
27
|
+
*/
|
|
28
|
+
export declare function safeHref(value: string | null): string | null;
|
|
29
|
+
export interface ClipboardNodeSpec {
|
|
30
|
+
/**
|
|
31
|
+
* The HTML a copy carries. `null` in place of a function says this kind of node never leaves the
|
|
32
|
+
* editor; a function answering null says this one leaves nothing. Either way neither the node
|
|
33
|
+
* nor anything inside it is written.
|
|
34
|
+
*/
|
|
35
|
+
toClipboardDOM: ((node: PMNode) => DOMOutputSpec | null) | null;
|
|
36
|
+
/**
|
|
37
|
+
* The plain text a copy carries, given what its children said. A null answer leaves the node out
|
|
38
|
+
* of the text entirely, separator and all, so a block standing for something invisible does not
|
|
39
|
+
* open a blank line where it stood.
|
|
40
|
+
*/
|
|
41
|
+
toClipboardText: ((node: PMNode, children: readonly string[]) => string | null) | null;
|
|
42
|
+
}
|
|
43
|
+
export interface ClipboardMarkSpec {
|
|
44
|
+
/** null writes no wrapper, and the content it covered goes out on its own */
|
|
45
|
+
toClipboardDOM: ((mark: Mark) => DOMOutputSpec) | null;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* `Schema.nodes` is indexed by `string` as well as by the names it was built from, so `keyof` over
|
|
49
|
+
* it widens to `string`. The generic argument the schema literal was inferred with keeps them.
|
|
50
|
+
*/
|
|
51
|
+
export type DocxNodeName = typeof docxSchema extends Schema<infer N, string> ? N : never;
|
|
52
|
+
export type DocxMarkName = typeof docxSchema extends Schema<string, infer M> ? M : never;
|
|
53
|
+
export declare const clipboardSpecs: {
|
|
54
|
+
nodes: Readonly<Record<DocxNodeName, ClipboardNodeSpec>>;
|
|
55
|
+
marks: Readonly<Record<DocxMarkName, ClipboardMarkSpec>>;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* The serializer a copy is written with.
|
|
59
|
+
*
|
|
60
|
+
* `stampCopy` is handed the whole copy once it stands, and only once: `DOMSerializer` draws a
|
|
61
|
+
* node's children by calling `serializeFragment` again with the element it drew as the target, so
|
|
62
|
+
* a call carrying one is inside the copy rather than around it.
|
|
63
|
+
*/
|
|
64
|
+
export declare function clipboardSerializer(stampCopy?: (copy: HTMLElement | DocumentFragment) => void): DOMSerializer;
|
|
65
|
+
/**
|
|
66
|
+
* The plain text a copy leaves beside the HTML.
|
|
67
|
+
*
|
|
68
|
+
* ProseMirror's own answer is the text content with a line between blocks, which loses a tab, a
|
|
69
|
+
* page break, and the difference between the next cell and the next row. Somewhere those are the
|
|
70
|
+
* whole of what was copied, a table pasted into a spreadsheet above all.
|
|
71
|
+
*/
|
|
72
|
+
export declare function clipboardText(slice: Slice): string;
|