@portone/docx-editor 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +65 -0
- package/CONTRIBUTING.md +10 -2
- package/README.md +1 -1
- package/dist/DocxEditor.js +3 -3
- package/dist/docx/formatting/styles.d.ts +0 -2
- package/dist/docx/formatting/styles.js +2 -15
- package/dist/docx/identities.js +17 -26
- package/dist/docx/importParagraph.d.ts +6 -5
- package/dist/docx/importParagraph.js +15 -62
- package/dist/docx/importPolicy.js +10 -5
- package/dist/docx/serializeParagraph.d.ts +5 -4
- package/dist/docx/serializeParagraph.js +22 -58
- 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 +2 -6
- package/dist/editor/clipboard/htmlReader.js +98 -24
- package/dist/editor/clipboard/inlineFormatting.d.ts +0 -7
- package/dist/editor/clipboard/inlineFormatting.js +1 -7
- package/dist/editor/clipboard/plugin.js +7 -139
- package/dist/editor/clipboard/readContext.d.ts +10 -0
- package/dist/editor/clipboard/readContext.js +8 -2
- 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 +4 -1
- package/dist/editor/commands/comments/editing.js +3 -4
- package/dist/editor/commands/comments/model.d.ts +7 -0
- package/dist/editor/commands/linkCommands.js +20 -5
- package/dist/editor/commands/lockCommands.js +59 -16
- package/dist/editor/commands/paragraphCommands.js +2 -2
- package/dist/editor/createEditor.js +4 -0
- package/dist/editor/plugins/commentDecorations.js +5 -4
- package/dist/editor/plugins/commentRestoration.d.ts +20 -0
- package/dist/editor/plugins/commentRestoration.js +168 -0
- package/dist/ooxml/props.d.ts +2 -0
- package/dist/ooxml/props.js +14 -0
- package/dist/schema/attrRoles.js +8 -3
- package/dist/schema/clipboard.d.ts +72 -0
- package/dist/schema/clipboard.js +288 -0
- package/dist/schema/docxSchema.js +27 -27
- package/dist/schema/locks.d.ts +3 -0
- package/dist/schema/locks.js +29 -12
- package/dist/schema/rendering.d.ts +14 -0
- package/dist/schema/rendering.js +24 -11
- package/dist/schema/wrappers.d.ts +58 -0
- package/dist/schema/wrappers.js +77 -0
- package/dist/styles/classNames.d.ts +2 -0
- package/dist/styles/classNames.js +2 -0
- package/dist/styles.css +7 -4
- package/dist/ui/CommentsPanel.d.ts +6 -1
- package/dist/ui/CommentsPanel.js +26 -8
- package/package.json +6 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,70 @@
|
|
|
1
1
|
# @portone/docx-editor
|
|
2
2
|
|
|
3
|
+
## 0.6.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#131](https://github.com/portone-io/docx-editor/pull/131) [`5dd468a`](https://github.com/portone-io/docx-editor/commit/5dd468ae14d6210070ab6c8275d16e03b20cf64c) Thanks [@Deea222](https://github.com/Deea222)! - A comment now outlives the text it was written for, and a resolved one stops marking the page.
|
|
8
|
+
|
|
9
|
+
Deleting commented text used to delete the comment and everything said under it. The thread now
|
|
10
|
+
stays. Trim the highlighted text - type over part of it, backspace through its edge - and the
|
|
11
|
+
comment follows what is left. Delete all of it and the comment comes back detached: no longer drawn
|
|
12
|
+
beside the page, since there is nothing to draw it next to, and listed under All comments marked
|
|
13
|
+
"Original content deleted". Cutting commented text leaves it behind the same way instead of dropping
|
|
14
|
+
it. Word has no detached state, so such a comment reaches the saved file as an ordinary comment
|
|
15
|
+
sitting where the deleted text was. Deleting a comment from its own Delete button still deletes it,
|
|
16
|
+
and undo and redo still take a deletion back and make it again.
|
|
17
|
+
|
|
18
|
+
Resolving a thread now takes its highlight off the text, and reopening puts it back. Clicking a
|
|
19
|
+
resolved comment's card in All comments selects the text it was written about and scrolls it into
|
|
20
|
+
view, which `selectComment` now does for every caller.
|
|
21
|
+
|
|
22
|
+
`documentComments` reports a new `anchored` field on every comment, `false` for one that no longer
|
|
23
|
+
marks a stretch of the document. Its existing fields are unchanged, so a panel of your own keeps
|
|
24
|
+
working and can read the new field to decide what a click on a card should do.
|
|
25
|
+
|
|
26
|
+
## 0.5.1
|
|
27
|
+
|
|
28
|
+
### Patch Changes
|
|
29
|
+
|
|
30
|
+
- [#125](https://github.com/portone-io/docx-editor/pull/125) [`88ff33a`](https://github.com/portone-io/docx-editor/commit/88ff33abac21cd56d87723af60d95ab597ccff39) Thanks [@Deea222](https://github.com/Deea222)! - Pasting from Word, Google Docs or LibreOffice now keeps tables as tables and Word lists as lists.
|
|
31
|
+
|
|
32
|
+
A table on the clipboard used to arrive as a single paragraph holding the text of every cell run
|
|
33
|
+
together. It now arrives as a table: one paragraph for each block a cell held, the columns and rows
|
|
34
|
+
a cell reached across kept, and the same width and lines a table inserted here has.
|
|
35
|
+
|
|
36
|
+
Word writes list items as ordinary paragraphs that name the list they belong to and draw their own
|
|
37
|
+
bullet or number, so pasting one used to give unnumbered paragraphs each beginning with a stray
|
|
38
|
+
marker. The items of one Word list now join one list, at the level Word gives them, counted when
|
|
39
|
+
the marker Word drew counts.
|
|
40
|
+
|
|
41
|
+
A table's caption is kept as the paragraph above it, a table inside a list item is read as a table
|
|
42
|
+
rather than as the item's own text, and a table too large for the editor to hold is read as one
|
|
43
|
+
paragraph per row instead of as a single run-on one.
|
|
44
|
+
|
|
45
|
+
A copy wrapped in a single element, which is how Google Docs writes one, no longer has its
|
|
46
|
+
paragraphs pressed into one.
|
|
47
|
+
|
|
48
|
+
- [#123](https://github.com/portone-io/docx-editor/pull/123) [`3d6b0a5`](https://github.com/portone-io/docx-editor/commit/3d6b0a58ea9e0a5ca651a5579d5dd0a8b4646ff6) Thanks [@Deea222](https://github.com/Deea222)! - Copied HTML and text now follow one declared outbound shape per node and mark, so what leaves the editor is decided beside the schema rather than filtered out of the page's own drawing afterwards. A copy pasted into another application carries paragraphs, styles, runs, links, images at the size they were drawn, and tables as tables, and none of the document's internals. A comment marker, a bookmark, and the placeholder standing for content the editor could not model now leave nothing behind them, where before they left an element carrying the editor's own name for what stood there.
|
|
49
|
+
|
|
50
|
+
Copied plain text reads as the page does. A table pasted into a spreadsheet keeps one row per row, with everything a cell holds on the one line that cell stands on, and a block the editor only kept no longer opens a blank line where it stood. A kept line break and a kept character, such as a no-break hyphen, travel with the text they stand in, and so does the text a placeholder draws: a field's cached result and the words of a tracked insertion read in a copy as they read on the page.
|
|
51
|
+
|
|
52
|
+
- [#124](https://github.com/portone-io/docx-editor/pull/124) [`936b2a0`](https://github.com/portone-io/docx-editor/commit/936b2a08d0d26b1a9b77cf39c718bb85b70a834c) Thanks [@Deea222](https://github.com/Deea222)! - Hyperlinks that hold content controls, and nested controls, open as editable text.
|
|
53
|
+
|
|
54
|
+
Only one of the arrangements used to be readable - a control holding a link. A link holding a
|
|
55
|
+
control, or a control holding a control, brought the inner one in as a small box naming the element
|
|
56
|
+
it stood for, and the text it held could not be typed in. Any depth and any order of the two is now
|
|
57
|
+
ordinary text wearing a wrapper each, and goes back out nested as it came. A hyperlink inside a
|
|
58
|
+
hyperlink is the one arrangement still kept whole, since a link marks the text it covers once.
|
|
59
|
+
|
|
60
|
+
A link made on text inside a control is written inside that control, and a lock put on a stretch
|
|
61
|
+
that sits wholly inside a link is written inside that link, instead of either wrapper being split
|
|
62
|
+
around the other. Where a control holds a control, locking shuts the outer one and lifting a lock
|
|
63
|
+
opens the inner one the selection stands in.
|
|
64
|
+
|
|
65
|
+
Plugins reading the drawn page will find `data-key` and `data-depth` on a control and a link in
|
|
66
|
+
place of `data-sdt-key` and `data-link-key`.
|
|
67
|
+
|
|
3
68
|
## 0.5.0
|
|
4
69
|
|
|
5
70
|
### Minor Changes
|
package/CONTRIBUTING.md
CHANGED
|
@@ -44,25 +44,33 @@ Run a specialized check when your change affects its area:
|
|
|
44
44
|
pnpm test:package
|
|
45
45
|
pnpm verify:package
|
|
46
46
|
pnpm test:e2e
|
|
47
|
+
pnpm bench
|
|
47
48
|
```
|
|
48
49
|
|
|
50
|
+
A pull request that changes performance-sensitive code attaches its `pnpm bench` output to the description.
|
|
51
|
+
|
|
49
52
|
[Testing](https://github.com/portone-io/docx-editor/blob/main/docs/testing.md) explains the scope and prerequisites of every check.
|
|
53
|
+
CI runs `pnpm test:package` on every pull request in a separate job; it is not part of the local `pnpm check` command.
|
|
50
54
|
|
|
51
55
|
A command added to `./commands` or `./table` needs a writer probe in `src/docx/__testing__/writerProbes.ts` with a `check` of its immediate effect, so its export is validated before another command can overwrite it, or an entry in `NOT_A_WRITER` giving the reason it reaches no writer.
|
|
52
56
|
|
|
53
57
|
## Changesets
|
|
54
58
|
|
|
55
|
-
If you changed
|
|
59
|
+
If you changed production code under `src/`, run `pnpm changeset` and commit the file it writes.
|
|
56
60
|
It becomes the CHANGELOG entry, so write it for someone reading release notes rather than the diff.
|
|
61
|
+
Changes limited to tests, fixtures, scripts, or documentation do not need a changeset.
|
|
57
62
|
|
|
58
|
-
The public API is the exported names and types of the four entry points - `.`, `./core`, `./commands`, and `./table` - together with the parts of the document model that [What a plugin may rely on](https://docx-editor.portone.io/docs/
|
|
63
|
+
The public API is the exported names and types of the four entry points - `.`, `./core`, `./commands`, and `./table` - together with the parts of the document model that [What a plugin may rely on](https://docx-editor.portone.io/docs/editor-api/plugins-and-presets#what-a-plugin-may-rely-on) calls stable.
|
|
59
64
|
An attr that page calls internal is not part of it, and neither is anything under `src/` that no entry point reaches.
|
|
60
65
|
|
|
61
66
|
Choose `patch` unless the change adds to that surface, takes something out of it, or changes what a part of it means.
|
|
62
67
|
Those are `minor` while the package is below 1.0.
|
|
68
|
+
A diff to `src/schema/attrRoles.ts` is where to check this: a change to a stable document-model attr is `minor` even when the declaration reports do not change.
|
|
63
69
|
A declaration added, removed, or rewritten in `etc/*.api.md` after running `pnpm api:update` is what that looks like; a line that only gains or loses an `(undocumented)` marker is not.
|
|
64
70
|
Commit the regenerated report in the same commit as the changeset.
|
|
65
71
|
|
|
72
|
+
The changesets that have landed become a release the way [Releasing](https://github.com/portone-io/docx-editor/blob/main/docs/releasing.md) describes.
|
|
73
|
+
|
|
66
74
|
By contributing, you agree that your contribution is licensed under the Apache License 2.0 used by this project.
|
|
67
75
|
|
|
68
76
|
## Commit messages
|
package/README.md
CHANGED
|
@@ -47,7 +47,7 @@ export function Editor({
|
|
|
47
47
|
- Preserves document structures and package parts across edits, and records who wrote each comment.
|
|
48
48
|
- Reads document styles, theme fonts, page layout, and CJK font information for display.
|
|
49
49
|
|
|
50
|
-
See [Feature support](https://docx-editor.portone.io/docs/features) for the support matrix. Build [
|
|
50
|
+
See [Feature support](https://docx-editor.portone.io/docs/features) for the support matrix. Build controls of your own with the [Editor API](https://docx-editor.portone.io/docs/editor-api) or use [programmatic DOCX import and export](https://docx-editor.portone.io/docs/core).
|
|
51
51
|
|
|
52
52
|
The full [documentation](https://docx-editor.portone.io/docs) lives on the documentation site.
|
|
53
53
|
|
package/dist/DocxEditor.js
CHANGED
|
@@ -31,7 +31,7 @@ import { usePageLayout } from "./page/usePageLayout.js";
|
|
|
31
31
|
import { editingProtection, protectionOf } from "./schema/protectionState.js";
|
|
32
32
|
import { storyNodeOf } from "./schema/stories.js";
|
|
33
33
|
import { editorClassNames } from "./styles/classNames.js";
|
|
34
|
-
import { CommentsPanel } from "./ui/CommentsPanel.js";
|
|
34
|
+
import { CommentsPanel, shownBesideThePage } from "./ui/CommentsPanel.js";
|
|
35
35
|
import { LinkCard } from "./ui/LinkCard.js";
|
|
36
36
|
import { LinkPanel } from "./ui/LinkPanel.js";
|
|
37
37
|
import { NotesPanel } from "./ui/NotesPanel.js";
|
|
@@ -280,9 +280,9 @@ function DocxEditorSurface({
|
|
|
280
280
|
const tableAnchor = live && !textAnchor ? tableMenuAnchor(live.state) : null;
|
|
281
281
|
const linkAtCursor = live && !live.view.composing && !isLinkPanelOpen(live.state) ? activeLinkSpan(live.state) : null;
|
|
282
282
|
const comments = live?.state === void 0 ? [] : commentProjection.read(live.state).comments;
|
|
283
|
-
const
|
|
283
|
+
const hasRailComments = comments.some(shownBesideThePage);
|
|
284
284
|
const composerOpen = live !== null && isCommentComposerOpen(live.state);
|
|
285
|
-
const showComments = live !== null && (commentsOpen || composerOpen ||
|
|
285
|
+
const showComments = live !== null && (commentsOpen || composerOpen || hasRailComments);
|
|
286
286
|
const commentsPanel = live && showComments && /* @__PURE__ */ jsx(
|
|
287
287
|
CommentsPanel,
|
|
288
288
|
{
|
|
@@ -75,8 +75,6 @@ export interface ParagraphStyleOption {
|
|
|
75
75
|
* `defaultParagraphStyleIdOf` reads them by.
|
|
76
76
|
*/
|
|
77
77
|
export declare function readParagraphStyles(styles: Document): ParagraphStyleOption[];
|
|
78
|
-
/** The style name the paragraph formatting XML points at. null if it points at none */
|
|
79
|
-
export declare function styleIdOf(pPr: unknown): string | null;
|
|
80
78
|
/**
|
|
81
79
|
* The values the style a paragraph wears lays down.
|
|
82
80
|
*
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
toRunFormat,
|
|
6
6
|
toTableFormat
|
|
7
7
|
} from "../../model/format.js";
|
|
8
|
-
import {
|
|
8
|
+
import { styleIdOf } from "../../ooxml/props.js";
|
|
9
9
|
import { ST_OnOff } from "../../ooxml/simpleTypes.js";
|
|
10
10
|
import { childValue, isOn, wAttr } from "../../ooxml/units.js";
|
|
11
11
|
import { childByLocalName, elementChildren } from "../../ooxml/xml.js";
|
|
@@ -201,18 +201,6 @@ function readParagraphStyles(styles) {
|
|
|
201
201
|
}
|
|
202
202
|
return options;
|
|
203
203
|
}
|
|
204
|
-
var styleIdsByPPr = /* @__PURE__ */ new Map();
|
|
205
|
-
var STYLE_ID_CACHE_LIMIT = 2e3;
|
|
206
|
-
function styleIdOf(pPr) {
|
|
207
|
-
if (typeof pPr !== "string" || !pPr.includes("pStyle")) return null;
|
|
208
|
-
const known = styleIdsByPPr.get(pPr);
|
|
209
|
-
if (known !== void 0) return known;
|
|
210
|
-
const el = parsePropsXml(pPr);
|
|
211
|
-
const id = el ? childValue(el, "pStyle") : null;
|
|
212
|
-
if (styleIdsByPPr.size >= STYLE_ID_CACHE_LIMIT) styleIdsByPPr.clear();
|
|
213
|
-
styleIdsByPPr.set(pPr, id);
|
|
214
|
-
return id;
|
|
215
|
-
}
|
|
216
204
|
function paragraphStyleFormat(pPr, styles, defaultStyleId = null) {
|
|
217
205
|
const id = styleIdOf(pPr) ?? defaultStyleId;
|
|
218
206
|
return id === null ? void 0 : styles.get(id);
|
|
@@ -241,6 +229,5 @@ export {
|
|
|
241
229
|
numberingStyleLinks,
|
|
242
230
|
paragraphStyleFormat,
|
|
243
231
|
readParagraphStyles,
|
|
244
|
-
readStyles
|
|
245
|
-
styleIdOf
|
|
232
|
+
readStyles
|
|
246
233
|
};
|
package/dist/docx/identities.js
CHANGED
|
@@ -4,8 +4,9 @@ import { DocxExportError } from "../ooxml/errors.js";
|
|
|
4
4
|
import { qualify } from "../ooxml/names.js";
|
|
5
5
|
import { parseAttrs } from "../ooxml/tagScan.js";
|
|
6
6
|
import { docxSchema } from "../schema/index.js";
|
|
7
|
+
import { wrappersOf } from "../schema/wrappers.js";
|
|
7
8
|
import { withoutParagraphIds } from "./cloning.js";
|
|
8
|
-
import {
|
|
9
|
+
import { wrapperKindOf } from "./wrappers.js";
|
|
9
10
|
var sourceBlockRule = {
|
|
10
11
|
name: "sourceBlock",
|
|
11
12
|
visit(block, written) {
|
|
@@ -51,21 +52,16 @@ var paragraphIdRule = {
|
|
|
51
52
|
);
|
|
52
53
|
}
|
|
53
54
|
};
|
|
54
|
-
function
|
|
55
|
-
return node
|
|
55
|
+
function sdtMarksOf(node) {
|
|
56
|
+
return wrappersOf(node, docxSchema.marks.sdt);
|
|
56
57
|
}
|
|
57
58
|
function controlName(mark) {
|
|
58
|
-
const key = mark.attrs.
|
|
59
|
+
const key = mark.attrs.key;
|
|
59
60
|
const prefix = mark.attrs.sdtPrefix;
|
|
60
61
|
return `${typeof key === "number" ? key : 0} ${typeof prefix === "string" ? prefix : ""}`;
|
|
61
62
|
}
|
|
62
63
|
function copiedMark(mark) {
|
|
63
|
-
|
|
64
|
-
if (typeof prefix !== "string") return mark;
|
|
65
|
-
return mark.type.create({
|
|
66
|
-
...mark.attrs,
|
|
67
|
-
sdtPrefix: copiedControlPrefix(prefix, newControlId())
|
|
68
|
-
});
|
|
64
|
+
return wrapperKindOf(mark).copy?.(mark) ?? mark;
|
|
69
65
|
}
|
|
70
66
|
function claim(mark, written) {
|
|
71
67
|
const name = controlName(mark);
|
|
@@ -75,25 +71,20 @@ function claim(mark, written) {
|
|
|
75
71
|
}
|
|
76
72
|
function rewriteParagraph(paragraph, written) {
|
|
77
73
|
const inline = [];
|
|
78
|
-
let running =
|
|
74
|
+
let running = [];
|
|
79
75
|
let renamed = false;
|
|
80
76
|
paragraph.forEach((child) => {
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
running = null;
|
|
84
|
-
inline.push(child);
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
const control = running?.mark.eq(mark) ? running : claim(mark, written);
|
|
88
|
-
running = control;
|
|
89
|
-
if (!control.copy) {
|
|
90
|
-
inline.push(child);
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
renamed = true;
|
|
94
|
-
inline.push(
|
|
95
|
-
child.mark(control.copy.addToSet(mark.removeFromSet(child.marks)))
|
|
77
|
+
const controls = sdtMarksOf(child).map(
|
|
78
|
+
(mark) => running.find((control) => control.mark.eq(mark)) ?? claim(mark, written)
|
|
96
79
|
);
|
|
80
|
+
running = controls;
|
|
81
|
+
let marks = child.marks;
|
|
82
|
+
for (const control of controls) {
|
|
83
|
+
if (!control.copy) continue;
|
|
84
|
+
renamed = true;
|
|
85
|
+
marks = control.copy.addToSet(control.mark.removeFromSet(marks));
|
|
86
|
+
}
|
|
87
|
+
inline.push(marks === child.marks ? child : child.mark(marks));
|
|
97
88
|
});
|
|
98
89
|
return renamed ? paragraph.copy(Fragment.fromArray(inline)) : paragraph;
|
|
99
90
|
}
|
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
*
|
|
6
6
|
* A `w:sdt` content control and a `w:hyperlink` standing inside the paragraph are both unwrapped, so
|
|
7
7
|
* the text they hold stays editable, and the wrapper each came in rides along on a mark to go back
|
|
8
|
-
* out around the same text.
|
|
9
|
-
*
|
|
8
|
+
* out around the same text. Either may hold the other and a control may hold a control: the nesting
|
|
9
|
+
* is read as the file wrote it and recorded on the marks (`docx/wrappers`). A link inside a link is
|
|
10
|
+
* the one arrangement no mark can record, and the inner one stays whole as it always has.
|
|
10
11
|
*
|
|
11
12
|
* Nothing here demotes a paragraph. What the editor has no model for - a field character, a
|
|
12
13
|
* tracked insertion, a symbol, a drawing nobody could read - is kept where it stood, inside its
|
|
@@ -37,8 +38,8 @@ export declare const NO_IMPORT_SOURCES: ImportSources;
|
|
|
37
38
|
/**
|
|
38
39
|
* Moves a paragraph into an editable node.
|
|
39
40
|
*
|
|
40
|
-
* Every paragraph opens editable. A child the reader has no model for, and a
|
|
41
|
-
*
|
|
42
|
-
*
|
|
41
|
+
* Every paragraph opens editable. A child the reader has no model for, and a wrapper whose shape
|
|
42
|
+
* it could not put back together, is kept whole where it stood instead of standing the whole
|
|
43
|
+
* paragraph down.
|
|
43
44
|
*/
|
|
44
45
|
export declare function buildParagraph(el: Element, srcId: string | null, sources?: ImportSources): PMNode;
|
|
@@ -12,10 +12,7 @@ import { docxSchema } from "../schema/index.js";
|
|
|
12
12
|
import { commentParaId, importedCommentReplies } from "./comments/model.js";
|
|
13
13
|
import { NO_COMMENTS } from "./comments/reading.js";
|
|
14
14
|
import { readParagraphFormat, readRunFormat } from "./formatting.js";
|
|
15
|
-
import {
|
|
16
|
-
NO_LINK_TARGETS,
|
|
17
|
-
readHyperlinkWrapper
|
|
18
|
-
} from "./hyperlink.js";
|
|
15
|
+
import { NO_LINK_TARGETS } from "./hyperlink.js";
|
|
19
16
|
import { policyFor } from "./importPolicy.js";
|
|
20
17
|
import {
|
|
21
18
|
buildPreservedInline,
|
|
@@ -24,8 +21,8 @@ import {
|
|
|
24
21
|
} from "./importPreserved.js";
|
|
25
22
|
import { NO_IMAGES } from "./media.js";
|
|
26
23
|
import { NO_NOTES, noteById } from "./notes.js";
|
|
27
|
-
import { readSdtWrapper } from "./sdt.js";
|
|
28
24
|
import { NO_THEME_FONTS } from "./theme.js";
|
|
25
|
+
import { wrapperFits, wrapperKindFor } from "./wrappers.js";
|
|
29
26
|
function runMark(run, themeFonts) {
|
|
30
27
|
const rPr = childByLocalName(run, "rPr");
|
|
31
28
|
return docxSchema.marks.run.create({
|
|
@@ -166,13 +163,6 @@ function buildRunNodes(run, images, themeFonts, comments, notes, noteLabel, wrap
|
|
|
166
163
|
}
|
|
167
164
|
return nodes;
|
|
168
165
|
}
|
|
169
|
-
var controlCounts = /* @__PURE__ */ new WeakMap();
|
|
170
|
-
var linkCounts = /* @__PURE__ */ new WeakMap();
|
|
171
|
-
function nextKey(counts, el) {
|
|
172
|
-
const key = counts.get(el.ownerDocument) ?? 0;
|
|
173
|
-
counts.set(el.ownerDocument, key + 1);
|
|
174
|
-
return key;
|
|
175
|
-
}
|
|
176
166
|
function annotationId(el) {
|
|
177
167
|
return Array.from(el.attributes).find((attribute) => attribute.localName === "id")?.value ?? null;
|
|
178
168
|
}
|
|
@@ -190,7 +180,7 @@ var NO_IMPORT_SOURCES = {
|
|
|
190
180
|
notes: NO_NOTES,
|
|
191
181
|
noteLabel: () => "?"
|
|
192
182
|
};
|
|
193
|
-
function
|
|
183
|
+
function buildModelledInline(child, sources, depth, wrappers) {
|
|
194
184
|
if (child.localName === "r") {
|
|
195
185
|
return buildRunNodes(
|
|
196
186
|
child,
|
|
@@ -202,23 +192,25 @@ function buildModelledWrapperChild(child, sources, wrappers, linkAllowed) {
|
|
|
202
192
|
wrappers
|
|
203
193
|
);
|
|
204
194
|
}
|
|
205
|
-
|
|
206
|
-
|
|
195
|
+
const kind = wrapperKindFor(child);
|
|
196
|
+
if (kind) {
|
|
197
|
+
if (!wrapperFits(kind, wrappers)) return null;
|
|
198
|
+
const reading = kind.read(child, depth, sources);
|
|
199
|
+
if (!reading) return null;
|
|
200
|
+
return buildContent(reading.content, sources, depth + 1, [
|
|
201
|
+
...wrappers,
|
|
202
|
+
reading.mark
|
|
203
|
+
]);
|
|
207
204
|
}
|
|
208
205
|
const commentMarker = commentRangeNode(child, wrappers);
|
|
209
206
|
return commentMarker === null ? null : [commentMarker];
|
|
210
207
|
}
|
|
211
|
-
function
|
|
208
|
+
function buildContent(content, sources, depth, wrappers) {
|
|
212
209
|
const nodes = [];
|
|
213
210
|
for (const child of elementChildren(content)) {
|
|
214
211
|
const policy = policyFor(child, "wrapper");
|
|
215
212
|
if (policy.tier === "model") {
|
|
216
|
-
const built =
|
|
217
|
-
child,
|
|
218
|
-
sources,
|
|
219
|
-
wrappers,
|
|
220
|
-
linkAllowed
|
|
221
|
-
);
|
|
213
|
+
const built = buildModelledInline(child, sources, depth, wrappers);
|
|
222
214
|
if (built !== null) {
|
|
223
215
|
nodes.push(...built);
|
|
224
216
|
continue;
|
|
@@ -231,45 +223,6 @@ function buildWrappedNodes(content, sources, wrappers, linkAllowed) {
|
|
|
231
223
|
if (nodes.length === 0) return null;
|
|
232
224
|
return nodes;
|
|
233
225
|
}
|
|
234
|
-
function buildHyperlinkNodes(el, sources, wrappers) {
|
|
235
|
-
const wrapper = readHyperlinkWrapper(el);
|
|
236
|
-
if (!wrapper) return null;
|
|
237
|
-
const mark = docxSchema.marks.link.create({
|
|
238
|
-
linkPrefix: wrapper.prefix,
|
|
239
|
-
href: wrapper.relId === null ? null : sources.links.get(wrapper.relId) ?? null,
|
|
240
|
-
linkKey: nextKey(linkCounts, el)
|
|
241
|
-
});
|
|
242
|
-
return buildWrappedNodes(el, sources, [...wrappers, mark], false);
|
|
243
|
-
}
|
|
244
|
-
function buildSdtNodes(el, sources) {
|
|
245
|
-
const wrapper = readSdtWrapper(el);
|
|
246
|
-
if (!wrapper) return null;
|
|
247
|
-
const mark = docxSchema.marks.sdt.create({
|
|
248
|
-
sdtPrefix: wrapper.prefix,
|
|
249
|
-
sdtKey: nextKey(controlCounts, el),
|
|
250
|
-
contentsLocked: wrapper.contentsLocked,
|
|
251
|
-
deletionLocked: wrapper.deletionLocked
|
|
252
|
-
});
|
|
253
|
-
return buildWrappedNodes(wrapper.content, sources, [mark], true);
|
|
254
|
-
}
|
|
255
|
-
function buildModelledParagraphChild(child, sources) {
|
|
256
|
-
if (child.localName === "r") {
|
|
257
|
-
return buildRunNodes(
|
|
258
|
-
child,
|
|
259
|
-
sources.images,
|
|
260
|
-
sources.themeFonts,
|
|
261
|
-
sources.comments,
|
|
262
|
-
sources.notes,
|
|
263
|
-
sources.noteLabel
|
|
264
|
-
);
|
|
265
|
-
}
|
|
266
|
-
if (child.localName === "sdt") return buildSdtNodes(child, sources);
|
|
267
|
-
if (child.localName === "hyperlink") {
|
|
268
|
-
return buildHyperlinkNodes(child, sources, []);
|
|
269
|
-
}
|
|
270
|
-
const commentMarker = commentRangeNode(child);
|
|
271
|
-
return commentMarker === null ? null : [commentMarker];
|
|
272
|
-
}
|
|
273
226
|
function buildParagraph(el, srcId, sources = NO_IMPORT_SOURCES) {
|
|
274
227
|
const inline = [];
|
|
275
228
|
let pPrElement = null;
|
|
@@ -280,7 +233,7 @@ function buildParagraph(el, srcId, sources = NO_IMPORT_SOURCES) {
|
|
|
280
233
|
pPrElement = child;
|
|
281
234
|
continue;
|
|
282
235
|
}
|
|
283
|
-
const built =
|
|
236
|
+
const built = buildModelledInline(child, sources, 0, []);
|
|
284
237
|
if (built !== null) {
|
|
285
238
|
inline.push(...built);
|
|
286
239
|
continue;
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
PERMISSION_MARKERS,
|
|
6
6
|
RANGE_MARKERS
|
|
7
7
|
} from "../ooxml/rangeMarkers.js";
|
|
8
|
+
import { WRAPPER_KINDS } from "./wrappers.js";
|
|
8
9
|
var MODEL = { tier: "model" };
|
|
9
10
|
var HIDDEN_MARKER = {
|
|
10
11
|
tier: "marker",
|
|
@@ -87,20 +88,24 @@ var PARAGRAPH_CHILDREN = [
|
|
|
87
88
|
[REVISION_CONTAINERS, INLINE_CHIP],
|
|
88
89
|
[["fldSimple", "smartTag", "customXml", "dir", "bdo", "subDoc"], INLINE_CHIP]
|
|
89
90
|
];
|
|
91
|
+
var WRAPPER_ELEMENTS = WRAPPER_KINDS.map(
|
|
92
|
+
(kind) => kind.element
|
|
93
|
+
);
|
|
90
94
|
var PARAGRAPH_LEVEL = new Map([
|
|
91
95
|
...rules([
|
|
92
|
-
[["pPr", "r"
|
|
96
|
+
[["pPr", "r"], MODEL],
|
|
93
97
|
[COMMENT_RANGE_MARKERS, MODEL],
|
|
94
|
-
...PARAGRAPH_CHILDREN
|
|
98
|
+
...PARAGRAPH_CHILDREN,
|
|
99
|
+
[WRAPPER_ELEMENTS, MODEL]
|
|
95
100
|
]),
|
|
96
101
|
...MATH.map((name) => [name, INLINE_CHIP])
|
|
97
102
|
]);
|
|
98
103
|
var WRAPPER_LEVEL = new Map([
|
|
99
104
|
...rules([
|
|
100
|
-
[["r"
|
|
105
|
+
[["r"], MODEL],
|
|
101
106
|
[COMMENT_RANGE_MARKERS, MODEL],
|
|
102
|
-
|
|
103
|
-
|
|
107
|
+
...PARAGRAPH_CHILDREN,
|
|
108
|
+
[WRAPPER_ELEMENTS, MODEL]
|
|
104
109
|
]),
|
|
105
110
|
...MATH.map((name) => [name, INLINE_CHIP])
|
|
106
111
|
]);
|
|
@@ -7,10 +7,11 @@
|
|
|
7
7
|
* a piece of that run, and one kept beside the runs is written between them, which is what the
|
|
8
8
|
* two content models admit.
|
|
9
9
|
*
|
|
10
|
-
* The inlines are grouped
|
|
11
|
-
*
|
|
12
|
-
* share
|
|
13
|
-
*
|
|
10
|
+
* The inlines are grouped from the outside in: neighbours sharing the outermost wrapper each stands
|
|
11
|
+
* inside go back into it, that grouping is made again one wrapper deeper, and at the bottom the
|
|
12
|
+
* neighbours that share their formatting become one run. `schema/wrappers` says which wrapper is
|
|
13
|
+
* which depth, and `docx/wrappers` says what each of them opens and closes as, so a wrapper of a
|
|
14
|
+
* new kind is written here without this file knowing anything about it.
|
|
14
15
|
*/
|
|
15
16
|
import type { Node as PMNode } from "prosemirror-model";
|
|
16
17
|
import { type ExportRefs } from "./exportRefs";
|
|
@@ -9,8 +9,9 @@ import {
|
|
|
9
9
|
} from "../ooxml/image.js";
|
|
10
10
|
import { wName } from "../ooxml/names.js";
|
|
11
11
|
import { escapeXml } from "../ooxml/xml.js";
|
|
12
|
+
import { wrapperMarks } from "../schema/wrappers.js";
|
|
12
13
|
import { NO_EXPORT_REFS } from "./exportRefs.js";
|
|
13
|
-
import {
|
|
14
|
+
import { wrapperKindOf } from "./wrappers.js";
|
|
14
15
|
function sameMark(a, b) {
|
|
15
16
|
if (a === null || b === null) return a === b;
|
|
16
17
|
return a.eq(b);
|
|
@@ -141,76 +142,39 @@ function addInline(parts, child, images) {
|
|
|
141
142
|
parts.push({ kind: "run", mark, pieces: [piece] });
|
|
142
143
|
}
|
|
143
144
|
}
|
|
144
|
-
function splitParagraphGroups(paragraph, refs) {
|
|
145
|
-
const groups = [];
|
|
146
|
-
paragraph.forEach((child) => {
|
|
147
|
-
const sdt = markOf(child, "sdt");
|
|
148
|
-
const link = markOf(child, "link");
|
|
149
|
-
let group = groups.at(-1);
|
|
150
|
-
if (!group || !sameMark(group.sdt, sdt)) {
|
|
151
|
-
group = { sdt, links: [] };
|
|
152
|
-
groups.push(group);
|
|
153
|
-
}
|
|
154
|
-
let inLink = group.links.at(-1);
|
|
155
|
-
if (!inLink || !sameMark(inLink.link, link)) {
|
|
156
|
-
inLink = { link, parts: [] };
|
|
157
|
-
group.links.push(inLink);
|
|
158
|
-
}
|
|
159
|
-
addInline(inLink.parts, child, refs.images);
|
|
160
|
-
});
|
|
161
|
-
return groups;
|
|
162
|
-
}
|
|
163
145
|
function renderParagraphPart(part) {
|
|
164
146
|
if (part.kind === "raw") return part.xml;
|
|
165
147
|
const open = openTagXml(wName("r"), rawAttrsOf(part.mark?.attrs.rAttrs));
|
|
166
148
|
const rPr = part.mark?.attrs.rPr;
|
|
167
149
|
return open + (typeof rPr === "string" ? rPr : "") + part.pieces.join("") + "</w:r>";
|
|
168
150
|
}
|
|
169
|
-
function
|
|
170
|
-
const
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
if (
|
|
175
|
-
|
|
176
|
-
"lost-original",
|
|
177
|
-
"a hyperlink carries neither an address nor the opening XML it goes back out as"
|
|
178
|
-
);
|
|
179
|
-
}
|
|
180
|
-
const relId = links.relIdOf(
|
|
181
|
-
href,
|
|
182
|
-
original === null ? null : relIdIn(original)
|
|
183
|
-
);
|
|
184
|
-
if (relId === void 0) {
|
|
185
|
-
if (original !== null) return original;
|
|
186
|
-
throw new DocxExportError(
|
|
187
|
-
"unsupported-content",
|
|
188
|
-
"an inserted hyperlink has no relationship to point at; export it through exportDocx"
|
|
189
|
-
);
|
|
151
|
+
function groupsAtDepth(children, depth) {
|
|
152
|
+
const groups = [];
|
|
153
|
+
for (const child of children) {
|
|
154
|
+
const mark = wrapperMarks(child)[depth] ?? null;
|
|
155
|
+
const last = groups.at(-1);
|
|
156
|
+
if (last && sameMark(last.mark, mark)) last.children.push(child);
|
|
157
|
+
else groups.push({ mark, children: [child] });
|
|
190
158
|
}
|
|
191
|
-
return
|
|
159
|
+
return groups;
|
|
192
160
|
}
|
|
193
|
-
function
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
return
|
|
161
|
+
function renderParts(children, images) {
|
|
162
|
+
const parts = [];
|
|
163
|
+
for (const child of children) addInline(parts, child, images);
|
|
164
|
+
return parts.map(renderParagraphPart).join("");
|
|
197
165
|
}
|
|
198
|
-
function
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
"a content control has lost the opening XML it goes back out as"
|
|
206
|
-
);
|
|
207
|
-
}
|
|
208
|
-
return `${prefix}<w:sdtContent>${body}</w:sdtContent></w:sdt>`;
|
|
166
|
+
function renderWrapped(children, depth, refs) {
|
|
167
|
+
return groupsAtDepth(children, depth).map((group) => {
|
|
168
|
+
if (group.mark === null) return renderParts(group.children, refs.images);
|
|
169
|
+
const kind = wrapperKindOf(group.mark);
|
|
170
|
+
const body = renderWrapped(group.children, depth + 1, refs);
|
|
171
|
+
return kind.open(group.mark, refs) + body + kind.close(group.mark);
|
|
172
|
+
}).join("");
|
|
209
173
|
}
|
|
210
174
|
function serializeParagraph(node, refs = NO_EXPORT_REFS) {
|
|
211
175
|
const open = openTagXml(wName("p"), rawAttrsOf(node.attrs.pAttrs));
|
|
212
176
|
const pPr = node.attrs.pPr;
|
|
213
|
-
const body =
|
|
177
|
+
const body = renderWrapped(node.children, 0, refs);
|
|
214
178
|
return open + (typeof pPr === "string" ? pPr : "") + body + "</w:p>";
|
|
215
179
|
}
|
|
216
180
|
export {
|
|
@@ -8,8 +8,24 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import type { Node as PMNode } from "prosemirror-model";
|
|
10
10
|
import { type PageGeometry } from "./pageGeometry";
|
|
11
|
+
import { type GridRect } from "./tableFormatting";
|
|
12
|
+
/**
|
|
13
|
+
* The limit on how many rows and cells one table can hold.
|
|
14
|
+
* The toolbar grid lets you pick from something smaller (6x6); this only blocks absurd values.
|
|
15
|
+
*/
|
|
16
|
+
export declare const MAX_TABLE_SIDE = 50;
|
|
11
17
|
/** Whether the value can be used as a row count or a cell count */
|
|
12
18
|
export declare function isTableSide(value: number): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* One cell of a table being built: the block of the grid it covers, and the blocks it holds.
|
|
21
|
+
*
|
|
22
|
+
* A cell reaching across columns or down rows exists once, as the cell it starts at, which is how
|
|
23
|
+
* the model holds a merge and what `serializeTable` writes `w:gridSpan` and `w:vMerge` from.
|
|
24
|
+
*/
|
|
25
|
+
export interface TableCellPlan {
|
|
26
|
+
rect: GridRect;
|
|
27
|
+
content?: readonly PMNode[];
|
|
28
|
+
}
|
|
13
29
|
/**
|
|
14
30
|
* A single table made up of nothing but empty cells.
|
|
15
31
|
*
|
|
@@ -20,3 +36,12 @@ export declare function isTableSide(value: number): boolean;
|
|
|
20
36
|
* The row and cell counts must be values that have passed through `isTableSide`.
|
|
21
37
|
*/
|
|
22
38
|
export declare function createTableNode(rows: number, cols: number, geometry?: PageGeometry): PMNode;
|
|
39
|
+
/**
|
|
40
|
+
* The same table, laid out and filled by the caller: the shape a table read off the clipboard
|
|
41
|
+
* takes, where the cells are neither all one wide nor all empty.
|
|
42
|
+
*
|
|
43
|
+
* Everything the table itself wears is the same as a newly inserted one's, so a pasted table is
|
|
44
|
+
* drawn and written exactly like a table the toolbar put there. The grid the cells are measured
|
|
45
|
+
* and dressed against is the one they cover between them, row by row.
|
|
46
|
+
*/
|
|
47
|
+
export declare function createTableNodeFrom(plan: readonly (readonly TableCellPlan[])[], geometry?: PageGeometry): PMNode;
|