@portone/docx-editor 0.3.0 → 0.5.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 +205 -0
- package/assets/editor.png +0 -0
- package/dist/DocxEditor.d.ts +1 -1
- package/dist/DocxEditor.js +45 -25
- package/dist/core.d.ts +1 -1
- 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 +78 -35
- package/dist/docx/exportDocx.js +30 -18
- 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/attrs.d.ts +20 -2
- package/dist/docx/formatting/attrs.js +57 -2
- package/dist/docx/formatting/context.d.ts +10 -2
- package/dist/docx/formatting/context.js +23 -5
- package/dist/docx/formatting/resolve.d.ts +10 -4
- package/dist/docx/formatting/resolve.js +31 -11
- package/dist/docx/formatting/styles.d.ts +25 -2
- package/dist/docx/formatting/styles.js +84 -3
- package/dist/docx/headersFooters.d.ts +89 -20
- package/dist/docx/headersFooters.js +163 -172
- package/dist/docx/hyperlink.d.ts +14 -4
- package/dist/docx/hyperlink.js +4 -1
- package/dist/docx/importDocx.d.ts +1 -1
- package/dist/docx/importDocx.js +135 -74
- package/dist/docx/importParagraph.d.ts +16 -4
- package/dist/docx/importParagraph.js +87 -93
- package/dist/docx/importPolicy.d.ts +70 -0
- package/dist/docx/importPolicy.js +190 -0
- package/dist/docx/importPreserved.d.ts +30 -0
- package/dist/docx/importPreserved.js +54 -0
- package/dist/docx/importTable.d.ts +2 -2
- package/dist/docx/importTable.js +107 -66
- package/dist/docx/invariants.js +42 -31
- package/dist/docx/newLists.d.ts +30 -0
- package/dist/docx/newLists.js +24 -1
- package/dist/docx/notes.d.ts +6 -1
- package/dist/docx/notes.js +6 -16
- package/dist/docx/numberingPlanner.d.ts +6 -3
- package/dist/docx/numberingPlanner.js +38 -7
- 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 +4 -0
- package/dist/docx/serializeParagraph.js +1 -0
- 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 +24 -7
- package/dist/docx/session.js +37 -6
- 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/tableFormatting/conditions.d.ts +76 -0
- package/dist/docx/tableFormatting/conditions.js +196 -0
- package/dist/docx/tableFormatting/editing.d.ts +7 -3
- package/dist/docx/tableFormatting/editing.js +4 -5
- package/dist/docx/tableFormatting/reading.d.ts +49 -19
- package/dist/docx/tableFormatting/reading.js +109 -35
- package/dist/docx/tableFormatting.d.ts +1 -0
- package/dist/docx/tableFormatting.js +1 -0
- package/dist/docx/tableTemplate.js +12 -12
- package/dist/editor/clipboard/htmlReader.d.ts +30 -0
- package/dist/editor/clipboard/htmlReader.js +302 -0
- 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 +320 -0
- package/dist/editor/clipboard/readContext.d.ts +24 -0
- package/dist/editor/clipboard/readContext.js +19 -0
- package/dist/editor/clipboard/readers.d.ts +33 -0
- package/dist/editor/clipboard/readers.js +37 -0
- package/dist/editor/commands/comments/editing.d.ts +26 -6
- package/dist/editor/commands/comments/editing.js +175 -105
- 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/formatting/propertyCommands.js +6 -1
- package/dist/editor/commands/index.d.ts +2 -2
- package/dist/editor/commands/index.js +2 -0
- package/dist/editor/commands/listCommands.d.ts +12 -3
- package/dist/editor/commands/listCommands.js +76 -30
- package/dist/editor/commands/noteQueries.d.ts +22 -5
- package/dist/editor/commands/noteQueries.js +34 -6
- package/dist/editor/commands/paragraphCommands.js +6 -1
- package/dist/editor/createEditor.js +14 -4
- package/dist/editor/documentStyles.d.ts +26 -4
- package/dist/editor/documentStyles.js +9 -4
- package/dist/editor/editorDocument.d.ts +7 -6
- package/dist/editor/editorDocument.js +18 -6
- 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/paragraphEdits.d.ts +16 -1
- package/dist/editor/paragraphEdits.js +13 -5
- package/dist/editor/paragraphPlacement.d.ts +11 -0
- package/dist/editor/paragraphPlacement.js +18 -0
- 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 +20 -10
- package/dist/editor/plugins/linkPanel.d.ts +7 -3
- package/dist/editor/plugins/linkPanel.js +13 -27
- package/dist/editor/plugins/numberingDecorations.d.ts +10 -4
- package/dist/editor/plugins/numberingDecorations.js +43 -2
- package/dist/editor/plugins/panelState.d.ts +67 -0
- package/dist/editor/plugins/panelState.js +54 -0
- package/dist/editor/plugins/paragraphDisplay.js +9 -3
- package/dist/editor/plugins/tableContextMenu.d.ts +1 -1
- package/dist/editor/plugins/tableContextMenu.js +30 -47
- package/dist/editor/plugins/tableDisplay.js +19 -5
- package/dist/editor/plugins/textContextMenu.d.ts +1 -1
- package/dist/editor/plugins/textContextMenu.js +30 -47
- package/dist/model/format.d.ts +50 -0
- package/dist/model/format.js +65 -0
- package/dist/numbering/listRegistry.d.ts +37 -0
- package/dist/numbering/listRegistry.js +112 -0
- package/dist/numbering/listTemplate.d.ts +26 -17
- package/dist/numbering/listTemplate.js +66 -20
- package/dist/numbering/markers.d.ts +15 -6
- package/dist/numbering/markers.js +26 -59
- package/dist/numbering/parseNumbering.d.ts +70 -5
- package/dist/numbering/parseNumbering.js +100 -26
- package/dist/numbering/spellers.d.ts +15 -0
- package/dist/numbering/spellers.js +96 -0
- package/dist/numbering/writeNumbering.d.ts +5 -3
- package/dist/numbering/writeNumbering.js +15 -15
- package/dist/ooxml/conformance.d.ts +28 -0
- package/dist/ooxml/conformance.js +20 -0
- package/dist/ooxml/errors.d.ts +5 -5
- 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 +15 -0
- package/dist/ooxml/names.js +10 -1
- package/dist/ooxml/partSplice.d.ts +1 -1
- package/dist/ooxml/partSplice.js +10 -3
- package/dist/ooxml/props.d.ts +8 -0
- package/dist/ooxml/props.js +14 -4
- package/dist/ooxml/rangeMarkers.d.ts +5 -0
- package/dist/ooxml/rangeMarkers.js +24 -0
- package/dist/ooxml/xml.d.ts +8 -9
- package/dist/ooxml/xml.js +6 -6
- package/dist/page/PageGuides.d.ts +8 -4
- package/dist/page/PageGuides.js +13 -37
- package/dist/page/pageLayout.d.ts +62 -13
- package/dist/page/pageLayout.js +91 -37
- package/dist/page/usePageLayout.d.ts +14 -11
- package/dist/page/usePageLayout.js +21 -29
- package/dist/schema/attrRoles.js +44 -19
- package/dist/schema/docxSchema.d.ts +9 -1
- package/dist/schema/docxSchema.js +220 -139
- 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/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.js +3 -1
- package/dist/schema/stories.d.ts +49 -0
- package/dist/schema/stories.js +78 -0
- package/dist/styles/classNames.d.ts +16 -7
- package/dist/styles/classNames.js +16 -7
- package/dist/styles.css +90 -30
- package/dist/table/cellFormatting.js +1 -2
- package/dist/table/gridBorders.d.ts +11 -15
- package/dist/table/gridBorders.js +32 -18
- 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 +1 -1
- package/dist/editor/externalClipboard.d.ts +0 -11
- package/dist/editor/externalClipboard.js +0 -456
package/dist/docx/session.d.ts
CHANGED
|
@@ -10,10 +10,12 @@
|
|
|
10
10
|
import type { Node as PMNode } from "prosemirror-model";
|
|
11
11
|
import type { DocumentDefaults } from "../model/format";
|
|
12
12
|
import { type Numbering, type NumberingOptions } from "../numbering/parseNumbering";
|
|
13
|
-
import type
|
|
14
|
-
import type {
|
|
15
|
-
import type
|
|
13
|
+
import { type StoryKey } from "../schema/stories";
|
|
14
|
+
import type { ImportedComments } from "./comments/reading";
|
|
15
|
+
import { type FormattingContext, type ParagraphStyleOption } from "./formatting";
|
|
16
|
+
import type { HeaderFooterStories } from "./headersFooters";
|
|
16
17
|
import type { PageGeometry } from "./pageGeometry";
|
|
18
|
+
import type { ImportedStory } from "./story";
|
|
17
19
|
export interface ImportedBlock {
|
|
18
20
|
xml: string;
|
|
19
21
|
/** The node as it was the moment the document was opened. Compared against the current node to tell whether it was edited */
|
|
@@ -55,7 +57,7 @@ export declare class SessionStore implements DocxSession, SessionIdentity {
|
|
|
55
57
|
readonly defaults: DocumentDefaults;
|
|
56
58
|
/** The effective automatic tab interval read from settings.xml. Used for display only. */
|
|
57
59
|
readonly defaultTabStopPt: number;
|
|
58
|
-
/** The paper this document is written on, read from the first section. Used for display only: the `w:sectPr` itself goes back out
|
|
60
|
+
/** The paper this document is written on, read from the first section. Used for display only: the `w:sectPr` itself goes back out as the text it arrived as */
|
|
59
61
|
readonly geometry: PageGeometry;
|
|
60
62
|
/** Everything the display values of a paragraph or a run are resolved against: the style chain, the defaults, the list definitions */
|
|
61
63
|
readonly formatting: FormattingContext;
|
|
@@ -69,9 +71,24 @@ export declare class SessionStore implements DocxSession, SessionIdentity {
|
|
|
69
71
|
readonly comments: ImportedComments;
|
|
70
72
|
/** Comment ids referenced by the original main story. A missing id after editing means deletion. */
|
|
71
73
|
readonly commentReferenceIds: ReadonlySet<string>;
|
|
72
|
-
/**
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
/**
|
|
75
|
+
* What picking one section's headers and footers takes beyond the section itself: which story
|
|
76
|
+
* each header or footer relationship names, and the document-wide switch between odd and even.
|
|
77
|
+
* The stories themselves stand with every other one, so an edited header is read the same way.
|
|
78
|
+
*/
|
|
79
|
+
readonly headerFooterStories: HeaderFooterStories;
|
|
80
|
+
/**
|
|
81
|
+
* The side stories the package arrived holding - a comment's body, a footnote's - each as the
|
|
82
|
+
* blocks it was written in. What the document currently says stands on the document node
|
|
83
|
+
* instead (`docx/story`), so the two compare the way a body block and its original do.
|
|
84
|
+
*/
|
|
85
|
+
readonly stories: ReadonlyMap<StoryKey, ImportedStory>;
|
|
86
|
+
constructor(opened: Omit<SessionStore, "kind" | "blocksOf">);
|
|
87
|
+
/**
|
|
88
|
+
* The blocks of one story as it arrived, which is what a block key of that story indexes into.
|
|
89
|
+
* Empty for a story this document does not hold.
|
|
90
|
+
*/
|
|
91
|
+
blocksOf(key: string): readonly ImportedBlock[];
|
|
75
92
|
}
|
|
76
93
|
/**
|
|
77
94
|
* The store behind a session token.
|
package/dist/docx/session.js
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
import {
|
|
3
3
|
parseNumbering
|
|
4
4
|
} from "../numbering/parseNumbering.js";
|
|
5
|
+
import { asStoryKey } from "../schema/stories.js";
|
|
6
|
+
import {
|
|
7
|
+
numberingOptionsFor
|
|
8
|
+
} from "./formatting.js";
|
|
5
9
|
var sessionsOpened = 0;
|
|
6
10
|
function newSessionId() {
|
|
7
11
|
sessionsOpened += 1;
|
|
@@ -21,7 +25,7 @@ var SessionStore = class {
|
|
|
21
25
|
defaults;
|
|
22
26
|
/** The effective automatic tab interval read from settings.xml. Used for display only. */
|
|
23
27
|
defaultTabStopPt;
|
|
24
|
-
/** The paper this document is written on, read from the first section. Used for display only: the `w:sectPr` itself goes back out
|
|
28
|
+
/** The paper this document is written on, read from the first section. Used for display only: the `w:sectPr` itself goes back out as the text it arrived as */
|
|
25
29
|
geometry;
|
|
26
30
|
/** Everything the display values of a paragraph or a run are resolved against: the style chain, the defaults, the list definitions */
|
|
27
31
|
formatting;
|
|
@@ -35,8 +39,18 @@ var SessionStore = class {
|
|
|
35
39
|
comments;
|
|
36
40
|
/** Comment ids referenced by the original main story. A missing id after editing means deletion. */
|
|
37
41
|
commentReferenceIds;
|
|
38
|
-
/**
|
|
39
|
-
|
|
42
|
+
/**
|
|
43
|
+
* What picking one section's headers and footers takes beyond the section itself: which story
|
|
44
|
+
* each header or footer relationship names, and the document-wide switch between odd and even.
|
|
45
|
+
* The stories themselves stand with every other one, so an edited header is read the same way.
|
|
46
|
+
*/
|
|
47
|
+
headerFooterStories;
|
|
48
|
+
/**
|
|
49
|
+
* The side stories the package arrived holding - a comment's body, a footnote's - each as the
|
|
50
|
+
* blocks it was written in. What the document currently says stands on the document node
|
|
51
|
+
* instead (`docx/story`), so the two compare the way a body block and its original do.
|
|
52
|
+
*/
|
|
53
|
+
stories;
|
|
40
54
|
constructor(opened) {
|
|
41
55
|
this.sessionId = opened.sessionId;
|
|
42
56
|
this.parts = opened.parts;
|
|
@@ -54,7 +68,17 @@ var SessionStore = class {
|
|
|
54
68
|
this.numberingPartPath = opened.numberingPartPath;
|
|
55
69
|
this.comments = opened.comments;
|
|
56
70
|
this.commentReferenceIds = opened.commentReferenceIds;
|
|
57
|
-
this.
|
|
71
|
+
this.headerFooterStories = opened.headerFooterStories;
|
|
72
|
+
this.stories = opened.stories;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* The blocks of one story as it arrived, which is what a block key of that story indexes into.
|
|
76
|
+
* Empty for a story this document does not hold.
|
|
77
|
+
*/
|
|
78
|
+
blocksOf(key) {
|
|
79
|
+
if (key === BODY_STORY_KEY) return this.blocks;
|
|
80
|
+
const story = asStoryKey(key);
|
|
81
|
+
return story === null ? [] : this.stories.get(story)?.blocks ?? [];
|
|
58
82
|
}
|
|
59
83
|
};
|
|
60
84
|
function sessionOf(session) {
|
|
@@ -65,7 +89,14 @@ function documentPartPath(session) {
|
|
|
65
89
|
return sessionOf(session).mainPartPath;
|
|
66
90
|
}
|
|
67
91
|
function documentNumbering(session, options) {
|
|
68
|
-
|
|
92
|
+
const store = sessionOf(session);
|
|
93
|
+
return parseNumbering(store.numberingXml, {
|
|
94
|
+
...numberingOptionsFor(
|
|
95
|
+
store.formatting.styles,
|
|
96
|
+
store.formatting.themeFonts
|
|
97
|
+
),
|
|
98
|
+
...options
|
|
99
|
+
});
|
|
69
100
|
}
|
|
70
101
|
var BODY_STORY_KEY = "body";
|
|
71
102
|
function blockKey(session, storyKey, index) {
|
|
@@ -90,7 +121,7 @@ function originalBlock(node, session) {
|
|
|
90
121
|
if (typeof srcId !== "string") return void 0;
|
|
91
122
|
const key = splitBlockKey(srcId);
|
|
92
123
|
if (key === null || key.sessionId !== session.sessionId) return void 0;
|
|
93
|
-
return key.storyKey
|
|
124
|
+
return session.blocksOf(key.storyKey)[key.index];
|
|
94
125
|
}
|
|
95
126
|
export {
|
|
96
127
|
BODY_STORY_KEY,
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads a side story - a comment's body, a footnote's - the way the body of the document is read.
|
|
3
|
+
*
|
|
4
|
+
* A story is a document of the same schema, built by the same block readers, sliced verbatim by
|
|
5
|
+
* the same scanner. That is what lets a comment keep its bold run, its paragraph style and its
|
|
6
|
+
* second paragraph through an edit: the editor no longer holds a body as the plain text it reads
|
|
7
|
+
* as, but as the blocks it was written in, and writes back the ones nobody touched exactly as they
|
|
8
|
+
* arrived (`./serializeStory`).
|
|
9
|
+
*
|
|
10
|
+
* The stories the package arrived holding live on the session; what the document currently says
|
|
11
|
+
* lives on the document node, under `doc.attrs.stories`, so an edit to one rides a transaction and
|
|
12
|
+
* lands in the history beside every other edit.
|
|
13
|
+
*/
|
|
14
|
+
import { type Node as PMNode } from "prosemirror-model";
|
|
15
|
+
import { type StoryKey, type StoryKind, storyNodeOf } from "../schema/stories";
|
|
16
|
+
import { type FormattingContext } from "./formatting";
|
|
17
|
+
import { type ImportSources } from "./importParagraph";
|
|
18
|
+
import { type ImportedBlock, type SessionIdentity } from "./session";
|
|
19
|
+
export type { StoryJson, StoryKey, StoryKind } from "../schema/stories";
|
|
20
|
+
export { asStoryKey, sameStory, storiesOf, storyKey, withoutCommentStories, } from "../schema/stories";
|
|
21
|
+
/** The story this key names as a document node, and null where the document holds none */
|
|
22
|
+
export declare const storyOf: typeof storyNodeOf;
|
|
23
|
+
/** One story as the package arrived holding it: where it stands, and the blocks it was written in */
|
|
24
|
+
export interface ImportedStory {
|
|
25
|
+
key: StoryKey;
|
|
26
|
+
kind: StoryKind;
|
|
27
|
+
id: string;
|
|
28
|
+
partPath: string;
|
|
29
|
+
/** The whole element as written, which is what a story nobody edited goes back out as */
|
|
30
|
+
xml: string;
|
|
31
|
+
/**
|
|
32
|
+
* The container's opening tag as written, e.g. `<w:comment w:id="4" w:author="A">`. A container
|
|
33
|
+
* that arrived self-closing is written out as a pair, since an edited story needs one
|
|
34
|
+
*/
|
|
35
|
+
open: string;
|
|
36
|
+
/** Everything after the last block, the closing tag included */
|
|
37
|
+
close: string;
|
|
38
|
+
/** The blocks between them, sliced verbatim; a block's `srcId` indexes into this list */
|
|
39
|
+
blocks: readonly ImportedBlock[];
|
|
40
|
+
doc: PMNode;
|
|
41
|
+
}
|
|
42
|
+
/** Where a run of stories of one kind is written down, and what tells one of them from the next */
|
|
43
|
+
export interface StoryPart {
|
|
44
|
+
kind: StoryKind;
|
|
45
|
+
partPath: string;
|
|
46
|
+
xml: string;
|
|
47
|
+
/** The local name of the element each story stands in, e.g. `comment` or `footnote` */
|
|
48
|
+
entryName: string;
|
|
49
|
+
/** The attribute naming which story an element is */
|
|
50
|
+
idAttr: string;
|
|
51
|
+
}
|
|
52
|
+
/** What reading a story takes beyond the text of it */
|
|
53
|
+
export interface StoryDeps {
|
|
54
|
+
session: SessionIdentity;
|
|
55
|
+
/**
|
|
56
|
+
* The images and links of the part the story stands in. A story holds neither a comment nor a
|
|
57
|
+
* note of its own - WordprocessingML puts neither there - so those two are read empty.
|
|
58
|
+
*/
|
|
59
|
+
sources: ImportSources;
|
|
60
|
+
formatting: FormattingContext;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Moves a single block into a node.
|
|
64
|
+
*
|
|
65
|
+
* A paragraph always opens editable (`./importParagraph`). What is left over is a table whose rows
|
|
66
|
+
* this reader could not take apart, a range marker standing between blocks, and a block this
|
|
67
|
+
* reader has no reader for at all; each stands as one placeholder naming the original fragment.
|
|
68
|
+
*/
|
|
69
|
+
export declare function buildBlock(el: Element, srcId: string, sources: ImportSources, context: FormattingContext): PMNode;
|
|
70
|
+
/**
|
|
71
|
+
* Folds the style chain into the display values.
|
|
72
|
+
*
|
|
73
|
+
* These values are used for display only, so the original XML fragments are left untouched.
|
|
74
|
+
* A table is left alone: which part of it a cell belongs to is what its table style dresses the
|
|
75
|
+
* paragraphs inside by, and `buildTable` is where that is known.
|
|
76
|
+
*/
|
|
77
|
+
export declare function withStyleFormats(node: PMNode, context: FormattingContext): PMNode;
|
|
78
|
+
/**
|
|
79
|
+
* One story as it stands in the part, with its blocks read and sliced.
|
|
80
|
+
*
|
|
81
|
+
* A container holding no block at all still opens editable, the way a body holding nothing but a
|
|
82
|
+
* section does: the placeholder paragraph names an empty original fragment, so a story nobody
|
|
83
|
+
* edited goes back out as the bytes it arrived as.
|
|
84
|
+
*/
|
|
85
|
+
export declare function readStory(place: {
|
|
86
|
+
kind: StoryKind;
|
|
87
|
+
id: string;
|
|
88
|
+
partPath: string;
|
|
89
|
+
}, container: {
|
|
90
|
+
el: Element;
|
|
91
|
+
xml: string;
|
|
92
|
+
}, deps: StoryDeps): ImportedStory;
|
|
93
|
+
/** Every story one part holds, in the order the part writes them */
|
|
94
|
+
export declare function readStories(part: StoryPart, deps: StoryDeps): readonly ImportedStory[];
|
|
95
|
+
/** The stories of a document keyed the way the session holds them */
|
|
96
|
+
export declare function storiesByKey(stories: Iterable<ImportedStory>): ReadonlyMap<StoryKey, ImportedStory>;
|
|
97
|
+
/**
|
|
98
|
+
* What writing a story takes of a transaction: the document it stands on, and the one step that
|
|
99
|
+
* changes a document attr.
|
|
100
|
+
*
|
|
101
|
+
* A `Transaction` is what a caller passes, and it is named by shape rather than imported because
|
|
102
|
+
* `src/core.ts` must reach neither the editor nor `prosemirror-state` (`src/core.test.ts`), and
|
|
103
|
+
* this module is on the way there.
|
|
104
|
+
*/
|
|
105
|
+
interface StoryWriter {
|
|
106
|
+
readonly doc: PMNode;
|
|
107
|
+
setDocAttribute(attr: string, value: unknown): this;
|
|
108
|
+
}
|
|
109
|
+
/** The document with this story written into it, which is the one way a story changes */
|
|
110
|
+
export declare function setStory<T extends StoryWriter>(tr: T, key: StoryKey, story: PMNode): T;
|
|
111
|
+
/** The document with these stories taken out of it, which is what deleting what they say leaves */
|
|
112
|
+
export declare function withoutStories<T extends StoryWriter>(tr: T, keys: Iterable<StoryKey>): T;
|
|
113
|
+
/**
|
|
114
|
+
* A story holding this text, one paragraph broken by a line break wherever the text is.
|
|
115
|
+
*
|
|
116
|
+
* That is the shape a plain-text body has been written in since before there were stories, so a
|
|
117
|
+
* body written through `updateComment` goes out as the bytes it always did.
|
|
118
|
+
*/
|
|
119
|
+
export declare function storyFromText(text: string): PMNode;
|
|
120
|
+
/**
|
|
121
|
+
* What one leaf of a story puts on screen.
|
|
122
|
+
*
|
|
123
|
+
* A break ends a line, and a fragment the editor keeps rather than models says what it stands for
|
|
124
|
+
* - the character a `w:noBreakHyphen` draws, the words inside a field - which is the same answer
|
|
125
|
+
* `docx/importPolicy` gave when a body was flattened on the way in. Everything reading a story as
|
|
126
|
+
* plain text - a comment body, a footnote body, a header preview - asks here rather than keeping a
|
|
127
|
+
* vocabulary of its own, so the three of them cannot disagree.
|
|
128
|
+
*/
|
|
129
|
+
export declare function storyLeafText(leaf: PMNode): string;
|
|
130
|
+
/** What a story reads as on screen: its paragraphs joined by newlines, its breaks and tabs kept */
|
|
131
|
+
export declare function storyText(story: PMNode | null): string;
|
|
132
|
+
/**
|
|
133
|
+
* The story with the thread key on the last paragraph of it, which is where Word keeps it.
|
|
134
|
+
*
|
|
135
|
+
* The key is written into the paragraph's own attribute text rather than into a model attr,
|
|
136
|
+
* because that text is what the writer puts back and a `w14:paraId` is not a name this schema
|
|
137
|
+
* holds. A paragraph already carrying one keeps it: a key already written is what the thread
|
|
138
|
+
* state elsewhere is keyed by, so re-pointing it would orphan that state.
|
|
139
|
+
*/
|
|
140
|
+
export declare function withThreadKeyOn(story: PMNode, paraId: string): PMNode;
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
// src/docx/story.ts
|
|
2
|
+
import { Fragment } from "prosemirror-model";
|
|
3
|
+
import { xmlnsAttr } from "../ooxml/element.js";
|
|
4
|
+
import { DocxImportError } from "../ooxml/errors.js";
|
|
5
|
+
import { NAMESPACES, qualify } from "../ooxml/names.js";
|
|
6
|
+
import { attributeByLocalName, elementChildren, parseXml } from "../ooxml/xml.js";
|
|
7
|
+
import { docxSchema } from "../schema/index.js";
|
|
8
|
+
import { isPreservedNode } from "../schema/preservedFragments.js";
|
|
9
|
+
import {
|
|
10
|
+
STORIES_ATTR,
|
|
11
|
+
storiesOf,
|
|
12
|
+
storyKey,
|
|
13
|
+
storyNodeOf
|
|
14
|
+
} from "../schema/stories.js";
|
|
15
|
+
import { styledParagraph } from "./formatting.js";
|
|
16
|
+
import { buildParagraph } from "./importParagraph.js";
|
|
17
|
+
import { buildPreservedBlock } from "./importPreserved.js";
|
|
18
|
+
import { buildTable } from "./importTable.js";
|
|
19
|
+
import { scanBlocksIn } from "./scan.js";
|
|
20
|
+
import { blockKey } from "./session.js";
|
|
21
|
+
import {
|
|
22
|
+
asStoryKey,
|
|
23
|
+
sameStory,
|
|
24
|
+
storiesOf as storiesOf2,
|
|
25
|
+
storyKey as storyKey2,
|
|
26
|
+
withoutCommentStories
|
|
27
|
+
} from "../schema/stories.js";
|
|
28
|
+
var storyOf = storyNodeOf;
|
|
29
|
+
function buildBlock(el, srcId, sources, context) {
|
|
30
|
+
if (el.localName === "p") return buildParagraph(el, srcId, sources);
|
|
31
|
+
if (el.localName === "tbl") {
|
|
32
|
+
const table = buildTable(el, srcId, sources, context);
|
|
33
|
+
if (table) return table;
|
|
34
|
+
}
|
|
35
|
+
return buildPreservedBlock(el, srcId, "body");
|
|
36
|
+
}
|
|
37
|
+
function withStyleFormats(node, context) {
|
|
38
|
+
if (node.type === docxSchema.nodes.paragraph) {
|
|
39
|
+
return styledParagraph(node, context);
|
|
40
|
+
}
|
|
41
|
+
if (node.childCount === 0 || node.type === docxSchema.nodes.table) {
|
|
42
|
+
return node;
|
|
43
|
+
}
|
|
44
|
+
const children = node.children.map(
|
|
45
|
+
(child) => withStyleFormats(child, context)
|
|
46
|
+
);
|
|
47
|
+
if (children.every((child, i) => child === node.child(i))) return node;
|
|
48
|
+
return node.copy(Fragment.fromArray(children));
|
|
49
|
+
}
|
|
50
|
+
function malformed(what) {
|
|
51
|
+
throw new DocxImportError("malformed-xml", what);
|
|
52
|
+
}
|
|
53
|
+
var SELF_CLOSING = /\/>\s*$/;
|
|
54
|
+
function scanElement(xml, el) {
|
|
55
|
+
const scan = scanBlocksIn(xml, (_tag, depth) => depth === 0);
|
|
56
|
+
if (scan !== null) return scan;
|
|
57
|
+
if (!SELF_CLOSING.test(xml) || el.children.length > 0) {
|
|
58
|
+
malformed(`no ${el.nodeName} element to read from`);
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
prefix: xml.replace(SELF_CLOSING, ">"),
|
|
62
|
+
blocks: [],
|
|
63
|
+
suffix: `</${el.nodeName}>`
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function scanContainer(xml, el) {
|
|
67
|
+
const scan = scanElement(xml, el);
|
|
68
|
+
const children = elementChildren(el);
|
|
69
|
+
if (children.length !== scan.blocks.length) {
|
|
70
|
+
malformed(
|
|
71
|
+
`the block count of ${el.nodeName} differs between the scan and the DOM`
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
children.forEach((child, at) => {
|
|
75
|
+
if (child.nodeName !== scan.blocks[at].name) {
|
|
76
|
+
malformed(
|
|
77
|
+
`story block names disagree: ${child.nodeName} vs ${scan.blocks[at].name}`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
return { scan, children };
|
|
82
|
+
}
|
|
83
|
+
function readStory(place, container, deps) {
|
|
84
|
+
const key = storyKey(place.kind, place.id);
|
|
85
|
+
const { scan, children } = scanContainer(container.xml, container.el);
|
|
86
|
+
const nodes = children.map(
|
|
87
|
+
(child, at) => withStyleFormats(
|
|
88
|
+
buildBlock(
|
|
89
|
+
child,
|
|
90
|
+
blockKey(deps.session, key, at),
|
|
91
|
+
deps.sources,
|
|
92
|
+
deps.formatting
|
|
93
|
+
),
|
|
94
|
+
deps.formatting
|
|
95
|
+
)
|
|
96
|
+
);
|
|
97
|
+
const blocks = nodes.map((node, at) => ({
|
|
98
|
+
xml: scan.blocks[at].xml,
|
|
99
|
+
node
|
|
100
|
+
}));
|
|
101
|
+
if (nodes.length === 0) {
|
|
102
|
+
const placeholder = withStyleFormats(
|
|
103
|
+
docxSchema.nodes.paragraph.create({
|
|
104
|
+
srcId: blockKey(deps.session, key, 0)
|
|
105
|
+
}),
|
|
106
|
+
deps.formatting
|
|
107
|
+
);
|
|
108
|
+
nodes.push(placeholder);
|
|
109
|
+
blocks.push({ xml: "", node: placeholder });
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
key,
|
|
113
|
+
kind: place.kind,
|
|
114
|
+
id: place.id,
|
|
115
|
+
partPath: place.partPath,
|
|
116
|
+
xml: container.xml,
|
|
117
|
+
open: scan.prefix,
|
|
118
|
+
close: scan.suffix,
|
|
119
|
+
blocks,
|
|
120
|
+
doc: docxSchema.nodes.doc.create(null, nodes)
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function readStories(part, deps) {
|
|
124
|
+
const root = parseXml(part.xml).documentElement;
|
|
125
|
+
const scan = scanElement(part.xml, root);
|
|
126
|
+
const children = elementChildren(root);
|
|
127
|
+
if (children.length !== scan.blocks.length) {
|
|
128
|
+
malformed(
|
|
129
|
+
`the entry count of ${part.partPath} differs between the scan and the DOM`
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
const stories = [];
|
|
133
|
+
const seen = /* @__PURE__ */ new Set();
|
|
134
|
+
children.forEach((el, at) => {
|
|
135
|
+
if (el.localName !== part.entryName) return;
|
|
136
|
+
const id = attributeByLocalName(el, part.idAttr);
|
|
137
|
+
if (id === null || seen.has(id)) return;
|
|
138
|
+
seen.add(id);
|
|
139
|
+
const xml = scan.blocks[at].xml;
|
|
140
|
+
stories.push(
|
|
141
|
+
readStory(
|
|
142
|
+
{ kind: part.kind, id, partPath: part.partPath },
|
|
143
|
+
{ el, xml: xml.slice(xml.indexOf("<")) },
|
|
144
|
+
deps
|
|
145
|
+
)
|
|
146
|
+
);
|
|
147
|
+
});
|
|
148
|
+
return stories;
|
|
149
|
+
}
|
|
150
|
+
function storiesByKey(stories) {
|
|
151
|
+
return new Map(Array.from(stories, (story) => [story.key, story]));
|
|
152
|
+
}
|
|
153
|
+
function setStory(tr, key, story) {
|
|
154
|
+
const json = story.toJSON();
|
|
155
|
+
return tr.setDocAttribute(STORIES_ATTR, {
|
|
156
|
+
...storiesOf(tr.doc),
|
|
157
|
+
[key]: json
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
function withoutStories(tr, keys) {
|
|
161
|
+
const dropped = new Set(keys);
|
|
162
|
+
const stories = storiesOf(tr.doc);
|
|
163
|
+
const kept = Object.fromEntries(
|
|
164
|
+
Object.entries(stories).filter(([held]) => !dropped.has(held))
|
|
165
|
+
);
|
|
166
|
+
if (Object.keys(kept).length === Object.keys(stories).length) return tr;
|
|
167
|
+
return tr.setDocAttribute(STORIES_ATTR, kept);
|
|
168
|
+
}
|
|
169
|
+
function storyFromText(text) {
|
|
170
|
+
const pieces = [];
|
|
171
|
+
text.split("\n").forEach((line, at) => {
|
|
172
|
+
if (at > 0) pieces.push(docxSchema.nodes.hardBreak.create());
|
|
173
|
+
if (line.length > 0) pieces.push(docxSchema.text(line));
|
|
174
|
+
});
|
|
175
|
+
return docxSchema.nodes.doc.create(null, [
|
|
176
|
+
docxSchema.nodes.paragraph.create(null, pieces)
|
|
177
|
+
]);
|
|
178
|
+
}
|
|
179
|
+
var EMBEDDED = /* @__PURE__ */ new Set([
|
|
180
|
+
"drawing",
|
|
181
|
+
"pict",
|
|
182
|
+
"object",
|
|
183
|
+
"AlternateContent"
|
|
184
|
+
]);
|
|
185
|
+
function storyLeafText(leaf) {
|
|
186
|
+
if (leaf.isText) return leaf.text ?? "";
|
|
187
|
+
if (leaf.type === docxSchema.nodes.hardBreak) return "\n";
|
|
188
|
+
if (!isPreservedNode(leaf)) return "";
|
|
189
|
+
const element = leaf.attrs.element;
|
|
190
|
+
if (typeof element === "string" && EMBEDDED.has(element)) return "";
|
|
191
|
+
if (leaf.attrs.display === "break") return "\n";
|
|
192
|
+
return typeof leaf.attrs.text === "string" ? leaf.attrs.text : "";
|
|
193
|
+
}
|
|
194
|
+
function storyText(story) {
|
|
195
|
+
if (story === null) return "";
|
|
196
|
+
return story.textBetween(0, story.content.size, "\n", storyLeafText);
|
|
197
|
+
}
|
|
198
|
+
function withThreadKeyOn(story, paraId) {
|
|
199
|
+
let at = -1;
|
|
200
|
+
story.forEach((block, _offset, index) => {
|
|
201
|
+
if (block.type === docxSchema.nodes.paragraph) at = index;
|
|
202
|
+
});
|
|
203
|
+
if (at === -1) return story;
|
|
204
|
+
const paragraph = story.child(at);
|
|
205
|
+
const written = paragraph.attrs.pAttrs;
|
|
206
|
+
const pAttrs = typeof written === "string" ? written : "";
|
|
207
|
+
if (/\bw14:paraId\s*=/.test(pAttrs)) return story;
|
|
208
|
+
const declared = /\bxmlns:w14\s*=/.test(pAttrs) ? "" : `${xmlnsAttr("w14")[0]}="${NAMESPACES.w14}" `;
|
|
209
|
+
const keyed = paragraph.type.create(
|
|
210
|
+
{
|
|
211
|
+
...paragraph.attrs,
|
|
212
|
+
pAttrs: `${pAttrs === "" ? "" : `${pAttrs} `}${declared}${qualify("w14", "paraId")}="${paraId}"`
|
|
213
|
+
},
|
|
214
|
+
paragraph.content,
|
|
215
|
+
paragraph.marks
|
|
216
|
+
);
|
|
217
|
+
return story.copy(story.content.replaceChild(at, keyed));
|
|
218
|
+
}
|
|
219
|
+
export {
|
|
220
|
+
asStoryKey,
|
|
221
|
+
buildBlock,
|
|
222
|
+
readStories,
|
|
223
|
+
readStory,
|
|
224
|
+
sameStory,
|
|
225
|
+
setStory,
|
|
226
|
+
storiesByKey,
|
|
227
|
+
storiesOf2 as storiesOf,
|
|
228
|
+
storyFromText,
|
|
229
|
+
storyKey2 as storyKey,
|
|
230
|
+
storyLeafText,
|
|
231
|
+
storyOf,
|
|
232
|
+
storyText,
|
|
233
|
+
withStyleFormats,
|
|
234
|
+
withThreadKeyOn,
|
|
235
|
+
withoutCommentStories,
|
|
236
|
+
withoutStories
|
|
237
|
+
};
|
|
@@ -9,7 +9,12 @@ function comparableStory(story, strip) {
|
|
|
9
9
|
const blocks = [];
|
|
10
10
|
try {
|
|
11
11
|
story.doc.forEach((block) => {
|
|
12
|
-
blocks.push(
|
|
12
|
+
blocks.push(
|
|
13
|
+
serializeBlock(strip(block), {
|
|
14
|
+
...NO_EXPORT_REFS,
|
|
15
|
+
session: story.session
|
|
16
|
+
})
|
|
17
|
+
);
|
|
13
18
|
});
|
|
14
19
|
} catch (error) {
|
|
15
20
|
if (error instanceof DocxExportError) return null;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which parts of a table one cell belongs to, and what its table style dresses those parts with.
|
|
3
|
+
*
|
|
4
|
+
* A table style says one thing for the header row, another for the first column, another for the
|
|
5
|
+
* banded rows (`w:tblStylePr`), and `w:tblLook` says which of those a table takes at all. Every
|
|
6
|
+
* caller that asks what a cell falls back on - the import, a table created fresh, the lines derived
|
|
7
|
+
* again after the grid moved, and a cell formatting edit - asks here, so a cell is dressed the same
|
|
8
|
+
* whichever of them asked.
|
|
9
|
+
*
|
|
10
|
+
* A conditional format writes the lines of the part it dresses rather than of every cell in it:
|
|
11
|
+
* its four sides are drawn where a cell lies on the edge of that part and its inside lines between
|
|
12
|
+
* the cells within it, which is the same rule the table's own lines are drawn by.
|
|
13
|
+
*/
|
|
14
|
+
import type { BandSizes, CellMargins, InsideBorders, TableFormat, TableStyleConditions, TableStyleOverrideType } from "../../model/format";
|
|
15
|
+
import { type CellDefaults, type GridRect, type GridSize } from "./reading";
|
|
16
|
+
/** Which parts of its table style a table takes (`w:tblLook`, §17.4.55) */
|
|
17
|
+
export interface TableLook {
|
|
18
|
+
firstRow: boolean;
|
|
19
|
+
lastRow: boolean;
|
|
20
|
+
firstColumn: boolean;
|
|
21
|
+
lastColumn: boolean;
|
|
22
|
+
noHBand: boolean;
|
|
23
|
+
noVBand: boolean;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* What a table writing no `w:tblLook` at all takes: the banded rows and columns, and neither the
|
|
27
|
+
* header row, the last row, nor the edge columns (§17.4.55). Switching the banding off is
|
|
28
|
+
* something a table has to say, which is why both band settings are worded as `no`.
|
|
29
|
+
*/
|
|
30
|
+
export declare const NO_LOOK: TableLook;
|
|
31
|
+
/**
|
|
32
|
+
* Which parts of its table style this table takes.
|
|
33
|
+
*
|
|
34
|
+
* The six attributes are what a document writes today; where one of them is missing the legacy
|
|
35
|
+
* bitmask answers for it, so a document written either way reads the same.
|
|
36
|
+
*/
|
|
37
|
+
export declare function readTableLook(tblPr: Element | null): TableLook;
|
|
38
|
+
/** One conditional format a cell takes, and the part of the grid it dresses */
|
|
39
|
+
export interface CellCondition {
|
|
40
|
+
type: TableStyleOverrideType;
|
|
41
|
+
/** Its four lines are drawn on the edge of this block, and its inside lines within it */
|
|
42
|
+
region: GridRect;
|
|
43
|
+
}
|
|
44
|
+
/** Where a cell sits, and which parts of the table it therefore belongs to */
|
|
45
|
+
export interface CellPlacement {
|
|
46
|
+
rect: GridRect;
|
|
47
|
+
grid: GridSize;
|
|
48
|
+
/** Lowest first, in the order §17.7.6 lays them over one another */
|
|
49
|
+
conditions: readonly CellCondition[];
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The parts of the table this cell belongs to, lowest first.
|
|
53
|
+
*
|
|
54
|
+
* A cell sits in a row band by the row it starts in and in a column band by the column it starts
|
|
55
|
+
* in, and it is a corner only where the table takes both the row and the column that meet there.
|
|
56
|
+
*/
|
|
57
|
+
export declare function cellPlacement(rect: GridRect, grid: GridSize, look: TableLook, bands: BandSizes): CellPlacement;
|
|
58
|
+
/** What a table lays down for its cells to draw. What its style laid down is not in the `tblPr`, so it comes along separately */
|
|
59
|
+
export interface TableCellSources {
|
|
60
|
+
outer: TableFormat | null;
|
|
61
|
+
inside: InsideBorders;
|
|
62
|
+
margins: CellMargins;
|
|
63
|
+
/** The parts of its style the table takes */
|
|
64
|
+
look: TableLook;
|
|
65
|
+
bands: BandSizes;
|
|
66
|
+
/** What the style dresses each of those parts with */
|
|
67
|
+
conditions: TableStyleConditions;
|
|
68
|
+
/** The style the paragraphs inside the cells resolve against. null for a table wearing none */
|
|
69
|
+
styleId: string | null;
|
|
70
|
+
}
|
|
71
|
+
export declare const NO_CELL_SOURCES: TableCellSources;
|
|
72
|
+
/**
|
|
73
|
+
* What the cell covering this block of the grid falls back on: the table's own lines and margins,
|
|
74
|
+
* with every conditional format its style dresses a part it belongs to with laid over them.
|
|
75
|
+
*/
|
|
76
|
+
export declare function cellDefaultsFor(rect: GridRect, grid: GridSize, sources: TableCellSources): CellDefaults;
|