@portone/docx-editor 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/CHANGELOG.md +201 -0
  2. package/CONTRIBUTING.md +2 -0
  3. package/assets/editor.png +0 -0
  4. package/dist/DocxEditor.d.ts +1 -1
  5. package/dist/DocxEditor.js +44 -25
  6. package/dist/docx/cloning.js +4 -9
  7. package/dist/docx/commentOnlyChange.d.ts +5 -3
  8. package/dist/docx/comments/grammar.d.ts +27 -10
  9. package/dist/docx/comments/grammar.js +18 -67
  10. package/dist/docx/comments/model.d.ts +5 -6
  11. package/dist/docx/comments/model.js +0 -9
  12. package/dist/docx/comments/parts.d.ts +16 -10
  13. package/dist/docx/comments/parts.js +65 -13
  14. package/dist/docx/comments/people.d.ts +11 -3
  15. package/dist/docx/comments/people.js +20 -6
  16. package/dist/docx/comments/policy.js +7 -1
  17. package/dist/docx/comments/reading.d.ts +4 -2
  18. package/dist/docx/comments/reading.js +1 -14
  19. package/dist/docx/comments/writing.js +65 -28
  20. package/dist/docx/exportDocx.js +10 -14
  21. package/dist/docx/exportRefs.d.ts +8 -3
  22. package/dist/docx/exportRefs.js +2 -1
  23. package/dist/docx/fidelity.js +24 -10
  24. package/dist/docx/fields.d.ts +42 -0
  25. package/dist/docx/fields.js +76 -0
  26. package/dist/docx/formatting/styles.d.ts +0 -2
  27. package/dist/docx/formatting/styles.js +2 -15
  28. package/dist/docx/headersFooters.d.ts +89 -20
  29. package/dist/docx/headersFooters.js +163 -172
  30. package/dist/docx/identities.js +17 -26
  31. package/dist/docx/importDocx.d.ts +1 -1
  32. package/dist/docx/importDocx.js +100 -48
  33. package/dist/docx/importParagraph.d.ts +18 -5
  34. package/dist/docx/importParagraph.js +75 -128
  35. package/dist/docx/importPolicy.d.ts +70 -0
  36. package/dist/docx/importPolicy.js +195 -0
  37. package/dist/docx/importPreserved.d.ts +30 -0
  38. package/dist/docx/importPreserved.js +54 -0
  39. package/dist/docx/importTable.js +56 -12
  40. package/dist/docx/invariants.js +16 -18
  41. package/dist/docx/notes.d.ts +6 -1
  42. package/dist/docx/notes.js +6 -16
  43. package/dist/docx/pageGeometry.d.ts +5 -14
  44. package/dist/docx/pageGeometry.js +0 -12
  45. package/dist/docx/protectionPolicy.d.ts +13 -5
  46. package/dist/docx/protectionPolicy.js +32 -22
  47. package/dist/docx/scan.d.ts +18 -8
  48. package/dist/docx/scan.js +17 -11
  49. package/dist/docx/sections.d.ts +126 -0
  50. package/dist/docx/sections.js +207 -0
  51. package/dist/docx/serializeBlock.d.ts +13 -4
  52. package/dist/docx/serializeBlock.js +16 -18
  53. package/dist/docx/serializeParagraph.d.ts +9 -4
  54. package/dist/docx/serializeParagraph.js +23 -58
  55. package/dist/docx/serializePreserved.d.ts +14 -0
  56. package/dist/docx/serializePreserved.js +24 -0
  57. package/dist/docx/serializeStory.d.ts +17 -0
  58. package/dist/docx/serializeStory.js +17 -0
  59. package/dist/docx/serializeTable.js +10 -13
  60. package/dist/docx/session.d.ts +23 -6
  61. package/dist/docx/session.js +26 -5
  62. package/dist/docx/story.d.ts +140 -0
  63. package/dist/docx/story.js +237 -0
  64. package/dist/docx/storyProjection.js +6 -1
  65. package/dist/docx/tableTemplate.d.ts +25 -0
  66. package/dist/docx/tableTemplate.js +30 -16
  67. package/dist/docx/wrappers.d.ts +67 -0
  68. package/dist/docx/wrappers.js +117 -0
  69. package/dist/editor/clipboard/blockReaders.d.ts +74 -0
  70. package/dist/editor/clipboard/blockReaders.js +193 -0
  71. package/dist/editor/clipboard/htmlReader.d.ts +26 -0
  72. package/dist/editor/clipboard/htmlReader.js +376 -0
  73. package/dist/editor/clipboard/inlineFormatting.d.ts +0 -7
  74. package/dist/editor/clipboard/inlineFormatting.js +1 -7
  75. package/dist/editor/clipboard/internalChannel.d.ts +57 -0
  76. package/dist/editor/clipboard/internalChannel.js +58 -0
  77. package/dist/editor/clipboard/normalizers.d.ts +99 -0
  78. package/dist/editor/clipboard/normalizers.js +199 -0
  79. package/dist/editor/clipboard/parser.d.ts +34 -0
  80. package/dist/editor/clipboard/parser.js +58 -0
  81. package/dist/editor/clipboard/plugin.d.ts +21 -0
  82. package/dist/editor/clipboard/plugin.js +188 -0
  83. package/dist/editor/clipboard/readContext.d.ts +34 -0
  84. package/dist/editor/clipboard/readContext.js +25 -0
  85. package/dist/editor/clipboard/readers.d.ts +33 -0
  86. package/dist/editor/clipboard/readers.js +37 -0
  87. package/dist/editor/clipboard/source.d.ts +12 -0
  88. package/dist/editor/clipboard/source.js +21 -0
  89. package/dist/editor/commands/comments/editing.d.ts +26 -6
  90. package/dist/editor/commands/comments/editing.js +177 -106
  91. package/dist/editor/commands/comments/model.d.ts +34 -17
  92. package/dist/editor/commands/comments/model.js +11 -0
  93. package/dist/editor/commands/comments/reading.d.ts +5 -3
  94. package/dist/editor/commands/comments/reading.js +8 -58
  95. package/dist/editor/commands/index.d.ts +2 -2
  96. package/dist/editor/commands/index.js +2 -0
  97. package/dist/editor/commands/linkCommands.js +20 -5
  98. package/dist/editor/commands/listCommands.d.ts +6 -0
  99. package/dist/editor/commands/listCommands.js +3 -2
  100. package/dist/editor/commands/lockCommands.js +59 -16
  101. package/dist/editor/commands/noteQueries.d.ts +22 -5
  102. package/dist/editor/commands/noteQueries.js +34 -6
  103. package/dist/editor/commands/paragraphCommands.js +2 -2
  104. package/dist/editor/createEditor.js +13 -3
  105. package/dist/editor/documentStyles.d.ts +26 -4
  106. package/dist/editor/documentStyles.js +9 -4
  107. package/dist/editor/imageFiles.d.ts +2 -2
  108. package/dist/editor/imageFiles.js +2 -0
  109. package/dist/editor/insertTable.d.ts +1 -1
  110. package/dist/editor/insertTable.js +2 -2
  111. package/dist/editor/plainText.d.ts +12 -0
  112. package/dist/editor/plainText.js +2 -1
  113. package/dist/editor/plugins/columnResize.js +6 -3
  114. package/dist/editor/plugins/commentComposer.d.ts +28 -0
  115. package/dist/editor/plugins/commentComposer.js +46 -0
  116. package/dist/editor/plugins/commentDecorations.d.ts +29 -2
  117. package/dist/editor/plugins/commentDecorations.js +89 -15
  118. package/dist/editor/plugins/documentProjection.d.ts +31 -0
  119. package/dist/editor/plugins/documentProjection.js +22 -0
  120. package/dist/editor/plugins/imagePaste.js +15 -8
  121. package/dist/editor/plugins/linkPanel.d.ts +7 -3
  122. package/dist/editor/plugins/linkPanel.js +13 -27
  123. package/dist/editor/plugins/panelState.d.ts +67 -0
  124. package/dist/editor/plugins/panelState.js +54 -0
  125. package/dist/editor/plugins/tableContextMenu.d.ts +1 -1
  126. package/dist/editor/plugins/tableContextMenu.js +30 -47
  127. package/dist/editor/plugins/textContextMenu.d.ts +1 -1
  128. package/dist/editor/plugins/textContextMenu.js +30 -47
  129. package/dist/ooxml/fragment.d.ts +11 -0
  130. package/dist/ooxml/fragment.js +14 -4
  131. package/dist/ooxml/image.d.ts +3 -4
  132. package/dist/ooxml/image.js +50 -5
  133. package/dist/ooxml/names.d.ts +6 -0
  134. package/dist/ooxml/names.js +2 -0
  135. package/dist/ooxml/props.d.ts +10 -0
  136. package/dist/ooxml/props.js +28 -4
  137. package/dist/ooxml/rangeMarkers.d.ts +5 -0
  138. package/dist/ooxml/rangeMarkers.js +24 -0
  139. package/dist/page/PageGuides.d.ts +7 -3
  140. package/dist/page/PageGuides.js +13 -24
  141. package/dist/page/pageLayout.d.ts +64 -5
  142. package/dist/page/pageLayout.js +90 -29
  143. package/dist/page/usePageLayout.d.ts +14 -4
  144. package/dist/page/usePageLayout.js +19 -20
  145. package/dist/schema/attrRoles.js +46 -23
  146. package/dist/schema/clipboard.d.ts +72 -0
  147. package/dist/schema/clipboard.js +288 -0
  148. package/dist/schema/docxSchema.d.ts +9 -1
  149. package/dist/schema/docxSchema.js +217 -165
  150. package/dist/schema/editGuard.d.ts +1 -1
  151. package/dist/schema/guards.js +2 -2
  152. package/dist/schema/index.d.ts +1 -0
  153. package/dist/schema/locks.d.ts +3 -0
  154. package/dist/schema/locks.js +29 -12
  155. package/dist/schema/preservedFragments.d.ts +12 -0
  156. package/dist/schema/preservedFragments.js +38 -0
  157. package/dist/schema/preservedGuards.d.ts +9 -7
  158. package/dist/schema/preservedGuards.js +112 -11
  159. package/dist/schema/protection.d.ts +34 -5
  160. package/dist/schema/protection.js +42 -14
  161. package/dist/schema/rendering.d.ts +14 -0
  162. package/dist/schema/rendering.js +27 -12
  163. package/dist/schema/stories.d.ts +49 -0
  164. package/dist/schema/stories.js +78 -0
  165. package/dist/schema/wrappers.d.ts +58 -0
  166. package/dist/schema/wrappers.js +77 -0
  167. package/dist/styles/classNames.d.ts +3 -5
  168. package/dist/styles/classNames.js +3 -5
  169. package/dist/styles.css +74 -12
  170. package/dist/table/resize.d.ts +5 -3
  171. package/dist/ui/CommentsPanel.d.ts +1 -3
  172. package/dist/ui/CommentsPanel.js +24 -15
  173. package/dist/ui/NotesPanel.js +2 -2
  174. package/dist/ui/TextMenu.d.ts +1 -3
  175. package/dist/ui/TextMenu.js +10 -6
  176. package/dist/ui/comments/CommentComposer.d.ts +6 -1
  177. package/dist/ui/comments/CommentComposer.js +26 -3
  178. package/package.json +5 -4
  179. package/dist/editor/externalClipboard.d.ts +0 -22
  180. package/dist/editor/externalClipboard.js +0 -486
