@portone/docx-editor 0.1.1 → 0.2.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 +82 -0
- package/CONTRIBUTING.md +4 -0
- package/README.md +17 -5
- package/dist/DocxEditor.d.ts +34 -10
- package/dist/DocxEditor.js +69 -24
- package/dist/core.d.ts +2 -0
- package/dist/core.js +2 -0
- package/dist/docx/commentOnlyChange.d.ts +47 -0
- package/dist/docx/commentOnlyChange.js +162 -0
- package/dist/docx/comments/constants.d.ts +11 -3
- package/dist/docx/comments/constants.js +10 -3
- package/dist/docx/comments/contentTypes.d.ts +7 -0
- package/dist/docx/comments/contentTypes.js +38 -0
- package/dist/docx/comments/grammar.d.ts +80 -0
- package/dist/docx/comments/grammar.js +213 -0
- package/dist/docx/comments/model.d.ts +2 -0
- package/dist/docx/comments/model.js +3 -0
- package/dist/docx/comments/people.d.ts +39 -0
- package/dist/docx/comments/people.js +196 -0
- package/dist/docx/comments/reading.d.ts +18 -1
- package/dist/docx/comments/reading.js +33 -20
- package/dist/docx/comments/verifying.d.ts +44 -0
- package/dist/docx/comments/verifying.js +206 -0
- package/dist/docx/comments/writing.d.ts +4 -1
- package/dist/docx/comments/writing.js +84 -77
- package/dist/docx/exportDocx.js +2 -1
- package/dist/docx/formatting/direct.js +3 -6
- package/dist/docx/headersFooters.js +8 -10
- package/dist/docx/importParagraph.js +1 -0
- package/dist/docx/notes.js +3 -5
- package/dist/docx/paraProps.js +51 -46
- package/dist/docx/propsXml.d.ts +20 -2
- package/dist/docx/propsXml.js +72 -32
- package/dist/docx/relationships.js +8 -3
- package/dist/docx/runProps.js +34 -34
- package/dist/docx/sdt.js +16 -14
- package/dist/docx/serializeBlock.js +1 -2
- package/dist/docx/serializeParagraph.d.ts +2 -1
- package/dist/docx/serializeParagraph.js +25 -14
- package/dist/docx/serializeTable.js +38 -24
- package/dist/docx/storyProjection.d.ts +36 -0
- package/dist/docx/storyProjection.js +23 -0
- package/dist/docx/tableFormatting/editing.js +49 -73
- package/dist/docx/tableTemplate.js +24 -3
- package/dist/docx/theme.d.ts +0 -6
- package/dist/docx/theme.js +0 -8
- package/dist/editor/clipboard/images.js +17 -3
- package/dist/editor/clipboard/inlineFormatting.d.ts +7 -0
- package/dist/editor/clipboard/inlineFormatting.js +1 -0
- package/dist/editor/commands/breakCommands.js +6 -2
- package/dist/editor/commands/canRunCommand.d.ts +3 -2
- package/dist/editor/commands/canRunCommand.js +2 -2
- package/dist/editor/commands/comments/editing.js +9 -10
- package/dist/editor/commands/comments/model.d.ts +9 -0
- package/dist/editor/commands/comments/reading.d.ts +7 -0
- package/dist/editor/commands/comments/reading.js +14 -0
- package/dist/editor/commands/formatting/editing.js +6 -1
- package/dist/editor/commands/historyCommands.js +14 -5
- package/dist/editor/commands/index.d.ts +7 -1
- package/dist/editor/commands/index.js +4 -0
- package/dist/editor/commands/linkCommands.js +2 -0
- package/dist/editor/commands/lockCommands.js +12 -1
- package/dist/editor/commands/tabCommands.js +4 -2
- package/dist/editor/createEditor.d.ts +13 -3
- package/dist/editor/createEditor.js +23 -15
- package/dist/editor/externalClipboard.js +166 -4
- package/dist/editor/insertImage.js +6 -2
- package/dist/editor/insertTable.js +2 -1
- package/dist/editor/paragraphEdits.d.ts +2 -0
- package/dist/editor/paragraphEdits.js +2 -0
- package/dist/editor/plugins/documentProtection.d.ts +21 -0
- package/dist/editor/plugins/documentProtection.js +44 -0
- package/dist/editor/plugins/imagePaste.js +4 -1
- package/dist/editor/plugins/keymap.d.ts +11 -1
- package/dist/editor/plugins/keymap.js +24 -4
- package/dist/editor/plugins/lockedContent.d.ts +4 -2
- package/dist/editor/plugins/lockedContent.js +2 -2
- package/dist/editor/plugins/tableContextMenu.js +2 -1
- package/dist/editor/plugins/textContextMenu.js +6 -1
- package/dist/index.d.ts +2 -0
- package/dist/numbering/listTemplate.js +32 -10
- package/dist/ooxml/element.d.ts +52 -0
- package/dist/ooxml/element.js +49 -0
- package/dist/ooxml/fragment.d.ts +53 -0
- package/dist/ooxml/fragment.js +76 -0
- package/dist/ooxml/image.js +4 -3
- package/dist/ooxml/names.d.ts +27 -0
- package/dist/ooxml/names.js +29 -0
- package/dist/ooxml/precedence.d.ts +31 -0
- package/dist/ooxml/precedence.js +51 -0
- package/dist/ooxml/xml.d.ts +31 -0
- package/dist/ooxml/xml.js +27 -0
- package/dist/page/blockKinds.d.ts +42 -0
- package/dist/page/blockKinds.js +0 -0
- package/dist/page/measureBlocks.d.ts +14 -1
- package/dist/page/measureBlocks.js +31 -11
- package/dist/page/pageDecorations.d.ts +16 -0
- package/dist/page/pageDecorations.js +1 -0
- package/dist/page/pageLayout.d.ts +8 -43
- package/dist/page/pageLayout.js +28 -51
- package/dist/page/tableMeasurements.d.ts +10 -2
- package/dist/page/tableMeasurements.js +20 -16
- package/dist/page/usePageLayout.d.ts +15 -0
- package/dist/page/usePageLayout.js +30 -2
- package/dist/schema/attrRoles.d.ts +35 -0
- package/dist/schema/attrRoles.js +106 -0
- package/dist/schema/docxSchema.d.ts +6 -0
- package/dist/schema/docxSchema.js +216 -100
- package/dist/schema/editGuard.d.ts +99 -0
- package/dist/schema/editGuard.js +43 -0
- package/dist/schema/guards.d.ts +68 -0
- package/dist/schema/guards.js +82 -0
- package/dist/schema/locks.d.ts +9 -8
- package/dist/schema/locks.js +32 -16
- package/dist/schema/preservedGuards.d.ts +30 -0
- package/dist/schema/preservedGuards.js +50 -0
- package/dist/schema/protection.d.ts +84 -0
- package/dist/schema/protection.js +171 -0
- package/dist/schema/protectionState.d.ts +20 -0
- package/dist/schema/protectionState.js +37 -0
- package/dist/schema/sourceEquality.d.ts +27 -0
- package/dist/schema/sourceEquality.js +65 -0
- package/dist/table/cellFormatting.js +5 -2
- package/dist/table/commands.d.ts +2 -2
- package/dist/table/commands.js +2 -2
- package/dist/table/index.d.ts +2 -2
- package/dist/table/merge.d.ts +2 -2
- package/dist/table/merge.js +3 -3
- package/dist/ui/CommentsPanel.d.ts +3 -3
- package/dist/ui/CommentsPanel.js +26 -18
- package/dist/ui/LinkCard.d.ts +1 -2
- package/dist/ui/LinkCard.js +3 -4
- package/dist/ui/TextMenu.js +15 -10
- package/package.json +5 -2
- package/dist/editor/plugins/bookmarkProtection.d.ts +0 -3
- package/dist/editor/plugins/bookmarkProtection.js +0 -28
- package/dist/editor/plugins/noteProtection.d.ts +0 -3
- package/dist/editor/plugins/noteProtection.js +0 -25
package/dist/ooxml/xml.d.ts
CHANGED
|
@@ -5,6 +5,14 @@ export declare const W_NS = "http://schemas.openxmlformats.org/wordprocessingml/
|
|
|
5
5
|
* every relationship type name is built on
|
|
6
6
|
*/
|
|
7
7
|
export declare const R_NS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
|
8
|
+
/**
|
|
9
|
+
* The prefixes whose meaning the editor depends on, and the namespace each one has to carry.
|
|
10
|
+
*
|
|
11
|
+
* Every element the writer spells out is a `w:` one, and the relationship a link or an image points
|
|
12
|
+
* at is named by `r:id` or `r:embed`, so a fragment that bound either prefix elsewhere would leave
|
|
13
|
+
* the writer's own markup meaning something else.
|
|
14
|
+
*/
|
|
15
|
+
export declare const RESERVED_PREFIXES: ReadonlyMap<string, string>;
|
|
8
16
|
/** The name with its namespace prefix stripped off (`w:ascii` -> `ascii`) */
|
|
9
17
|
export declare function localPart(name: string): string;
|
|
10
18
|
/**
|
|
@@ -22,10 +30,33 @@ export declare function decodeUtf8(bytes: Uint8Array): {
|
|
|
22
30
|
};
|
|
23
31
|
export declare function encodeUtf8(text: string, withBom: boolean): Uint8Array;
|
|
24
32
|
export declare function parseXml(source: string): Document;
|
|
33
|
+
/**
|
|
34
|
+
* Gathers the namespace prefixes used in the fragment and declares them.
|
|
35
|
+
* Only `w` carries real meaning; the rest are placeholders that keep the parser from stopping.
|
|
36
|
+
* All we read are element names and `w:` attributes, so placeholders still let the values be read as they are.
|
|
37
|
+
*
|
|
38
|
+
* An attribute standing at the very start of the string counts too, since a fragment may be an
|
|
39
|
+
* attribute list of its own (`attrString`) rather than an element.
|
|
40
|
+
*/
|
|
41
|
+
export declare function namespaceDecls(xml: string): string;
|
|
25
42
|
export declare function elementChildren(el: Element): Element[];
|
|
43
|
+
/**
|
|
44
|
+
* The value of the attribute with this local name, whatever prefix it was written under.
|
|
45
|
+
*
|
|
46
|
+
* A document is free to bind the WordprocessingML namespace to a prefix of its own, so an
|
|
47
|
+
* attribute is looked up by the name it carries rather than by the spelling a producer chose.
|
|
48
|
+
*/
|
|
49
|
+
export declare function attributeByLocalName(el: Element, localName: string): string | null;
|
|
26
50
|
export declare function childByLocalName(el: Element, name: string): Element | null;
|
|
27
51
|
/** Turns an opening tag's attributes into an `a="b" c="d"` string */
|
|
28
52
|
export declare function attrString(el: Element): string | null;
|
|
53
|
+
/**
|
|
54
|
+
* Whether this node is an element.
|
|
55
|
+
*
|
|
56
|
+
* `Node` is a global a server is asked to install (`site/content/docs/core.mdx`); `Element` is
|
|
57
|
+
* not, so nothing here may reach for it at run time.
|
|
58
|
+
*/
|
|
59
|
+
export declare function isElement(node: Node): node is Element;
|
|
29
60
|
/**
|
|
30
61
|
* Turns a single element back into an XML string.
|
|
31
62
|
*
|
package/dist/ooxml/xml.js
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
import { DocxImportError } from "./errors.js";
|
|
3
3
|
var W_NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
4
4
|
var R_NS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
|
5
|
+
var RESERVED_PREFIXES = /* @__PURE__ */ new Map([
|
|
6
|
+
["w", W_NS],
|
|
7
|
+
["r", R_NS]
|
|
8
|
+
]);
|
|
5
9
|
function localPart(name) {
|
|
6
10
|
const colon = name.indexOf(":");
|
|
7
11
|
return colon === -1 ? name : name.slice(colon + 1);
|
|
@@ -42,9 +46,28 @@ function parseXml(source) {
|
|
|
42
46
|
}
|
|
43
47
|
return doc;
|
|
44
48
|
}
|
|
49
|
+
function namespaceDecls(xml) {
|
|
50
|
+
const prefixes = /* @__PURE__ */ new Set(["w"]);
|
|
51
|
+
for (const [, prefix] of xml.matchAll(/<\/?([A-Za-z_][\w.-]*):/g)) {
|
|
52
|
+
prefixes.add(prefix);
|
|
53
|
+
}
|
|
54
|
+
for (const [, prefix] of xml.matchAll(
|
|
55
|
+
/(?:^|[\s"'])([A-Za-z_][\w.-]*):[\w.-]+=/g
|
|
56
|
+
)) {
|
|
57
|
+
prefixes.add(prefix);
|
|
58
|
+
}
|
|
59
|
+
prefixes.delete("xml");
|
|
60
|
+
prefixes.delete("xmlns");
|
|
61
|
+
return Array.from(prefixes).map(
|
|
62
|
+
(prefix) => `xmlns:${prefix}="${prefix === "w" ? W_NS : `urn:docx-editor:${prefix}`}"`
|
|
63
|
+
).join(" ");
|
|
64
|
+
}
|
|
45
65
|
function elementChildren(el) {
|
|
46
66
|
return Array.from(el.children);
|
|
47
67
|
}
|
|
68
|
+
function attributeByLocalName(el, localName) {
|
|
69
|
+
return Array.from(el.attributes).find((entry) => entry.localName === localName)?.value ?? null;
|
|
70
|
+
}
|
|
48
71
|
function childByLocalName(el, name) {
|
|
49
72
|
return elementChildren(el).find((child) => child.localName === name) ?? null;
|
|
50
73
|
}
|
|
@@ -75,15 +98,19 @@ function serializeXml(el) {
|
|
|
75
98
|
return inner ? `${open}>${inner}</${el.nodeName}>` : `${open}/>`;
|
|
76
99
|
}
|
|
77
100
|
export {
|
|
101
|
+
RESERVED_PREFIXES,
|
|
78
102
|
R_NS,
|
|
79
103
|
W_NS,
|
|
80
104
|
attrString,
|
|
105
|
+
attributeByLocalName,
|
|
81
106
|
childByLocalName,
|
|
82
107
|
decodeUtf8,
|
|
83
108
|
elementChildren,
|
|
84
109
|
encodeUtf8,
|
|
85
110
|
escapeXml,
|
|
111
|
+
isElement,
|
|
86
112
|
localPart,
|
|
113
|
+
namespaceDecls,
|
|
87
114
|
parseXml,
|
|
88
115
|
serializeXml
|
|
89
116
|
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the page engine knows about a block, whatever shape of block it is.
|
|
3
|
+
*
|
|
4
|
+
* A measurer says where a block may be parted and how much of it has to fit on the page it
|
|
5
|
+
* starts on; the layout (`page/pageLayout`) answers with the places it decided to part it at.
|
|
6
|
+
* Neither side names a paragraph or a table.
|
|
7
|
+
*/
|
|
8
|
+
/** A place inside a block where the next page may, or must, start */
|
|
9
|
+
export interface BreakCandidate {
|
|
10
|
+
/**
|
|
11
|
+
* The document position the continued piece starts at: a page `br` for a paragraph, a row for a
|
|
12
|
+
* table. A position maps through an edit, which is why the marks are kept by position and not
|
|
13
|
+
* by ordinal (see `page/pageDecorations`)
|
|
14
|
+
*/
|
|
15
|
+
at: number;
|
|
16
|
+
/** The top of the continued piece, measured from the block's natural top with no space in it */
|
|
17
|
+
offset: number;
|
|
18
|
+
/** A forced candidate always cuts; an optional one cuts only when the piece after it would overflow */
|
|
19
|
+
forced: boolean;
|
|
20
|
+
/** Height the continued piece carries onto the next page ahead of its own content (a repeated header) */
|
|
21
|
+
repeatHeight: number;
|
|
22
|
+
}
|
|
23
|
+
/** One body block as drawn on screen */
|
|
24
|
+
export interface MeasuredBlock {
|
|
25
|
+
/** The position where this block starts in the document */
|
|
26
|
+
pos: number;
|
|
27
|
+
/** The height that naturally opens up between the previous block and this one */
|
|
28
|
+
gap: number;
|
|
29
|
+
height: number;
|
|
30
|
+
/** The document records that a new page starts at this block */
|
|
31
|
+
breakBefore: boolean;
|
|
32
|
+
/** The block holds a break it cannot open a space at (inside a cell), so the next block starts a page */
|
|
33
|
+
breakAfter: boolean;
|
|
34
|
+
candidates: readonly BreakCandidate[];
|
|
35
|
+
/** The smallest piece that has to fit on the page the block starts on */
|
|
36
|
+
minFirstPiece: number;
|
|
37
|
+
}
|
|
38
|
+
/** A page cut the layout decided on: the space opened before the continued piece */
|
|
39
|
+
export interface PageCut {
|
|
40
|
+
at: number;
|
|
41
|
+
height: number;
|
|
42
|
+
}
|
|
File without changes
|
|
@@ -2,13 +2,21 @@
|
|
|
2
2
|
* Measures the sheet as drawn on screen: the height of each body block, the gap above it, and
|
|
3
3
|
* where the page breaks inside it stand, with nothing computed line by line.
|
|
4
4
|
*
|
|
5
|
+
* Every place a block may be parted at is read as a break candidate (`page/blockKinds`), whatever
|
|
6
|
+
* shape of block it is: a page break for a paragraph, a safe row boundary for a table.
|
|
5
7
|
* The engine's own marks are taken back off as they are read, so a block already pushed and a
|
|
6
8
|
* break already given its space read as they would with neither applied. A measurement that read
|
|
7
9
|
* them in would add to what is already there, and the layout would creep on every pass.
|
|
8
10
|
* Where there is no layout (in tests) everything is 0, so the result is a single page.
|
|
9
11
|
*/
|
|
10
12
|
import type { EditorView } from "prosemirror-view";
|
|
11
|
-
import type { MeasuredBlock } from "./
|
|
13
|
+
import type { MeasuredBlock } from "./blockKinds";
|
|
14
|
+
/** What a table continuation still needs from the measurement to be drawn */
|
|
15
|
+
export interface TableHeaderProjection {
|
|
16
|
+
headerRows: readonly number[];
|
|
17
|
+
headerSignature: string;
|
|
18
|
+
columns: number;
|
|
19
|
+
}
|
|
12
20
|
/**
|
|
13
21
|
* The measurements taken in order to draw the page overlay. Positions are relative to
|
|
14
22
|
* the overlay box
|
|
@@ -21,5 +29,10 @@ export interface SheetMeasure {
|
|
|
21
29
|
contentTop: number;
|
|
22
30
|
contentBottom: number;
|
|
23
31
|
blocks: MeasuredBlock[];
|
|
32
|
+
/**
|
|
33
|
+
* The header facts of every table block, by the table's position, for the adapter that turns
|
|
34
|
+
* cuts back into table continuations (`page/usePageLayout`)
|
|
35
|
+
*/
|
|
36
|
+
tables: ReadonlyMap<number, TableHeaderProjection>;
|
|
24
37
|
}
|
|
25
38
|
export declare function measureSheet(view: EditorView, layer: HTMLElement): SheetMeasure;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/page/measureBlocks.ts
|
|
2
2
|
import { editorAttributes } from "../styles/classNames.js";
|
|
3
|
+
import { pageBreaksIn } from "./pageDecorations.js";
|
|
3
4
|
import { measureTable } from "./tableMeasurements.js";
|
|
4
5
|
var PAGE_BREAK_BR = `br[${editorAttributes.breakType}="page"]`;
|
|
5
6
|
var BREAK_SPACE = `[${editorAttributes.pageBreakSpace}]`;
|
|
@@ -37,34 +38,52 @@ function measureSheet(view, layer) {
|
|
|
37
38
|
const contentBottom = pixels(style.paddingBottom);
|
|
38
39
|
const sheetY = (viewportY) => (viewportY - sheetRect.top) / scale;
|
|
39
40
|
const blocks = [];
|
|
41
|
+
const tables = /* @__PURE__ */ new Map();
|
|
40
42
|
let previousBottom = contentTop;
|
|
41
43
|
let applied = 0;
|
|
42
|
-
let breakAfterPrevious = false;
|
|
43
44
|
for (const { pos, dom } of drawnBlocks(view)) {
|
|
44
45
|
const node = view.state.doc.nodeAt(pos);
|
|
45
46
|
const rect = dom.getBoundingClientRect();
|
|
46
47
|
applied += appliedPush(dom);
|
|
47
48
|
const top = sheetY(rect.top) - applied;
|
|
48
49
|
const measuredTable = node ? measureTable(view, node, pos, dom, scale) : null;
|
|
49
|
-
const breaks = [];
|
|
50
|
+
const breaks = node ? pageBreaksIn(node, pos) : [];
|
|
50
51
|
const spaces = Array.from(dom.querySelectorAll(BREAK_SPACE));
|
|
51
|
-
|
|
52
|
+
const forced = [];
|
|
53
|
+
spaces.forEach((space, index) => {
|
|
52
54
|
const box = space.getBoundingClientRect();
|
|
53
|
-
breaks.
|
|
55
|
+
const found = breaks.at(index);
|
|
56
|
+
if (found) {
|
|
57
|
+
forced.push({
|
|
58
|
+
at: found.at,
|
|
59
|
+
offset: sheetY(box.top) - applied - top,
|
|
60
|
+
forced: true,
|
|
61
|
+
repeatHeight: 0
|
|
62
|
+
});
|
|
63
|
+
}
|
|
54
64
|
applied += box.height / scale;
|
|
55
|
-
}
|
|
65
|
+
});
|
|
56
66
|
applied += measuredTable?.appliedHeight ?? 0;
|
|
57
67
|
const bottom = sheetY(rect.bottom) - applied;
|
|
68
|
+
const height = bottom - top;
|
|
69
|
+
if (measuredTable) {
|
|
70
|
+
tables.set(pos, {
|
|
71
|
+
headerRows: measuredTable.headerRows,
|
|
72
|
+
headerSignature: measuredTable.headerSignature,
|
|
73
|
+
columns: measuredTable.columns
|
|
74
|
+
});
|
|
75
|
+
}
|
|
58
76
|
blocks.push({
|
|
59
77
|
pos,
|
|
60
78
|
gap: top - previousBottom,
|
|
61
|
-
height
|
|
62
|
-
breakBefore:
|
|
63
|
-
|
|
64
|
-
|
|
79
|
+
height,
|
|
80
|
+
breakBefore: dom.hasAttribute(editorAttributes.pageBreakBefore),
|
|
81
|
+
breakAfter: breakWithoutSpace(dom, spaces.length),
|
|
82
|
+
// A space is never opened inside a table, so at most one of the two lists holds anything
|
|
83
|
+
candidates: [...forced, ...measuredTable?.candidates ?? []],
|
|
84
|
+
minFirstPiece: measuredTable?.minFirstPiece ?? forced[0]?.offset ?? height
|
|
65
85
|
});
|
|
66
86
|
previousBottom = bottom;
|
|
67
|
-
breakAfterPrevious = breakWithoutSpace(dom, spaces.length);
|
|
68
87
|
}
|
|
69
88
|
return {
|
|
70
89
|
left: (sheetRect.left - layerRect.left) / scale,
|
|
@@ -72,7 +91,8 @@ function measureSheet(view, layer) {
|
|
|
72
91
|
width: sheetRect.width / scale,
|
|
73
92
|
contentTop,
|
|
74
93
|
contentBottom,
|
|
75
|
-
blocks
|
|
94
|
+
blocks,
|
|
95
|
+
tables
|
|
76
96
|
};
|
|
77
97
|
}
|
|
78
98
|
export {
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* The document model is left untouched, so no trace of them is left in
|
|
6
6
|
* the exported XML or in the edit history.
|
|
7
7
|
*/
|
|
8
|
+
import type { Node as PMNode } from "prosemirror-model";
|
|
8
9
|
import { Plugin } from "prosemirror-state";
|
|
9
10
|
import { DecorationSet, type EditorView } from "prosemirror-view";
|
|
10
11
|
import type { BlockPush, BreakSpace, TableContinuation } from "./pageLayout";
|
|
@@ -26,6 +27,21 @@ interface PageMarks {
|
|
|
26
27
|
tableContinuations: readonly TableContinuation[];
|
|
27
28
|
decorations: DecorationSet;
|
|
28
29
|
}
|
|
30
|
+
/** One page break inside a block, where it stands and how much room the `br` itself takes */
|
|
31
|
+
interface BreakAt {
|
|
32
|
+
at: number;
|
|
33
|
+
size: number;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Every page break inside this block, in document order.
|
|
37
|
+
* A space is put on the breaks of a top-level paragraph alone: inside a table cell it would grow
|
|
38
|
+
* the cell rather than the page, so a break there is left to the whole-block rule in
|
|
39
|
+
* `page/measureBlocks`, and none is found here.
|
|
40
|
+
*
|
|
41
|
+
* The measurement reads the same list to pair each space element it finds with the break it
|
|
42
|
+
* belongs to (`page/measureBlocks`).
|
|
43
|
+
*/
|
|
44
|
+
export declare function pageBreaksIn(block: PMNode, blockPos: number): BreakAt[];
|
|
29
45
|
export declare function pageDecorations(): Plugin<PageMarks>;
|
|
30
46
|
/** Does nothing when the same values are already applied */
|
|
31
47
|
export declare function setPagePushes(view: EditorView, pushes: readonly BlockPush[]): void;
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
* document breaks at Letter's height and fits its tables to Letter's width.
|
|
11
11
|
*/
|
|
12
12
|
import { type PageGeometry } from "../docx/pageGeometry";
|
|
13
|
+
import type { MeasuredBlock, PageCut } from "./blockKinds";
|
|
14
|
+
export type { MeasuredBlock };
|
|
13
15
|
/** The paper measured in the pixels the sheet is drawn with */
|
|
14
16
|
export interface PagePixels {
|
|
15
17
|
/** The whole sheet, padding included */
|
|
@@ -45,44 +47,6 @@ export declare const A4_PAGE_PIXELS: PagePixels;
|
|
|
45
47
|
* moment before a document is opened.
|
|
46
48
|
*/
|
|
47
49
|
export declare function pageGeometryStyle(page: PagePixels): string;
|
|
48
|
-
/** One body block as drawn on screen */
|
|
49
|
-
export interface MeasuredBlock {
|
|
50
|
-
/** The position where this block starts in the document */
|
|
51
|
-
pos: number;
|
|
52
|
-
/** The height that naturally opens up between the previous block and this one */
|
|
53
|
-
gap: number;
|
|
54
|
-
height: number;
|
|
55
|
-
/** Set when the document records that a new page starts at this block */
|
|
56
|
-
breakBefore: boolean;
|
|
57
|
-
/**
|
|
58
|
-
* Every page break inside the block, in document order, each measured from the block's own top
|
|
59
|
-
* so that the block can be laid out wherever it lands.
|
|
60
|
-
* That top is the one the block would be drawn at with no space in it, so a space opened at one
|
|
61
|
-
* break shifts the ones after it.
|
|
62
|
-
*/
|
|
63
|
-
breaks: readonly number[];
|
|
64
|
-
/** Row boundaries available when this block is an editable table */
|
|
65
|
-
table?: MeasuredTable;
|
|
66
|
-
}
|
|
67
|
-
/** A row that may start the continued part of a table */
|
|
68
|
-
export interface TableBoundary {
|
|
69
|
-
/** The row's document position */
|
|
70
|
-
pos: number;
|
|
71
|
-
/** Its top measured from the table's natural top */
|
|
72
|
-
offset: number;
|
|
73
|
-
}
|
|
74
|
-
/** The measurements needed to continue a table without changing the document */
|
|
75
|
-
export interface MeasuredTable {
|
|
76
|
-
boundaries: readonly TableBoundary[];
|
|
77
|
-
/** The smallest useful first piece: headers followed by one body row group */
|
|
78
|
-
firstPageMinimum: number;
|
|
79
|
-
repeatHeaderHeight: number;
|
|
80
|
-
/** Document positions of the contiguous header rows at the start of the table */
|
|
81
|
-
headerRows: readonly number[];
|
|
82
|
-
/** Changes when any projected header content or formatting changes */
|
|
83
|
-
headerSignature: string;
|
|
84
|
-
columns: number;
|
|
85
|
-
}
|
|
86
50
|
/** The space opened up at one page break, so that what follows it starts the next page */
|
|
87
51
|
export interface BreakSpace {
|
|
88
52
|
/** The position where the block holding the break starts */
|
|
@@ -148,8 +112,8 @@ export interface PageStart {
|
|
|
148
112
|
export interface PageLayout {
|
|
149
113
|
/** Only the blocks to be moved down to the next page */
|
|
150
114
|
pushes: BlockPush[];
|
|
151
|
-
|
|
152
|
-
|
|
115
|
+
/** Where the layout parted a block, and the space it opened before the continued piece */
|
|
116
|
+
cuts: PageCut[];
|
|
153
117
|
splits: PageSplit[];
|
|
154
118
|
/** One per page, the first page included */
|
|
155
119
|
pages: PageStart[];
|
|
@@ -169,8 +133,9 @@ export interface PageLayoutInput {
|
|
|
169
133
|
* blocks below it move down by the same amount.
|
|
170
134
|
* A block taller than one page cannot be pushed, so it is left where it is and only the
|
|
171
135
|
* places it crosses are reported.
|
|
172
|
-
* A
|
|
173
|
-
*
|
|
174
|
-
* the next page while the block itself stays whole.
|
|
136
|
+
* A block is parted only where its measurer offered a candidate. A forced one - a page break
|
|
137
|
+
* written into the text - is filled out to the end of the page it falls on, which carries the rest
|
|
138
|
+
* of that block to the top of the next page while the block itself stays whole. An optional one -
|
|
139
|
+
* a row boundary in a table - is taken only where the piece after it would run off the page.
|
|
175
140
|
*/
|
|
176
141
|
export declare function pageLayout({ blocks, pageBodyHeight, pageStep, }: PageLayoutInput): PageLayout;
|
package/dist/page/pageLayout.js
CHANGED
|
@@ -47,8 +47,7 @@ function pageLayout({
|
|
|
47
47
|
pageStep
|
|
48
48
|
}) {
|
|
49
49
|
const pushes = [];
|
|
50
|
-
const
|
|
51
|
-
const tableContinuations = [];
|
|
50
|
+
const cuts = [];
|
|
52
51
|
const splits = [];
|
|
53
52
|
const firstPage = {
|
|
54
53
|
page: 1,
|
|
@@ -58,14 +57,7 @@ function pageLayout({
|
|
|
58
57
|
};
|
|
59
58
|
const pages = [firstPage];
|
|
60
59
|
if (!(pageBodyHeight > 0)) {
|
|
61
|
-
return {
|
|
62
|
-
pushes,
|
|
63
|
-
spaces,
|
|
64
|
-
tableContinuations,
|
|
65
|
-
splits,
|
|
66
|
-
pages,
|
|
67
|
-
bodyHeight: 0
|
|
68
|
-
};
|
|
60
|
+
return { pushes, cuts, splits, pages, bodyHeight: 0 };
|
|
69
61
|
}
|
|
70
62
|
let pageStart = 0;
|
|
71
63
|
let cursor = 0;
|
|
@@ -82,11 +74,12 @@ function pageLayout({
|
|
|
82
74
|
split(pageStart + pageBodyHeight, true, false);
|
|
83
75
|
}
|
|
84
76
|
};
|
|
77
|
+
let breakAfterPrevious = false;
|
|
85
78
|
for (const block of blocks) {
|
|
86
79
|
const pageEnd = pageStart + pageBodyHeight;
|
|
87
80
|
const top = cursor + block.gap;
|
|
88
|
-
const startsPage = block.breakBefore && top > pageStart + TOLERANCE_PX;
|
|
89
|
-
const first = block.
|
|
81
|
+
const startsPage = (block.breakBefore || breakAfterPrevious) && top > pageStart + TOLERANCE_PX;
|
|
82
|
+
const first = block.minFirstPiece;
|
|
90
83
|
const overflows = top + first > pageEnd + TOLERANCE_PX;
|
|
91
84
|
const fits = first <= pageBodyHeight + TOLERANCE_PX;
|
|
92
85
|
const push = startsPage || overflows && fits ? Math.max(0, pageEnd + pageStep - top) : 0;
|
|
@@ -98,51 +91,35 @@ function pageLayout({
|
|
|
98
91
|
push: round(push)
|
|
99
92
|
});
|
|
100
93
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
contentTop
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const boundary = index === 0 ? null : block.table.boundaries[index - 1];
|
|
120
|
-
const segmentHeight = segmentEnd - segmentStart;
|
|
121
|
-
if (overflows2 && boundary && segmentTop > pageStart + TOLERANCE_PX && segmentHeight <= pageBodyHeight + TOLERANCE_PX) {
|
|
122
|
-
split(pageEnd2, false, false);
|
|
123
|
-
const height = Math.max(0, pageStart - segmentTop);
|
|
124
|
-
tableContinuations.push({
|
|
125
|
-
pos: boundary.pos,
|
|
126
|
-
height: round(height),
|
|
127
|
-
headerRows: block.table.headerRows,
|
|
128
|
-
headerSignature: block.table.headerSignature,
|
|
129
|
-
columns: block.table.columns
|
|
130
|
-
});
|
|
131
|
-
tableAdded += height + block.table.repeatHeaderHeight;
|
|
132
|
-
segmentBottom = contentTop + segmentEnd + tableAdded;
|
|
94
|
+
const contentTop = top + push;
|
|
95
|
+
let added = 0;
|
|
96
|
+
let pieceStart = 0;
|
|
97
|
+
for (let index = 0; index <= block.candidates.length; index += 1) {
|
|
98
|
+
const cut = index === 0 ? null : block.candidates[index - 1];
|
|
99
|
+
const pieceEnd = block.candidates[index]?.offset ?? block.height;
|
|
100
|
+
const pieceTop = contentTop + pieceStart + added;
|
|
101
|
+
let pieceBottom = contentTop + pieceEnd + added;
|
|
102
|
+
if (cut) {
|
|
103
|
+
if (cut.forced) crossTo(pieceTop);
|
|
104
|
+
const pageBottom = pageStart + pageBodyHeight;
|
|
105
|
+
const cutHere = cut.forced || pieceBottom > pageBottom + TOLERANCE_PX && pieceTop > pageStart + TOLERANCE_PX && pieceEnd - pieceStart <= pageBodyHeight + TOLERANCE_PX;
|
|
106
|
+
if (cutHere) {
|
|
107
|
+
split(pageBottom, false, cut.forced);
|
|
108
|
+
const height = Math.max(0, pageStart - pieceTop);
|
|
109
|
+
cuts.push({ at: cut.at, height: round(height) });
|
|
110
|
+
added += height + cut.repeatHeight;
|
|
111
|
+
pieceBottom += height + cut.repeatHeight;
|
|
133
112
|
}
|
|
134
|
-
crossTo(segmentBottom);
|
|
135
|
-
segmentStart = segmentEnd;
|
|
136
113
|
}
|
|
114
|
+
crossTo(pieceBottom);
|
|
115
|
+
pieceStart = pieceEnd;
|
|
137
116
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
cursor = bottom;
|
|
117
|
+
cursor = contentTop + block.height + added;
|
|
118
|
+
breakAfterPrevious = block.breakAfter;
|
|
141
119
|
}
|
|
142
120
|
return {
|
|
143
121
|
pushes,
|
|
144
|
-
|
|
145
|
-
tableContinuations,
|
|
122
|
+
cuts,
|
|
146
123
|
splits,
|
|
147
124
|
pages,
|
|
148
125
|
bodyHeight: round(pageStart + pageBodyHeight)
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
/** Measures safe row boundaries without treating pagination decorations as document content. */
|
|
2
2
|
import type { Node as PMNode } from "prosemirror-model";
|
|
3
3
|
import type { EditorView } from "prosemirror-view";
|
|
4
|
-
import type {
|
|
4
|
+
import type { BreakCandidate } from "./blockKinds";
|
|
5
5
|
export interface TableMeasure {
|
|
6
|
-
table
|
|
6
|
+
/** Every row that may start the continued part of the table */
|
|
7
|
+
candidates: readonly BreakCandidate[];
|
|
8
|
+
/** The smallest useful first piece: headers followed by one body row group */
|
|
9
|
+
minFirstPiece: number;
|
|
7
10
|
/** Height already added by the previous pagination pass */
|
|
8
11
|
appliedHeight: number;
|
|
12
|
+
/** Document positions of the contiguous header rows at the start of the table */
|
|
13
|
+
headerRows: readonly number[];
|
|
14
|
+
/** Changes when any projected header content or formatting changes */
|
|
15
|
+
headerSignature: string;
|
|
16
|
+
columns: number;
|
|
9
17
|
}
|
|
10
18
|
export declare function measureTable(view: EditorView, tableNode: PMNode, tablePos: number, tableDom: HTMLElement, scale?: number): TableMeasure | null;
|
|
@@ -81,33 +81,37 @@ function measureTable(view, tableNode, tablePos, tableDom, scale = 1) {
|
|
|
81
81
|
if (rows.length === 0) return null;
|
|
82
82
|
const headers = headerCount(rows);
|
|
83
83
|
const unsafe = unsafeBoundaries(rows);
|
|
84
|
-
const
|
|
84
|
+
const headerRows = rows.slice(0, headers);
|
|
85
|
+
const repeatHeaderHeight = headerRows.reduce(
|
|
86
|
+
(total, row) => total + row.height,
|
|
87
|
+
0
|
|
88
|
+
);
|
|
89
|
+
const candidates = [];
|
|
85
90
|
for (let rowIndex = Math.max(1, headers); rowIndex < rows.length; rowIndex += 1) {
|
|
86
91
|
const row = rows[rowIndex];
|
|
87
92
|
if (row && !unsafe.has(rowIndex)) {
|
|
88
|
-
|
|
93
|
+
candidates.push({
|
|
94
|
+
at: row.pos,
|
|
95
|
+
offset: row.top,
|
|
96
|
+
forced: false,
|
|
97
|
+
repeatHeight: repeatHeaderHeight
|
|
98
|
+
});
|
|
89
99
|
}
|
|
90
100
|
}
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
(boundary) => boundary.offset > repeatHeaderHeight + 0.5
|
|
101
|
+
const firstBodyBoundary = candidates.find(
|
|
102
|
+
(candidate) => candidate.offset > repeatHeaderHeight + 0.5
|
|
94
103
|
);
|
|
95
104
|
const appliedHeight = appliedRows.reduce(
|
|
96
105
|
(total, row) => total + row.getBoundingClientRect().height / scale,
|
|
97
106
|
0
|
|
98
107
|
);
|
|
99
108
|
return {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
rows.slice(0, headers).map((row) => row.node.toJSON())
|
|
107
|
-
),
|
|
108
|
-
columns: columnCount(tableNode)
|
|
109
|
-
},
|
|
110
|
-
appliedHeight
|
|
109
|
+
candidates,
|
|
110
|
+
minFirstPiece: firstBodyBoundary?.offset ?? tableDom.getBoundingClientRect().height / scale - appliedHeight,
|
|
111
|
+
appliedHeight,
|
|
112
|
+
headerRows: headerRows.map((row) => row.pos),
|
|
113
|
+
headerSignature: JSON.stringify(headerRows.map((row) => row.node.toJSON())),
|
|
114
|
+
columns: columnCount(tableNode)
|
|
111
115
|
};
|
|
112
116
|
}
|
|
113
117
|
export {
|
|
@@ -8,9 +8,13 @@
|
|
|
8
8
|
* in Japanese or Chinese stays open across a whole clause while the text grows line by line. So no
|
|
9
9
|
* measurement is taken while a composition is open: the frame is taken again until it is over.
|
|
10
10
|
*/
|
|
11
|
+
import type { Node as PMNode } from "prosemirror-model";
|
|
11
12
|
import type { EditorView } from "prosemirror-view";
|
|
12
13
|
import { type RefObject } from "react";
|
|
13
14
|
import type { PageGeometry } from "../docx/pageGeometry";
|
|
15
|
+
import type { PageCut } from "./blockKinds";
|
|
16
|
+
import { type TableHeaderProjection } from "./measureBlocks";
|
|
17
|
+
import { type BreakSpace, type TableContinuation } from "./pageLayout";
|
|
14
18
|
/** One place where a page parts from the next. The position is measured on the sheet */
|
|
15
19
|
export interface PageMark {
|
|
16
20
|
page: number;
|
|
@@ -53,5 +57,16 @@ interface PageLayoutOptions {
|
|
|
53
57
|
/** The paper the open document names. A4 where a document names none */
|
|
54
58
|
geometry?: PageGeometry;
|
|
55
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* The layout's cuts as the two kinds of mark the decorations still take.
|
|
62
|
+
*
|
|
63
|
+
* A cut names the position the continued piece starts at, and which of the two it is follows from
|
|
64
|
+
* what stands there: a page `br` the block it sits in counted, or a table row. This stands in
|
|
65
|
+
* until the decorations take the cuts themselves.
|
|
66
|
+
*/
|
|
67
|
+
export declare function cutsToLegacyMarks(cuts: readonly PageCut[], tables: ReadonlyMap<number, TableHeaderProjection>, doc: PMNode): {
|
|
68
|
+
spaces: BreakSpace[];
|
|
69
|
+
tableContinuations: TableContinuation[];
|
|
70
|
+
};
|
|
56
71
|
export declare function usePageLayout({ view, layer, enabled, revision, geometry, }: PageLayoutOptions): PageOverlay | null;
|
|
57
72
|
export {};
|