@portone/docx-editor 0.4.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 +159 -0
- package/assets/editor.png +0 -0
- package/dist/DocxEditor.d.ts +1 -1
- package/dist/DocxEditor.js +44 -25
- package/dist/docx/cloning.js +4 -9
- package/dist/docx/commentOnlyChange.d.ts +5 -3
- package/dist/docx/comments/grammar.d.ts +27 -10
- package/dist/docx/comments/grammar.js +18 -67
- package/dist/docx/comments/model.d.ts +5 -6
- package/dist/docx/comments/model.js +0 -9
- package/dist/docx/comments/parts.d.ts +16 -10
- package/dist/docx/comments/parts.js +65 -13
- package/dist/docx/comments/people.d.ts +11 -3
- package/dist/docx/comments/people.js +20 -6
- package/dist/docx/comments/policy.js +7 -1
- package/dist/docx/comments/reading.d.ts +4 -2
- package/dist/docx/comments/reading.js +1 -14
- package/dist/docx/comments/writing.js +65 -28
- package/dist/docx/exportDocx.js +10 -14
- package/dist/docx/exportRefs.d.ts +8 -3
- package/dist/docx/exportRefs.js +2 -1
- package/dist/docx/fidelity.js +24 -10
- package/dist/docx/fields.d.ts +42 -0
- package/dist/docx/fields.js +76 -0
- package/dist/docx/headersFooters.d.ts +89 -20
- package/dist/docx/headersFooters.js +163 -172
- package/dist/docx/importDocx.d.ts +1 -1
- package/dist/docx/importDocx.js +100 -48
- 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.js +56 -12
- package/dist/docx/invariants.js +16 -18
- package/dist/docx/notes.d.ts +6 -1
- package/dist/docx/notes.js +6 -16
- package/dist/docx/pageGeometry.d.ts +5 -14
- package/dist/docx/pageGeometry.js +0 -12
- package/dist/docx/protectionPolicy.d.ts +13 -5
- package/dist/docx/protectionPolicy.js +32 -22
- package/dist/docx/scan.d.ts +18 -8
- package/dist/docx/scan.js +17 -11
- package/dist/docx/sections.d.ts +126 -0
- package/dist/docx/sections.js +207 -0
- package/dist/docx/serializeBlock.d.ts +13 -4
- package/dist/docx/serializeBlock.js +16 -18
- package/dist/docx/serializeParagraph.d.ts +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 +23 -6
- package/dist/docx/session.js +26 -5
- package/dist/docx/story.d.ts +140 -0
- package/dist/docx/story.js +237 -0
- package/dist/docx/storyProjection.js +6 -1
- package/dist/editor/clipboard/htmlReader.d.ts +30 -0
- package/dist/editor/{externalClipboard.js → clipboard/htmlReader.js} +39 -223
- 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/index.d.ts +2 -2
- package/dist/editor/commands/index.js +2 -0
- package/dist/editor/commands/listCommands.d.ts +6 -0
- package/dist/editor/commands/listCommands.js +3 -2
- package/dist/editor/commands/noteQueries.d.ts +22 -5
- package/dist/editor/commands/noteQueries.js +34 -6
- package/dist/editor/createEditor.js +13 -3
- package/dist/editor/documentStyles.d.ts +26 -4
- package/dist/editor/documentStyles.js +9 -4
- package/dist/editor/imageFiles.d.ts +2 -2
- package/dist/editor/imageFiles.js +2 -0
- package/dist/editor/insertTable.d.ts +1 -1
- package/dist/editor/insertTable.js +2 -2
- package/dist/editor/plainText.d.ts +12 -0
- package/dist/editor/plainText.js +2 -1
- package/dist/editor/plugins/columnResize.js +6 -3
- package/dist/editor/plugins/commentComposer.d.ts +28 -0
- package/dist/editor/plugins/commentComposer.js +46 -0
- package/dist/editor/plugins/commentDecorations.d.ts +29 -2
- package/dist/editor/plugins/commentDecorations.js +89 -15
- package/dist/editor/plugins/documentProjection.d.ts +31 -0
- package/dist/editor/plugins/documentProjection.js +22 -0
- package/dist/editor/plugins/imagePaste.js +15 -8
- package/dist/editor/plugins/linkPanel.d.ts +7 -3
- package/dist/editor/plugins/linkPanel.js +13 -27
- package/dist/editor/plugins/panelState.d.ts +67 -0
- package/dist/editor/plugins/panelState.js +54 -0
- package/dist/editor/plugins/tableContextMenu.d.ts +1 -1
- package/dist/editor/plugins/tableContextMenu.js +30 -47
- package/dist/editor/plugins/textContextMenu.d.ts +1 -1
- package/dist/editor/plugins/textContextMenu.js +30 -47
- package/dist/ooxml/fragment.d.ts +11 -0
- package/dist/ooxml/fragment.js +14 -4
- package/dist/ooxml/image.d.ts +3 -4
- package/dist/ooxml/image.js +50 -5
- package/dist/ooxml/names.d.ts +6 -0
- package/dist/ooxml/names.js +2 -0
- package/dist/ooxml/props.d.ts +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/page/PageGuides.d.ts +7 -3
- package/dist/page/PageGuides.js +13 -24
- package/dist/page/pageLayout.d.ts +64 -5
- package/dist/page/pageLayout.js +90 -29
- package/dist/page/usePageLayout.d.ts +14 -4
- package/dist/page/usePageLayout.js +19 -20
- package/dist/schema/attrRoles.js +38 -20
- package/dist/schema/docxSchema.d.ts +9 -1
- package/dist/schema/docxSchema.js +191 -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 +3 -5
- package/dist/styles/classNames.js +3 -5
- package/dist/styles.css +74 -12
- package/dist/table/resize.d.ts +5 -3
- package/dist/ui/CommentsPanel.d.ts +1 -3
- package/dist/ui/CommentsPanel.js +24 -15
- package/dist/ui/NotesPanel.js +2 -2
- package/dist/ui/TextMenu.d.ts +1 -3
- package/dist/ui/TextMenu.js +10 -6
- package/dist/ui/comments/CommentComposer.d.ts +6 -1
- package/dist/ui/comments/CommentComposer.js +26 -3
- package/package.json +1 -1
- package/dist/editor/externalClipboard.d.ts +0 -22
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Slice } from "prosemirror-model";
|
|
2
|
+
import type { Transaction } from "prosemirror-state";
|
|
3
|
+
import { type NewLists } from "../../numbering/listRegistry";
|
|
4
|
+
import type { ListKinds } from "./internalChannel";
|
|
5
|
+
import type { HtmlReadContext } from "./readContext";
|
|
6
|
+
/**
|
|
7
|
+
* The paragraph style a copy carries, which is the one thing a paste needs that the drawing does
|
|
8
|
+
* not already say. It is the style's id alone, where the editor draws the whole `w:pPr`.
|
|
9
|
+
*/
|
|
10
|
+
export declare const COPIED_STYLE_ATTRIBUTE = "data-style";
|
|
11
|
+
/** What a paste puts in: the content, and the definitions of the lists it started */
|
|
12
|
+
export interface PastedContent {
|
|
13
|
+
slice: Slice;
|
|
14
|
+
newLists: NewLists;
|
|
15
|
+
/**
|
|
16
|
+
* What the numbers this slice's paragraphs name meant where it was copied
|
|
17
|
+
* (`./internalChannel`). Absent for a reading that gave the lists it read numbers of its own,
|
|
18
|
+
* which knows what kind each is from the markup it read them out of.
|
|
19
|
+
*/
|
|
20
|
+
listKinds?: ListKinds;
|
|
21
|
+
}
|
|
22
|
+
/** The content one piece of already parsed markup reads as, or null when it reads as nothing */
|
|
23
|
+
export declare function readHtml(root: ParentNode, context: HtmlReadContext): PastedContent | null;
|
|
24
|
+
/** The same reading, for a caller holding the markup as a string rather than as elements */
|
|
25
|
+
export declare function readHtmlSlice(context: HtmlReadContext, source: string): PastedContent | null;
|
|
26
|
+
/**
|
|
27
|
+
* The transaction a paste is put in with: the content, and the definitions of the lists it started
|
|
28
|
+
* recorded on the document node so that the export writes them out and undo takes them back.
|
|
29
|
+
*/
|
|
30
|
+
export declare function withPastedContent(tr: Transaction, content: PastedContent): Transaction;
|
|
@@ -1,43 +1,33 @@
|
|
|
1
|
-
// src/editor/
|
|
2
|
-
import {
|
|
3
|
-
DOMSerializer,
|
|
4
|
-
Fragment,
|
|
5
|
-
Slice
|
|
6
|
-
} from "prosemirror-model";
|
|
7
|
-
import { Plugin } from "prosemirror-state";
|
|
1
|
+
// src/editor/clipboard/htmlReader.ts
|
|
2
|
+
import { Fragment, Slice } from "prosemirror-model";
|
|
8
3
|
import {
|
|
9
4
|
paragraphAttrsFor,
|
|
10
5
|
styleIdOf
|
|
11
|
-
} from "
|
|
6
|
+
} from "../../docx/formatting.js";
|
|
12
7
|
import {
|
|
13
8
|
withListNumbering,
|
|
14
9
|
withParagraphStyle
|
|
15
|
-
} from "
|
|
10
|
+
} from "../../docx/paraProps.js";
|
|
16
11
|
import {
|
|
17
12
|
listsWorn,
|
|
18
13
|
NEW_LISTS_ATTR,
|
|
19
14
|
newListsValue
|
|
20
|
-
} from "
|
|
15
|
+
} from "../../numbering/listRegistry.js";
|
|
21
16
|
import {
|
|
22
17
|
allocateList,
|
|
23
18
|
MAX_ILVL,
|
|
24
19
|
templateList
|
|
25
|
-
} from "
|
|
26
|
-
import { docxSchema
|
|
27
|
-
import { editorClassNames } from "
|
|
28
|
-
import {
|
|
20
|
+
} from "../../numbering/listTemplate.js";
|
|
21
|
+
import { docxSchema } from "../../schema/index.js";
|
|
22
|
+
import { editorClassNames } from "../../styles/classNames.js";
|
|
23
|
+
import { numIdsIn } from "../commands/listCommands.js";
|
|
24
|
+
import { PASTED_IMAGE_ATTRIBUTE } from "./images.js";
|
|
29
25
|
import {
|
|
30
26
|
appendInline,
|
|
31
27
|
contextFor,
|
|
32
28
|
marksFor,
|
|
33
|
-
safeHref,
|
|
34
29
|
withInlineStyle
|
|
35
|
-
} from "./
|
|
36
|
-
import { numIdsIn } from "./commands/listCommands.js";
|
|
37
|
-
import { documentFormatting, documentParagraphStyles } from "./documentStyles.js";
|
|
38
|
-
import { insertPlainText } from "./plainText.js";
|
|
39
|
-
import { moveCaretToDrop } from "./plugins/dropCaret.js";
|
|
40
|
-
import { canStartNewList } from "./plugins/numberingDecorations.js";
|
|
30
|
+
} from "./inlineFormatting.js";
|
|
41
31
|
var BLOCK_TAGS = /* @__PURE__ */ new Set([
|
|
42
32
|
"ADDRESS",
|
|
43
33
|
"ARTICLE",
|
|
@@ -71,143 +61,7 @@ var HEADING_FALLBACKS = {
|
|
|
71
61
|
5: { bold: true, fontSizePt: 10 },
|
|
72
62
|
6: { bold: true, fontSizePt: 8 }
|
|
73
63
|
};
|
|
74
|
-
var PUBLIC_ATTRIBUTES = /* @__PURE__ */ new Set([
|
|
75
|
-
"alt",
|
|
76
|
-
"aria-label",
|
|
77
|
-
"class",
|
|
78
|
-
"colspan",
|
|
79
|
-
"height",
|
|
80
|
-
"href",
|
|
81
|
-
"lang",
|
|
82
|
-
"rowspan",
|
|
83
|
-
"src",
|
|
84
|
-
"style",
|
|
85
|
-
"width"
|
|
86
|
-
]);
|
|
87
64
|
var COPIED_STYLE_ATTRIBUTE = "data-style";
|
|
88
|
-
function isAttributes(value) {
|
|
89
|
-
return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Node);
|
|
90
|
-
}
|
|
91
|
-
function publicAttributes(attrs) {
|
|
92
|
-
return Object.fromEntries(
|
|
93
|
-
Object.entries(attrs).filter(([name]) => PUBLIC_ATTRIBUTES.has(name))
|
|
94
|
-
);
|
|
95
|
-
}
|
|
96
|
-
function isSpecArray(value) {
|
|
97
|
-
return Array.isArray(value) && typeof value[0] === "string";
|
|
98
|
-
}
|
|
99
|
-
function stripPrivateAttributes(spec) {
|
|
100
|
-
if (!isSpecArray(spec)) return spec;
|
|
101
|
-
const [tag, ...rest] = spec;
|
|
102
|
-
const attrs = isAttributes(rest[0]) ? publicAttributes(rest[0]) : null;
|
|
103
|
-
const children = (attrs === null ? rest : rest.slice(1)).map(
|
|
104
|
-
(child) => isSpecArray(child) ? stripPrivateAttributes(child) : child
|
|
105
|
-
);
|
|
106
|
-
return attrs === null ? [tag, ...children] : [tag, attrs, ...children];
|
|
107
|
-
}
|
|
108
|
-
function withDomAttribute(spec, name, value) {
|
|
109
|
-
if (!isSpecArray(spec)) return spec;
|
|
110
|
-
const [tag, ...rest] = spec;
|
|
111
|
-
return isAttributes(rest[0]) ? [tag, { ...rest[0], [name]: value }, ...rest.slice(1)] : [tag, { [name]: value }, ...rest];
|
|
112
|
-
}
|
|
113
|
-
function asTag(spec, tag) {
|
|
114
|
-
return isSpecArray(spec) ? [tag, ...spec.slice(1)] : spec;
|
|
115
|
-
}
|
|
116
|
-
function copiedMarkSpec(mark, spec) {
|
|
117
|
-
const stripped = stripPrivateAttributes(spec);
|
|
118
|
-
if (mark.type !== docxSchema.marks.link) return stripped;
|
|
119
|
-
const href = safeHref(
|
|
120
|
-
typeof mark.attrs.href === "string" ? mark.attrs.href : null
|
|
121
|
-
);
|
|
122
|
-
return href === null ? stripped : asTag(withDomAttribute(stripped, "href", href), "a");
|
|
123
|
-
}
|
|
124
|
-
function copiedNodeSpec(node, spec) {
|
|
125
|
-
const stripped = stripPrivateAttributes(spec);
|
|
126
|
-
if (node.type !== docxSchema.nodes.paragraph) return stripped;
|
|
127
|
-
const styleId = styleIdOf(node.attrs.pPr);
|
|
128
|
-
return styleId === null ? stripped : withDomAttribute(stripped, COPIED_STYLE_ATTRIBUTE, styleId);
|
|
129
|
-
}
|
|
130
|
-
function clipboardSerializer() {
|
|
131
|
-
const drawn = DOMSerializer.fromSchema(docxSchema);
|
|
132
|
-
const nodes = Object.fromEntries(
|
|
133
|
-
Object.entries(drawn.nodes).map(([name, toDOM]) => [
|
|
134
|
-
name,
|
|
135
|
-
(node) => copiedNodeSpec(node, toDOM(node))
|
|
136
|
-
])
|
|
137
|
-
);
|
|
138
|
-
const marks = Object.fromEntries(
|
|
139
|
-
Object.entries(drawn.marks).map(([name, toDOM]) => [
|
|
140
|
-
name,
|
|
141
|
-
(mark, inline) => copiedMarkSpec(mark, toDOM(mark, inline))
|
|
142
|
-
])
|
|
143
|
-
);
|
|
144
|
-
return new DOMSerializer(nodes, marks);
|
|
145
|
-
}
|
|
146
|
-
var UNREADABLE_BLOCKS = /* @__PURE__ */ new Set([
|
|
147
|
-
"bookmarkBlock",
|
|
148
|
-
"docxRaw",
|
|
149
|
-
"rawBlock"
|
|
150
|
-
]);
|
|
151
|
-
function stringAttr(value) {
|
|
152
|
-
return typeof value === "string" ? value : "";
|
|
153
|
-
}
|
|
154
|
-
function inlineText(node) {
|
|
155
|
-
if (node.isText) return node.text ?? "";
|
|
156
|
-
if (node.type === docxSchema.nodes.hardBreak) {
|
|
157
|
-
return isPageBreak(node.attrs.brAttrs) ? "\f" : "\n";
|
|
158
|
-
}
|
|
159
|
-
if (node.type === docxSchema.nodes.image) return stringAttr(node.attrs.alt);
|
|
160
|
-
if (node.type === docxSchema.nodes.noteReference) {
|
|
161
|
-
return node.attrs.customMarkFollows === true ? "" : stringAttr(node.attrs.label);
|
|
162
|
-
}
|
|
163
|
-
return "";
|
|
164
|
-
}
|
|
165
|
-
function rowsText(table) {
|
|
166
|
-
const rows = [];
|
|
167
|
-
table.forEach((row) => {
|
|
168
|
-
const cells = [];
|
|
169
|
-
row.forEach((cell) => {
|
|
170
|
-
cells.push(fragmentText(cell.content));
|
|
171
|
-
});
|
|
172
|
-
rows.push(cells.join(" "));
|
|
173
|
-
});
|
|
174
|
-
return rows.join("\n");
|
|
175
|
-
}
|
|
176
|
-
function blockText(node) {
|
|
177
|
-
if (node.type === docxSchema.nodes.table) return rowsText(node);
|
|
178
|
-
if (UNREADABLE_BLOCKS.has(node.type.name)) return "";
|
|
179
|
-
return fragmentText(node.content);
|
|
180
|
-
}
|
|
181
|
-
function fragmentText(fragment) {
|
|
182
|
-
const pieces = [];
|
|
183
|
-
fragment.forEach((child) => {
|
|
184
|
-
pieces.push(child.isInline ? inlineText(child) : blockText(child));
|
|
185
|
-
});
|
|
186
|
-
return pieces.join(fragment.firstChild?.isInline === true ? "" : "\n");
|
|
187
|
-
}
|
|
188
|
-
function bareWrappers(content, openStart, openEnd) {
|
|
189
|
-
const wrapper = content.firstChild;
|
|
190
|
-
if (openStart <= 1 || openEnd <= 1 || content.childCount !== 1)
|
|
191
|
-
return content;
|
|
192
|
-
if (wrapper === null || wrapper.childCount !== 1) return content;
|
|
193
|
-
return Fragment.from(
|
|
194
|
-
wrapper.type.create(
|
|
195
|
-
null,
|
|
196
|
-
bareWrappers(wrapper.content, openStart - 1, openEnd - 1),
|
|
197
|
-
wrapper.marks
|
|
198
|
-
)
|
|
199
|
-
);
|
|
200
|
-
}
|
|
201
|
-
function copiedSlice(slice) {
|
|
202
|
-
return new Slice(
|
|
203
|
-
bareWrappers(slice.content, slice.openStart, slice.openEnd),
|
|
204
|
-
slice.openStart,
|
|
205
|
-
slice.openEnd
|
|
206
|
-
);
|
|
207
|
-
}
|
|
208
|
-
function clipboardText(slice) {
|
|
209
|
-
return fragmentText(slice.content);
|
|
210
|
-
}
|
|
211
65
|
function normalizedStyleName(value) {
|
|
212
66
|
return value.toLowerCase().replace(/[\s_-]+/g, "");
|
|
213
67
|
}
|
|
@@ -223,8 +77,7 @@ function headingLevel(element, sourceStyleId) {
|
|
|
223
77
|
);
|
|
224
78
|
return match ? Number.parseInt(match[1] ?? "", 10) : null;
|
|
225
79
|
}
|
|
226
|
-
function destinationStyleId(
|
|
227
|
-
const styles = documentParagraphStyles(state);
|
|
80
|
+
function destinationStyleId(styles, sourceStyleId, level) {
|
|
228
81
|
if (sourceStyleId && styles.some((style) => style.id === sourceStyleId)) {
|
|
229
82
|
return sourceStyleId;
|
|
230
83
|
}
|
|
@@ -234,7 +87,7 @@ function destinationStyleId(state, sourceStyleId, level) {
|
|
|
234
87
|
(style) => normalizedStyleName(style.id) === wanted || normalizedStyleName(style.name) === wanted
|
|
235
88
|
)?.id ?? null;
|
|
236
89
|
}
|
|
237
|
-
function blockContext(
|
|
90
|
+
function blockContext(context, parent, element, preserveParagraphStyle) {
|
|
238
91
|
const editorParagraph = element.classList.contains(
|
|
239
92
|
editorClassNames.paragraph
|
|
240
93
|
);
|
|
@@ -246,7 +99,11 @@ function blockContext(state, parent, element, preserveParagraphStyle) {
|
|
|
246
99
|
paragraph: null
|
|
247
100
|
};
|
|
248
101
|
}
|
|
249
|
-
const styleId = destinationStyleId(
|
|
102
|
+
const styleId = destinationStyleId(
|
|
103
|
+
context.paragraphStyles,
|
|
104
|
+
sourceStyleId,
|
|
105
|
+
level
|
|
106
|
+
);
|
|
250
107
|
const paragraph = styleId === null ? null : withParagraphStyle(null, styleId);
|
|
251
108
|
const usesDestinationStyle = editorParagraph && (sourceStyleId === null || paragraph !== null);
|
|
252
109
|
let inline = parent;
|
|
@@ -270,17 +127,15 @@ function paragraphAttrs(formatting, list, paragraph) {
|
|
|
270
127
|
return props ? { pPr: props.pPr, ...paragraphAttrsFor(props.pPr, formatting) } : null;
|
|
271
128
|
}
|
|
272
129
|
var HtmlReader = class {
|
|
273
|
-
constructor(
|
|
274
|
-
this.
|
|
130
|
+
constructor(context, preserveParagraphStyles) {
|
|
131
|
+
this.context = context;
|
|
275
132
|
this.preserveParagraphStyles = preserveParagraphStyles;
|
|
276
|
-
this.
|
|
277
|
-
this.
|
|
278
|
-
this.
|
|
279
|
-
this.canCreateLists = canStartNewList(state);
|
|
133
|
+
this.formatting = context.formatting;
|
|
134
|
+
this.used = new Set(context.numbering.used);
|
|
135
|
+
this.canCreateLists = context.numbering.canCreate;
|
|
280
136
|
}
|
|
281
|
-
|
|
137
|
+
context;
|
|
282
138
|
preserveParagraphStyles;
|
|
283
|
-
images;
|
|
284
139
|
blocks = [];
|
|
285
140
|
used;
|
|
286
141
|
canCreateLists;
|
|
@@ -302,7 +157,7 @@ var HtmlReader = class {
|
|
|
302
157
|
appendInline(target, node, context, (content, element, inline) => {
|
|
303
158
|
if (element.tagName !== "IMG") return false;
|
|
304
159
|
const token = element.getAttribute(PASTED_IMAGE_ATTRIBUTE);
|
|
305
|
-
const image = token === null ? void 0 : this.images.get(token);
|
|
160
|
+
const image = token === null ? void 0 : this.context.images.get(token);
|
|
306
161
|
if (!image) return true;
|
|
307
162
|
content.push(
|
|
308
163
|
docxSchema.nodes.image.create(
|
|
@@ -346,7 +201,7 @@ var HtmlReader = class {
|
|
|
346
201
|
flush();
|
|
347
202
|
const before = this.blocks.length;
|
|
348
203
|
const block = blockContext(
|
|
349
|
-
this.
|
|
204
|
+
this.context,
|
|
350
205
|
context,
|
|
351
206
|
element,
|
|
352
207
|
this.preserveParagraphStyles
|
|
@@ -418,69 +273,30 @@ function sliceDepth(root) {
|
|
|
418
273
|
const blocks = [...BLOCK_TAGS, "OL", "UL"].map((tag) => tag.toLowerCase());
|
|
419
274
|
return root.querySelector(blocks.join(",")) ? 0 : 1;
|
|
420
275
|
}
|
|
421
|
-
function
|
|
422
|
-
|
|
423
|
-
const
|
|
424
|
-
|
|
425
|
-
const open = sliceDepth(template.content);
|
|
426
|
-
const reader = new HtmlReader(state, open === 0, images);
|
|
427
|
-
const blocks = reader.read(template.content);
|
|
276
|
+
function readHtml(root, context) {
|
|
277
|
+
const open = sliceDepth(root);
|
|
278
|
+
const reader = new HtmlReader(context, open === 0);
|
|
279
|
+
const blocks = reader.read(root);
|
|
428
280
|
return blocks.length === 0 ? null : {
|
|
429
281
|
slice: new Slice(Fragment.fromArray([...blocks]), open, open),
|
|
430
282
|
newLists: reader.newLists
|
|
431
283
|
};
|
|
432
284
|
}
|
|
285
|
+
function readHtmlSlice(context, source) {
|
|
286
|
+
if (source.trim() === "") return null;
|
|
287
|
+
const template = context.document.createElement("template");
|
|
288
|
+
template.innerHTML = source;
|
|
289
|
+
return readHtml(template.content, context);
|
|
290
|
+
}
|
|
433
291
|
function withPastedContent(tr, content) {
|
|
434
292
|
return tr.setDocAttribute(
|
|
435
293
|
NEW_LISTS_ATTR,
|
|
436
294
|
newListsValue(listsWorn(content.newLists, numIdsIn(tr.doc)))
|
|
437
295
|
);
|
|
438
296
|
}
|
|
439
|
-
function insertRichHtml(view, source) {
|
|
440
|
-
const content = richHtmlSlice(view.state, view.dom.ownerDocument, source);
|
|
441
|
-
if (content === null) return false;
|
|
442
|
-
view.dispatch(
|
|
443
|
-
withPastedContent(
|
|
444
|
-
view.state.tr.replaceSelection(content.slice),
|
|
445
|
-
content
|
|
446
|
-
).scrollIntoView()
|
|
447
|
-
);
|
|
448
|
-
return true;
|
|
449
|
-
}
|
|
450
|
-
function insertClipboardData(view, data) {
|
|
451
|
-
if (insertRichHtml(view, data.html ?? "")) return;
|
|
452
|
-
insertPlainText(view, data.text ?? "");
|
|
453
|
-
}
|
|
454
|
-
function externalClipboard() {
|
|
455
|
-
const serializer = clipboardSerializer();
|
|
456
|
-
return new Plugin({
|
|
457
|
-
props: {
|
|
458
|
-
clipboardSerializer: serializer,
|
|
459
|
-
clipboardTextSerializer: clipboardText,
|
|
460
|
-
transformCopied: copiedSlice,
|
|
461
|
-
handlePaste(view, event) {
|
|
462
|
-
insertClipboardData(view, {
|
|
463
|
-
html: event.clipboardData?.getData("text/html"),
|
|
464
|
-
text: event.clipboardData?.getData("text/plain")
|
|
465
|
-
});
|
|
466
|
-
return true;
|
|
467
|
-
},
|
|
468
|
-
handleDrop(view, event) {
|
|
469
|
-
if (view.dragging) return false;
|
|
470
|
-
const text = event.dataTransfer?.getData("text/plain") ?? "";
|
|
471
|
-
if (text) {
|
|
472
|
-
moveCaretToDrop(view, event);
|
|
473
|
-
insertPlainText(view, text);
|
|
474
|
-
}
|
|
475
|
-
return true;
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
});
|
|
479
|
-
}
|
|
480
297
|
export {
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
richHtmlSlice,
|
|
298
|
+
COPIED_STYLE_ATTRIBUTE,
|
|
299
|
+
readHtml,
|
|
300
|
+
readHtmlSlice,
|
|
485
301
|
withPastedContent
|
|
486
302
|
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The channel a copy made in this editor comes back through.
|
|
3
|
+
*
|
|
4
|
+
* What leaves for the clipboard is HTML, and HTML carries only what a reader can make out of it:
|
|
5
|
+
* a paragraph's own properties, a table's grid, the relationship a link hung off are none of them
|
|
6
|
+
* writable there without publishing the document's insides. So the slice itself stays here, under
|
|
7
|
+
* a name the copied HTML carries, and a paste that hands that name back is given what was copied.
|
|
8
|
+
*
|
|
9
|
+
* The name is trusted only alongside the session it was copied from: another open document -
|
|
10
|
+
* a second editor on the same page among them - numbers its lists and relates its images against
|
|
11
|
+
* its own file, so a slice of it means something else here, and such a paste falls to the reader
|
|
12
|
+
* that reads the markup instead. A token nothing kept, a token from a copy since replaced, and a
|
|
13
|
+
* token invented by whoever wrote the markup all miss the same way.
|
|
14
|
+
*
|
|
15
|
+
* The slice is kept alongside the kind of each list its paragraphs name, which is the one thing
|
|
16
|
+
* about the copy that only the editor it was copied from can answer: a number says nothing about
|
|
17
|
+
* whether it counted or bulleted, and the definition that said so is the source document's. It is
|
|
18
|
+
* read here because this is where both ends are the same session.
|
|
19
|
+
*/
|
|
20
|
+
import type { Slice } from "prosemirror-model";
|
|
21
|
+
import type { ListKind } from "../../numbering/listTemplate";
|
|
22
|
+
import type { Numbering } from "../../numbering/parseNumbering";
|
|
23
|
+
/** Where the copied HTML carries the name of the slice this editor kept */
|
|
24
|
+
export declare const INTERNAL_TOKEN_ATTRIBUTE = "data-docx-clip";
|
|
25
|
+
/** The kind of list each numbering id of a slice stood for where the slice was copied */
|
|
26
|
+
export type ListKinds = ReadonlyMap<number, ListKind>;
|
|
27
|
+
export declare const NO_LIST_KINDS: ListKinds;
|
|
28
|
+
/**
|
|
29
|
+
* The two ways a copy leaves the editor.
|
|
30
|
+
*
|
|
31
|
+
* A drag is serialized through the very same path a copy takes, so the one the drag wrote would
|
|
32
|
+
* replace the one the clipboard still holds, and a drag begun and let go would send the next paste
|
|
33
|
+
* of that clipboard to the reader that reads markup. They are kept apart instead, and each is
|
|
34
|
+
* replaced only by the next copy made the same way.
|
|
35
|
+
*/
|
|
36
|
+
export type CopyRoute = "clipboard" | "drag";
|
|
37
|
+
/** What one name stands for: the slice that was copied, and what its list numbers meant there */
|
|
38
|
+
export interface RecalledCopy {
|
|
39
|
+
readonly slice: Slice;
|
|
40
|
+
readonly listKinds: ListKinds;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Keeps the copied slice under a fresh name, and answers with the name to write into the markup.
|
|
44
|
+
* Null for a state built without a document: with no session to bind the name to there is nothing
|
|
45
|
+
* to tell this editor's copy from another's, and the copy travels as markup alone.
|
|
46
|
+
*/
|
|
47
|
+
export declare function rememberCopied(route: CopyRoute, slice: Slice, sessionId: string | null, numbering: Numbering): string | null;
|
|
48
|
+
/** The copy that name stands for, and only when the session asking is the one that copied it */
|
|
49
|
+
export declare function recallCopied(token: string | null, sessionId: string | null): RecalledCopy | null;
|
|
50
|
+
/**
|
|
51
|
+
* The name the pasted markup carries, if it carries one.
|
|
52
|
+
*
|
|
53
|
+
* The copy writes it on the first element it draws. `prosemirror-view` may wrap that element in
|
|
54
|
+
* the parents its tag needs (a row in a table) and descends back through them before parsing, so
|
|
55
|
+
* the element handed here is either the one carrying the name or its parent.
|
|
56
|
+
*/
|
|
57
|
+
export declare function internalTokenOf(dom: Node): string | null;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// src/editor/clipboard/internalChannel.ts
|
|
2
|
+
import { docxSchema } from "../../schema/index.js";
|
|
3
|
+
import { listKindOf, listRefOf } from "../commands/listCommands.js";
|
|
4
|
+
var INTERNAL_TOKEN_ATTRIBUTE = "data-docx-clip";
|
|
5
|
+
var NO_LIST_KINDS = /* @__PURE__ */ new Map();
|
|
6
|
+
var copied = /* @__PURE__ */ new Map();
|
|
7
|
+
var copies = 0;
|
|
8
|
+
function nextToken() {
|
|
9
|
+
copies += 1;
|
|
10
|
+
return `c${copies}-${Math.random().toString(36).slice(2, 10)}`;
|
|
11
|
+
}
|
|
12
|
+
function rememberCopied(route, slice, sessionId, numbering) {
|
|
13
|
+
if (sessionId === null) return null;
|
|
14
|
+
const token = nextToken();
|
|
15
|
+
copied.set(route, {
|
|
16
|
+
token,
|
|
17
|
+
sessionId,
|
|
18
|
+
slice,
|
|
19
|
+
listKinds: listKindsIn(slice, numbering)
|
|
20
|
+
});
|
|
21
|
+
return token;
|
|
22
|
+
}
|
|
23
|
+
function recallCopied(token, sessionId) {
|
|
24
|
+
if (token === null || sessionId === null) return null;
|
|
25
|
+
const found = [...copied.values()].find(
|
|
26
|
+
(copy) => copy.token === token && copy.sessionId === sessionId
|
|
27
|
+
);
|
|
28
|
+
return found === void 0 ? null : { slice: found.slice, listKinds: found.listKinds };
|
|
29
|
+
}
|
|
30
|
+
function listKindsIn(slice, numbering) {
|
|
31
|
+
const kinds = /* @__PURE__ */ new Map();
|
|
32
|
+
const read = (node) => {
|
|
33
|
+
if (node.type === docxSchema.nodes.paragraph) {
|
|
34
|
+
const ref = listRefOf(node);
|
|
35
|
+
const kind = ref === null ? null : listKindOf(numbering, ref);
|
|
36
|
+
if (ref !== null && kind !== null) kinds.set(ref.numId, kind);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
node.forEach(read);
|
|
40
|
+
};
|
|
41
|
+
slice.content.forEach(read);
|
|
42
|
+
return kinds.size === 0 ? NO_LIST_KINDS : kinds;
|
|
43
|
+
}
|
|
44
|
+
function internalTokenOf(dom) {
|
|
45
|
+
if (dom instanceof Element) {
|
|
46
|
+
const own = dom.getAttribute(INTERNAL_TOKEN_ATTRIBUTE);
|
|
47
|
+
if (own !== null) return own;
|
|
48
|
+
}
|
|
49
|
+
const first = dom instanceof Element || dom instanceof DocumentFragment ? dom.firstElementChild : null;
|
|
50
|
+
return first?.getAttribute(INTERNAL_TOKEN_ATTRIBUTE) ?? null;
|
|
51
|
+
}
|
|
52
|
+
export {
|
|
53
|
+
INTERNAL_TOKEN_ATTRIBUTE,
|
|
54
|
+
NO_LIST_KINDS,
|
|
55
|
+
internalTokenOf,
|
|
56
|
+
recallCopied,
|
|
57
|
+
rememberCopied
|
|
58
|
+
};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a slice has to obey before it may be put into this document.
|
|
3
|
+
*
|
|
4
|
+
* A slice reaching a paste says where its blocks came from, which list its paragraphs belong to,
|
|
5
|
+
* which comment its markers open, and what everything is drawn with. None of those mean anything
|
|
6
|
+
* outside the document that wrote them: a block naming a fragment of the file would be written
|
|
7
|
+
* from that fragment a second time, a list number naming no definition here would be exported
|
|
8
|
+
* bare, and a comment marker copied along would open a range the comment part never opens. So a
|
|
9
|
+
* paste is a normalization rather than an insertion, and each rule stands as one normalizer over
|
|
10
|
+
* the whole slice, in the order they are declared.
|
|
11
|
+
*
|
|
12
|
+
* A move drop is the exception the first two rules read: content dragged from one place in this
|
|
13
|
+
* very document to another is the same content, so it keeps the identity it had and the anchors it
|
|
14
|
+
* stood in. The source of a move is deleted along with the drop, so nothing is duplicated.
|
|
15
|
+
*/
|
|
16
|
+
import { type Node as PMNode, Slice } from "prosemirror-model";
|
|
17
|
+
import type { EditorState } from "prosemirror-state";
|
|
18
|
+
import { type ListKind } from "../../numbering/listTemplate";
|
|
19
|
+
import type { PastedContent } from "./htmlReader";
|
|
20
|
+
import { type ListKinds } from "./internalChannel";
|
|
21
|
+
/** What one normalizer answers about the document the slice is going into */
|
|
22
|
+
export interface NormalizeContext {
|
|
23
|
+
readonly state: EditorState;
|
|
24
|
+
/** A drop that moves what it carries rather than copying it, which keeps its source identity */
|
|
25
|
+
readonly move: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* The list numbers this document answers for: the ones it defines, the ones a list started
|
|
28
|
+
* while editing was registered under, the ones its paragraphs wear, and the ones this very
|
|
29
|
+
* paste has started.
|
|
30
|
+
*/
|
|
31
|
+
readonly knownLists: ReadonlySet<number>;
|
|
32
|
+
/**
|
|
33
|
+
* What the numbers the slice names meant where it was copied, for the numbers this document
|
|
34
|
+
* cannot answer for. Empty for a slice that came in as markup, whose lists were given numbers
|
|
35
|
+
* of this document as they were read.
|
|
36
|
+
*/
|
|
37
|
+
readonly listKinds: ListKinds;
|
|
38
|
+
/**
|
|
39
|
+
* A number for a list this slice starts, registered with the definition it is started with, as
|
|
40
|
+
* the list button starts one. Null in a document with nowhere to write a definition.
|
|
41
|
+
*/
|
|
42
|
+
startList(kind: ListKind): number | null;
|
|
43
|
+
}
|
|
44
|
+
export type SliceNormalizer = (slice: Slice, context: NormalizeContext) => Slice;
|
|
45
|
+
/** The slice with every node rewritten depth first, and the ones mapped to null taken out */
|
|
46
|
+
export declare function mapSliceNodes(slice: Slice, f: (node: PMNode) => PMNode | null): Slice;
|
|
47
|
+
/**
|
|
48
|
+
* Takes the source identity off what is pasted.
|
|
49
|
+
*
|
|
50
|
+
* A block names the fragment of the opened file it came from, and the export writes that fragment
|
|
51
|
+
* back verbatim rather than building one. Two blocks naming the same fragment would therefore go
|
|
52
|
+
* out as the same bytes twice, identifiers and all, so a pasted copy names none and is written
|
|
53
|
+
* from the model instead. A preserved block holding its own XML is that XML wherever it stands and
|
|
54
|
+
* travels whole; one that only names a fragment has nothing of its own to travel with, so it goes.
|
|
55
|
+
*/
|
|
56
|
+
export declare const dropSourceIdentity: SliceNormalizer;
|
|
57
|
+
/**
|
|
58
|
+
* Gives a pasted list a number this document defines.
|
|
59
|
+
*
|
|
60
|
+
* A number the destination knows nothing about is exported as a list nothing defines, which the
|
|
61
|
+
* export invariants refuse. The paragraphs that shared such a number still share the one they are
|
|
62
|
+
* given, so a pasted list stays one list, started the way the list button starts one and drawn and
|
|
63
|
+
* written from the destination's own definition from here on.
|
|
64
|
+
*
|
|
65
|
+
* Which of the two kinds it is started as is the one thing the number alone does not say. A copy
|
|
66
|
+
* made in this same session was remembered alongside what its numbers meant there
|
|
67
|
+
* (`./internalChannel`), so a bulleted list comes back bulleted; a number arriving any other way
|
|
68
|
+
* has no kind to be read off it and counts.
|
|
69
|
+
*/
|
|
70
|
+
export declare const rekeyNumbering: SliceNormalizer;
|
|
71
|
+
/**
|
|
72
|
+
* Takes the anchors off what is pasted.
|
|
73
|
+
*
|
|
74
|
+
* A comment marker, a bookmark and a note reference all point at something written elsewhere in
|
|
75
|
+
* the package: the comment, the bookmark's other end, the note's body. A copy of the marker alone
|
|
76
|
+
* points at the same thing a second time, which is a duplicate identifier the file may not hold
|
|
77
|
+
* and a range the reader cannot close. Nothing here duplicates what they point at, so the anchor
|
|
78
|
+
* goes and the text it stood in stays, which is what Word does with a note it has no body for.
|
|
79
|
+
*
|
|
80
|
+
* A move drop duplicates nothing: the source goes as the drop lands, so the one anchor there was
|
|
81
|
+
* travels with the text it opened. Taking it off would delete a marker the document has no way to
|
|
82
|
+
* write back, which is a change the preserved guard refuses whole, and the drag would do nothing.
|
|
83
|
+
*/
|
|
84
|
+
export declare const detachAnchors: SliceNormalizer;
|
|
85
|
+
/**
|
|
86
|
+
* Works out again what the pasted paragraphs are drawn with.
|
|
87
|
+
*
|
|
88
|
+
* The values a paragraph and the runs in it carry were resolved against the styles, the defaults
|
|
89
|
+
* and the list definitions of wherever the slice came from, this document's own list definitions
|
|
90
|
+
* after the numbers have just been reissued among them. Resolving them here is what makes the
|
|
91
|
+
* numbering the paste registers the numbering the document is then found to wear.
|
|
92
|
+
*/
|
|
93
|
+
export declare const rederiveDisplay: SliceNormalizer;
|
|
94
|
+
export declare const DEFAULT_NORMALIZERS: readonly SliceNormalizer[];
|
|
95
|
+
/**
|
|
96
|
+
* The pasted content as this document may hold it, and the definitions of the lists the paste
|
|
97
|
+
* starts - the ones a reading started, and the ones a reissued number was taken for.
|
|
98
|
+
*/
|
|
99
|
+
export declare function normalizePasted(content: PastedContent, state: EditorState, move: boolean, normalizers?: readonly SliceNormalizer[]): PastedContent;
|