@@ -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;
@@ -0,0 +1,199 @@
1
+ // src/editor/clipboard/normalizers.ts
2
+ import {
3
+ Fragment,
4
+ Mark,
5
+ Slice
6
+ } from "prosemirror-model";
7
+ import {
8
+ paragraphAttrsOf,
9
+ resolveParagraph,
10
+ resolveRun
11
+ } from "../../docx/formatting.js";
12
+ import { withListNumbering } from "../../docx/paraProps.js";
13
+ import {
14
+ allocateList,
15
+ templateList
16
+ } from "../../numbering/listTemplate.js";
17
+ import {
18
+ COMMENT_RANGE_MARKERS,
19
+ PERMISSION_MARKERS,
20
+ RANGE_MARKERS
21
+ } from "../../ooxml/rangeMarkers.js";
22
+ import { docxSchema } from "../../schema/index.js";
23
+ import { listRefOf, numIdsIn } from "../commands/listCommands.js";
24
+ import { documentFormatting } from "../documentStyles.js";
25
+ import {
26
+ canStartNewList,
27
+ documentNumbering
28
+ } from "../plugins/numberingDecorations.js";
29
+ import { NO_LIST_KINDS } from "./internalChannel.js";
30
+ function xmlAttr(value) {
31
+ return typeof value === "string" ? value : null;
32
+ }
33
+ function withAttrs(node, attrs) {
34
+ return node.type.create(attrs, node.content, node.marks);
35
+ }
36
+ function mapFragment(fragment, openStart, openEnd, f) {
37
+ const mapped = [];
38
+ fragment.forEach((child, _offset, index) => {
39
+ const first = index === 0;
40
+ const last = index === fragment.childCount - 1;
41
+ const inside = child.isText ? child : child.type.create(
42
+ child.attrs,
43
+ mapFragment(
44
+ child.content,
45
+ first ? openStart - 1 : 0,
46
+ last ? openEnd - 1 : 0,
47
+ f
48
+ ),
49
+ child.marks
50
+ );
51
+ const next = f(inside);
52
+ if (next !== null) mapped.push(next);
53
+ else if (first && openStart > 0 || last && openEnd > 0) {
54
+ mapped.push(docxSchema.nodes.paragraph.create());
55
+ }
56
+ });
57
+ return Fragment.fromArray(mapped);
58
+ }
59
+ function mapSliceNodes(slice, f) {
60
+ return new Slice(
61
+ mapFragment(slice.content, slice.openStart, slice.openEnd, f),
62
+ slice.openStart,
63
+ slice.openEnd
64
+ );
65
+ }
66
+ var dropSourceIdentity = (slice, { move }) => move ? slice : mapSliceNodes(slice, (node) => {
67
+ if (node.type === docxSchema.nodes.rawBlock) {
68
+ return node.attrs.xml === null ? null : withAttrs(node, { ...node.attrs, srcId: null });
69
+ }
70
+ if (node.type !== docxSchema.nodes.paragraph && node.type !== docxSchema.nodes.table) {
71
+ return node;
72
+ }
73
+ return node.attrs.srcId === null ? node : withAttrs(node, { ...node.attrs, srcId: null });
74
+ });
75
+ var rekeyNumbering = (slice, context) => {
76
+ const given = /* @__PURE__ */ new Map();
77
+ return mapSliceNodes(slice, (node) => {
78
+ if (node.type !== docxSchema.nodes.paragraph) return node;
79
+ const ref = listRefOf(node);
80
+ if (ref === null || context.knownLists.has(ref.numId)) return node;
81
+ const taken = given.has(ref.numId) ? given.get(ref.numId) ?? null : context.startList(context.listKinds.get(ref.numId) ?? "numbered");
82
+ given.set(ref.numId, taken);
83
+ const props = taken === null ? withListNumbering(node.attrs.pPr, {
84
+ numbering: null,
85
+ indent: { kind: "clearHanging" }
86
+ }) : withListNumbering(node.attrs.pPr, {
87
+ numbering: { numId: taken, ilvl: ref.ilvl },
88
+ indent: { kind: "keep" }
89
+ });
90
+ return props === null ? node : withAttrs(node, { ...node.attrs, pPr: props.pPr });
91
+ });
92
+ };
93
+ var ANCHOR_NODES = /* @__PURE__ */ new Set([
94
+ "commentStart",
95
+ "commentEnd",
96
+ "commentReference",
97
+ "noteReference"
98
+ ]);
99
+ var REFERENCE_ELEMENTS = [
100
+ "commentReference",
101
+ "footnoteReference",
102
+ "endnoteReference"
103
+ ];
104
+ var ANCHOR_ELEMENTS = /* @__PURE__ */ new Set([
105
+ ...RANGE_MARKERS,
106
+ ...PERMISSION_MARKERS,
107
+ ...COMMENT_RANGE_MARKERS,
108
+ ...REFERENCE_ELEMENTS
109
+ ]);
110
+ var PRESERVED_INLINE = /* @__PURE__ */ new Set([
111
+ "rawInline",
112
+ "rawRunContent"
113
+ ]);
114
+ function preservedName(node) {
115
+ const xml = node.attrs.xml;
116
+ const opening = typeof xml === "string" ? /^<\s*(?:[^\s:>/]+:)?([^\s>/]+)/.exec(xml) : null;
117
+ if (opening?.[1] !== void 0) return opening[1];
118
+ const element = node.attrs.element;
119
+ return typeof element === "string" ? element : null;
120
+ }
121
+ var detachAnchors = (slice, { move }) => move ? slice : mapSliceNodes(slice, (node) => {
122
+ if (ANCHOR_NODES.has(node.type.name)) return null;
123
+ if (!PRESERVED_INLINE.has(node.type.name)) return node;
124
+ const name = preservedName(node);
125
+ return name !== null && ANCHOR_ELEMENTS.has(name) ? null : node;
126
+ });
127
+ var rederiveDisplay = (slice, { state }) => {
128
+ const formatting = documentFormatting(state);
129
+ return mapSliceNodes(slice, (node) => {
130
+ if (node.type !== docxSchema.nodes.paragraph) return node;
131
+ const paragraph = resolveParagraph(xmlAttr(node.attrs.pPr), formatting);
132
+ const inline = node.children.map((child) => {
133
+ const marks = child.marks.map(
134
+ (mark) => mark.type === docxSchema.marks.run ? mark.type.create({
135
+ ...mark.attrs,
136
+ format: resolveRun(
137
+ xmlAttr(mark.attrs.rPr),
138
+ paragraph,
139
+ formatting
140
+ )
141
+ }) : mark
142
+ );
143
+ return Mark.sameSet(marks, child.marks) ? child : child.mark(marks);
144
+ });
145
+ return node.type.create(
146
+ { ...node.attrs, ...paragraphAttrsOf(paragraph) },
147
+ Fragment.fromArray(inline),
148
+ node.marks
149
+ );
150
+ });
151
+ };
152
+ var DEFAULT_NORMALIZERS = [
153
+ dropSourceIdentity,
154
+ detachAnchors,
155
+ rekeyNumbering,
156
+ rederiveDisplay
157
+ ];
158
+ function normalizePasted(content, state, move, normalizers = DEFAULT_NORMALIZERS) {
159
+ let numbering = documentNumbering(state);
160
+ const known = /* @__PURE__ */ new Set([
161
+ ...numbering.lists.keys(),
162
+ ...numbering.added.keys(),
163
+ ...numIdsIn(state.doc),
164
+ ...content.newLists.keys()
165
+ ]);
166
+ const started = /* @__PURE__ */ new Map();
167
+ const context = {
168
+ state,
169
+ move,
170
+ knownLists: known,
171
+ listKinds: content.listKinds ?? NO_LIST_KINDS,
172
+ startList(kind) {
173
+ if (!canStartNewList(state)) return null;
174
+ const list = templateList(kind);
175
+ const taken = allocateList(numbering, known, list);
176
+ numbering = taken.numbering;
177
+ known.add(taken.numId);
178
+ started.set(taken.numId, list);
179
+ return taken.numId;
180
+ }
181
+ };
182
+ const slice = normalizers.reduce(
183
+ (current, normalize) => normalize(current, context),
184
+ content.slice
185
+ );
186
+ return {
187
+ slice,
188
+ newLists: started.size === 0 ? content.newLists : new Map([...content.newLists, ...started])
189
+ };
190
+ }
191
+ export {
192
+ DEFAULT_NORMALIZERS,
193
+ detachAnchors,
194
+ dropSourceIdentity,
195
+ mapSliceNodes,
196
+ normalizePasted,
197
+ rederiveDisplay,
198
+ rekeyNumbering
199
+ };
@@ -0,0 +1,34 @@
1
+ import { DOMParser, Slice } from "prosemirror-model";
2
+ import type { EditorView } from "prosemirror-view";
3
+ import type { PastedContent } from "./htmlReader";
4
+ import type { ClipboardInput, ClipboardReader } from "./readers";
5
+ /**
6
+ * The parser ProseMirror hands the clipboard to.
7
+ *
8
+ * It is a `DOMParser` because that is what the `clipboardParser` prop is, but it holds no rules:
9
+ * clipboard markup is read by the reader chain alone, so a `parseDOM` rule of the schema - which
10
+ * would take the document's own XML back out of a private attribute - never sees it.
11
+ */
12
+ export declare class DocxClipboardParser extends DOMParser {
13
+ private readonly readers;
14
+ private readonly inputOf;
15
+ private read;
16
+ private textOnly;
17
+ private pasteEvent;
18
+ constructor(readers: readonly ClipboardReader[], inputOf: (dom: Node) => ClipboardInput | null);
19
+ parseSlice(dom: Node): Slice;
20
+ /**
21
+ * What the last parse read, given up as it is taken.
22
+ *
23
+ * ProseMirror widens whatever a parser returns to the openness the surrounding markup implies
24
+ * and wraps it in the nodes a `data-pm-slice` context names, neither of which the reader asked
25
+ * for. The plugin puts this back in `transformPasted`, and takes it so that a later drag - which
26
+ * reaches `transformPasted` without a parse - never picks up a stale reading.
27
+ */
28
+ takeRead(): PastedContent | null;
29
+ /** A new reading replaces the mode left by the previous clipboard event */
30
+ setPlainText(plain: boolean): void;
31
+ isPlainText(event: ClipboardEvent): boolean;
32
+ }
33
+ /** Whether the current paste chose text, so image handlers must leave its HTML and files alone */
34
+ export declare function plainTextPaste(view: EditorView, event: ClipboardEvent): boolean;
@@ -0,0 +1,58 @@
1
+ // src/editor/clipboard/parser.ts
2
+ import { DOMParser, Slice } from "prosemirror-model";
3
+ import { docxSchema } from "../../schema/index.js";
4
+ var DocxClipboardParser = class extends DOMParser {
5
+ constructor(readers, inputOf) {
6
+ super(docxSchema, []);
7
+ this.readers = readers;
8
+ this.inputOf = inputOf;
9
+ }
10
+ readers;
11
+ inputOf;
12
+ read = null;
13
+ textOnly = false;
14
+ pasteEvent = null;
15
+ parseSlice(dom) {
16
+ this.read = null;
17
+ this.setPlainText(false);
18
+ const input = this.inputOf(dom);
19
+ if (input === null) return Slice.empty;
20
+ for (const reader of this.readers) {
21
+ const content = reader(input);
22
+ if (content === null) continue;
23
+ this.read = content;
24
+ return content.slice;
25
+ }
26
+ return Slice.empty;
27
+ }
28
+ /**
29
+ * What the last parse read, given up as it is taken.
30
+ *
31
+ * ProseMirror widens whatever a parser returns to the openness the surrounding markup implies
32
+ * and wraps it in the nodes a `data-pm-slice` context names, neither of which the reader asked
33
+ * for. The plugin puts this back in `transformPasted`, and takes it so that a later drag - which
34
+ * reaches `transformPasted` without a parse - never picks up a stale reading.
35
+ */
36
+ takeRead() {
37
+ const read = this.read;
38
+ this.read = null;
39
+ return read;
40
+ }
41
+ /** A new reading replaces the mode left by the previous clipboard event */
42
+ setPlainText(plain) {
43
+ this.textOnly = plain;
44
+ this.pasteEvent = null;
45
+ }
46
+ isPlainText(event) {
47
+ if (this.pasteEvent === null) this.pasteEvent = event;
48
+ return this.pasteEvent === event && this.textOnly;
49
+ }
50
+ };
51
+ function plainTextPaste(view, event) {
52
+ const parser = view.someProp("clipboardParser");
53
+ return parser instanceof DocxClipboardParser && parser.isPlainText(event);
54
+ }
55
+ export {
56
+ DocxClipboardParser,
57
+ plainTextPaste
58
+ };
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The one plugin holding the editor's clipboard.
3
+ *
4
+ * Everything the clipboard carries in or out passes through the props declared here: what a copy
5
+ * is written as, what a paste is read as, and what a drop from outside becomes. ProseMirror asks
6
+ * these before it asks any `handlePaste`. This plugin handles only an empty reading, preserving
7
+ * the selection or retrying the text fallback. Content goes in through ProseMirror's insertion
8
+ * with the `paste` and `uiEvent` meta a plugin
9
+ * watching for one reads (`prosemirror-view`'s `doPaste`), and a table decides for itself what a
10
+ * paste over a cell selection means (`prosemirror-tables`).
11
+ */
12
+ import { Plugin } from "prosemirror-state";
13
+ import { type SliceNormalizer } from "./normalizers";
14
+ import { type ClipboardReader } from "./readers";
15
+ export interface ClipboardOptions {
16
+ /** Tried from the front. The first reader to answer decides what a paste puts in */
17
+ readers?: readonly ClipboardReader[];
18
+ /** Run in order over every pasted and dropped slice before it is put in */
19
+ normalizers?: readonly SliceNormalizer[];
20
+ }
21
+ export declare function docxClipboard(options?: ClipboardOptions): Plugin;
@@ -0,0 +1,188 @@
1
+ // src/editor/clipboard/plugin.ts
2
+ import { Fragment, Slice } from "prosemirror-model";
3
+ import { Plugin } from "prosemirror-state";
4
+ import {
5
+ NEW_LISTS_ATTR,
6
+ NO_NEW_LISTS,
7
+ newListsOf,
8
+ newListsValue
9
+ } from "../../numbering/listRegistry.js";
10
+ import { clipboardSerializer, clipboardText } from "../../schema/clipboard.js";
11
+ import { numIdsIn } from "../commands/listCommands.js";
12
+ import { documentOf } from "../editorDocument.js";
13
+ import { insertPlainText } from "../plainText.js";
14
+ import { moveCaretToDrop } from "../plugins/dropCaret.js";
15
+ import { documentNumbering } from "../plugins/numberingDecorations.js";
16
+ import {
17
+ INTERNAL_TOKEN_ATTRIBUTE,
18
+ internalTokenOf,
19
+ rememberCopied
20
+ } from "./internalChannel.js";
21
+ import {
22
+ DEFAULT_NORMALIZERS,
23
+ normalizePasted
24
+ } from "./normalizers.js";
25
+ import { DocxClipboardParser } from "./parser.js";
26
+ import { readContextOf } from "./readContext.js";
27
+ import {
28
+ DEFAULT_READERS,
29
+ plainTextSlice
30
+ } from "./readers.js";
31
+ function bareWrappers(content, openStart, openEnd) {
32
+ const wrapper = content.firstChild;
33
+ if (openStart <= 1 || openEnd <= 1 || content.childCount !== 1)
34
+ return content;
35
+ if (wrapper === null || wrapper.childCount !== 1) return content;
36
+ return Fragment.from(
37
+ wrapper.type.create(
38
+ null,
39
+ bareWrappers(wrapper.content, openStart - 1, openEnd - 1),
40
+ wrapper.marks
41
+ )
42
+ );
43
+ }
44
+ function copiedSlice(slice) {
45
+ return new Slice(
46
+ bareWrappers(slice.content, slice.openStart, slice.openEnd),
47
+ slice.openStart,
48
+ slice.openEnd
49
+ );
50
+ }
51
+ function dragCopyModifier() {
52
+ return /Mac|iP(hone|[oa]d)/.test(navigator.platform) ? "altKey" : "ctrlKey";
53
+ }
54
+ function dropMoves(view, event) {
55
+ if (view.dragging === null) return false;
56
+ let copies;
57
+ view.someProp("dragCopies", (test) => {
58
+ copies = copies === true || test(event);
59
+ });
60
+ return copies === void 0 ? !event[dragCopyModifier()] : !copies;
61
+ }
62
+ function docxClipboard(options = {}) {
63
+ const normalizers = options.normalizers ?? DEFAULT_NORMALIZERS;
64
+ let copyToken = null;
65
+ const serializer = clipboardSerializer((copy) => {
66
+ if (copyToken !== null) {
67
+ copy.firstElementChild?.setAttribute(INTERNAL_TOKEN_ATTRIBUTE, copyToken);
68
+ }
69
+ });
70
+ let host = null;
71
+ const parser = new DocxClipboardParser(
72
+ options.readers ?? DEFAULT_READERS,
73
+ (dom) => host === null ? null : {
74
+ dom,
75
+ token: internalTokenOf(dom),
76
+ sessionId: documentOf(host.state).session?.sessionId ?? null,
77
+ context: readContextOf(host.state, host.dom.ownerDocument)
78
+ }
79
+ );
80
+ let started = null;
81
+ let dropMove = false;
82
+ let draggingOut = false;
83
+ return new Plugin({
84
+ view(view) {
85
+ host = view;
86
+ return {
87
+ destroy() {
88
+ if (host === view) host = null;
89
+ }
90
+ };
91
+ },
92
+ /**
93
+ * The definitions of the lists a paste began, registered on the document once the paste is
94
+ * in. A list the pasted markup started is numbered as it is read, and only the edit that
95
+ * lands says whether that number is worn in the end.
96
+ */
97
+ appendTransaction(transactions, _before, after) {
98
+ const lists = started;
99
+ started = null;
100
+ if (lists === null || lists.size === 0 || !transactions.some((tr) => tr.docChanged)) {
101
+ return null;
102
+ }
103
+ const worn = numIdsIn(after.doc);
104
+ const registered = newListsOf(after.doc.attrs[NEW_LISTS_ATTR]);
105
+ const missing = [...lists].filter(
106
+ ([numId]) => worn.has(numId) && !registered.has(numId)
107
+ );
108
+ if (missing.length === 0) return null;
109
+ return after.tr.setDocAttribute(
110
+ NEW_LISTS_ATTR,
111
+ newListsValue(new Map([...registered, ...missing]))
112
+ );
113
+ },
114
+ props: {
115
+ handleDOMEvents: {
116
+ paste() {
117
+ parser.setPlainText(false);
118
+ return false;
119
+ },
120
+ // ProseMirror writes and reads the dragged slice inside the very event the two below
121
+ // answer for, through props it hands the view and not the event, and it runs these first.
122
+ // A microtask is the first thing to run once the event is over, so neither answer is left
123
+ // standing for whatever comes next
124
+ dragstart() {
125
+ draggingOut = true;
126
+ queueMicrotask(() => {
127
+ draggingOut = false;
128
+ });
129
+ return false;
130
+ },
131
+ drop(view, event) {
132
+ dropMove = dropMoves(view, event);
133
+ queueMicrotask(() => {
134
+ dropMove = false;
135
+ });
136
+ return false;
137
+ }
138
+ },
139
+ clipboardParser: parser,
140
+ clipboardSerializer: serializer,
141
+ clipboardTextParser(text, context, plain) {
142
+ parser.setPlainText(plain);
143
+ return plainTextSlice(text, context);
144
+ },
145
+ clipboardTextSerializer: clipboardText,
146
+ transformCopied(slice, view) {
147
+ const route = draggingOut ? "drag" : "clipboard";
148
+ copyToken = rememberCopied(
149
+ route,
150
+ slice,
151
+ documentOf(view.state).session?.sessionId ?? null,
152
+ documentNumbering(view.state)
153
+ );
154
+ return copiedSlice(slice);
155
+ },
156
+ transformPasted(slice, view, plain) {
157
+ if (!plain) parser.setPlainText(false);
158
+ const read = parser.takeRead();
159
+ const content = normalizePasted(
160
+ read ?? { slice, newLists: NO_NEW_LISTS },
161
+ view.state,
162
+ dropMove,
163
+ normalizers
164
+ );
165
+ started = content.newLists;
166
+ return content.slice;
167
+ },
168
+ handlePaste(view, event, slice) {
169
+ if (slice.content.size > 0) return false;
170
+ const text = event.clipboardData?.getData("text/plain") ?? "";
171
+ if (!parser.isPlainText(event) && text) view.pasteText(text, event);
172
+ return true;
173
+ },
174
+ handleDrop(view, event) {
175
+ if (view.dragging) return false;
176
+ const text = event.dataTransfer?.getData("text/plain") ?? "";
177
+ if (text) {
178
+ moveCaretToDrop(view, event);
179
+ insertPlainText(view, text);
180
+ }
181
+ return true;
182
+ }
183
+ }
184
+ });
185
+ }
186
+ export {
187
+ docxClipboard
188
+ };