@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
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
} from "prosemirror-state";
|
|
6
6
|
import { createTableNode, isTableSide } from "../docx/tableTemplate.js";
|
|
7
7
|
import { editShut } from "../schema/guards.js";
|
|
8
|
-
import {
|
|
8
|
+
import { sectionGeometryAt } from "./documentStyles.js";
|
|
9
9
|
function isInTable($pos) {
|
|
10
10
|
for (let depth = $pos.depth; depth > 0; depth -= 1) {
|
|
11
11
|
if ($pos.node(depth).type.spec.tableRole === "table") return true;
|
|
@@ -30,7 +30,7 @@ function insertTable({ rows, columns }) {
|
|
|
30
30
|
const tr = state.tr.insert(
|
|
31
31
|
at,
|
|
32
32
|
Fragment.fromArray([
|
|
33
|
-
createTableNode(rows, columns,
|
|
33
|
+
createTableNode(rows, columns, sectionGeometryAt(state, at)),
|
|
34
34
|
state.schema.nodes.paragraph.create()
|
|
35
35
|
])
|
|
36
36
|
);
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import type { Node as PMNode } from "prosemirror-model";
|
|
10
10
|
import type { EditorState, Transaction } from "prosemirror-state";
|
|
11
|
+
import { type FormattingContext } from "../docx/formatting";
|
|
11
12
|
import type { ParagraphProps } from "../docx/paraProps";
|
|
12
13
|
export interface ParagraphSpot {
|
|
13
14
|
pos: number;
|
|
@@ -33,5 +34,19 @@ export declare function editableParagraphs(state: EditorState): ParagraphSpot[];
|
|
|
33
34
|
export declare function paragraphPPr(node: PMNode): string | null;
|
|
34
35
|
/** How to edit a single paragraph. Null skips that paragraph */
|
|
35
36
|
export type ParagraphSurgery = (node: PMNode) => ParagraphProps | null;
|
|
37
|
+
/** What an edit does beyond swapping the fragments of the paragraphs it names */
|
|
38
|
+
export interface ParagraphEditExtras {
|
|
39
|
+
/**
|
|
40
|
+
* The context the new display values are resolved against, which is the state's own unless the
|
|
41
|
+
* edit is itself changing it. An edit that starts a list hands in the context that already
|
|
42
|
+
* holds it, so the paragraph is drawn against the list it is joining in that same transaction.
|
|
43
|
+
*/
|
|
44
|
+
formatting?: FormattingContext;
|
|
45
|
+
/**
|
|
46
|
+
* What else the edit records in the same transaction, such as the definition of the list the
|
|
47
|
+
* paragraphs are joining, so that undo takes the two back together.
|
|
48
|
+
*/
|
|
49
|
+
alongside?: (tr: Transaction) => void;
|
|
50
|
+
}
|
|
36
51
|
/** Edits the selected paragraphs in a single transaction. Does nothing when there is nothing to edit */
|
|
37
|
-
export declare function editParagraphs(state: EditorState, dispatch: ((tr: Transaction) => void) | undefined, surgery: ParagraphSurgery): boolean;
|
|
52
|
+
export declare function editParagraphs(state: EditorState, dispatch: ((tr: Transaction) => void) | undefined, surgery: ParagraphSurgery, extras?: ParagraphEditExtras): boolean;
|
|
@@ -3,6 +3,7 @@ import { paragraphAttrsFor } from "../docx/formatting.js";
|
|
|
3
3
|
import { docxSchema } from "../schema/index.js";
|
|
4
4
|
import { editShut } from "../schema/guards.js";
|
|
5
5
|
import { documentFormatting } from "./documentStyles.js";
|
|
6
|
+
import { paragraphPlacementAt } from "./paragraphPlacement.js";
|
|
6
7
|
function selectedParagraphs(state) {
|
|
7
8
|
const spots = /* @__PURE__ */ new Map();
|
|
8
9
|
for (const range of state.selection.ranges) {
|
|
@@ -23,25 +24,32 @@ function paragraphPPr(node) {
|
|
|
23
24
|
const pPr = node.attrs.pPr;
|
|
24
25
|
return typeof pPr === "string" ? pPr : null;
|
|
25
26
|
}
|
|
26
|
-
function writeChanges(state, changed) {
|
|
27
|
+
function writeChanges(state, changed, extras) {
|
|
27
28
|
const tr = state.tr;
|
|
28
|
-
const formatting = documentFormatting(state);
|
|
29
|
+
const formatting = extras.formatting ?? documentFormatting(state);
|
|
29
30
|
for (const { spot, props } of changed) {
|
|
30
31
|
tr.setNodeMarkup(tr.mapping.map(spot.pos), void 0, {
|
|
31
32
|
...spot.node.attrs,
|
|
32
33
|
pPr: props.pPr,
|
|
33
|
-
...paragraphAttrsFor(
|
|
34
|
+
...paragraphAttrsFor(
|
|
35
|
+
props.pPr,
|
|
36
|
+
formatting,
|
|
37
|
+
paragraphPlacementAt(state.doc, spot.pos)
|
|
38
|
+
)
|
|
34
39
|
});
|
|
35
40
|
}
|
|
41
|
+
extras.alongside?.(tr);
|
|
36
42
|
return tr;
|
|
37
43
|
}
|
|
38
|
-
function editParagraphs(state, dispatch, surgery) {
|
|
44
|
+
function editParagraphs(state, dispatch, surgery, extras = {}) {
|
|
39
45
|
const changed = editableParagraphs(state).flatMap((spot) => {
|
|
40
46
|
const props = surgery(spot.node);
|
|
41
47
|
return props ? [{ spot, props }] : [];
|
|
42
48
|
});
|
|
43
49
|
if (changed.length === 0) return false;
|
|
44
|
-
if (dispatch)
|
|
50
|
+
if (dispatch) {
|
|
51
|
+
dispatch(writeChanges(state, changed, extras).scrollIntoView());
|
|
52
|
+
}
|
|
45
53
|
return true;
|
|
46
54
|
}
|
|
47
55
|
export {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Finds the table-style conditions at an editor position for both reads and writes. */
|
|
2
|
+
import type { Node as PMNode } from "prosemirror-model";
|
|
3
|
+
import type { ParagraphPlacement } from "../docx/formatting";
|
|
4
|
+
/**
|
|
5
|
+
* The part of a table the paragraph stands in, which its table style dresses it by. null for a
|
|
6
|
+
* paragraph of the body, and for one in a cell of a table this editor did not build.
|
|
7
|
+
*
|
|
8
|
+
* The cell is found by walking out of the paragraph rather than passed down, because the walk that
|
|
9
|
+
* runs the derivers goes over the blocks and hands each of them nothing but its own position.
|
|
10
|
+
*/
|
|
11
|
+
export declare function paragraphPlacementAt(doc: PMNode, pos: number): ParagraphPlacement | null;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// src/editor/paragraphPlacement.ts
|
|
2
|
+
import { TableMap } from "prosemirror-tables";
|
|
3
|
+
import { docxSchema } from "../schema/index.js";
|
|
4
|
+
import { cellDefaultsAt, tableCellSources } from "../table/gridBorders.js";
|
|
5
|
+
function paragraphPlacementAt(doc, pos) {
|
|
6
|
+
const $pos = doc.resolve(pos);
|
|
7
|
+
for (let depth = $pos.depth; depth >= 2; depth -= 1) {
|
|
8
|
+
if ($pos.node(depth).type !== docxSchema.nodes.tableCell) continue;
|
|
9
|
+
const table = $pos.node(depth - 2);
|
|
10
|
+
if (table.type !== docxSchema.nodes.table) return null;
|
|
11
|
+
const cell = $pos.before(depth) - $pos.start(depth - 2);
|
|
12
|
+
return cellDefaultsAt(TableMap.get(table), cell, tableCellSources(table)).placement;
|
|
13
|
+
}
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
export {
|
|
17
|
+
paragraphPlacementAt
|
|
18
|
+
};
|
|
@@ -1,3 +1,15 @@
|
|
|
1
1
|
import type { EditorView } from "prosemirror-view";
|
|
2
|
+
/** A single character that becomes an element, or text that goes in as it is */
|
|
3
|
+
export type Piece = {
|
|
4
|
+
text: string;
|
|
5
|
+
} | {
|
|
6
|
+
node: "hardBreak" | "tab";
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Splits the incoming text into the pieces to be put into the document.
|
|
10
|
+
* Control characters that carry meaning become elements, and the rest are stripped because they
|
|
11
|
+
* would make the file impossible to open.
|
|
12
|
+
*/
|
|
13
|
+
export declare function toPieces(source: string): Piece[];
|
|
2
14
|
export declare function insertPlainText(view: EditorView, source: string): void;
|
|
3
15
|
export declare function insertPlainTextAt(view: EditorView, source: string, from: number, to: number): void;
|
package/dist/editor/plainText.js
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
shiftedTableWidth,
|
|
15
15
|
tableGridCols
|
|
16
16
|
} from "../../table/widths.js";
|
|
17
|
-
import {
|
|
17
|
+
import { sectionGeometryAt } from "../documentStyles.js";
|
|
18
18
|
import { TableNodeView } from "../views/tableView.js";
|
|
19
19
|
var EDGE_TOLERANCE_PX = 4;
|
|
20
20
|
var FALLBACK_DXA_PER_PX = 15;
|
|
@@ -127,7 +127,7 @@ function columnResize() {
|
|
|
127
127
|
col: current.target.col,
|
|
128
128
|
width: current.width
|
|
129
129
|
},
|
|
130
|
-
|
|
130
|
+
sectionGeometryAt(current.view.state, current.target.tablePos)
|
|
131
131
|
);
|
|
132
132
|
if (tr) current.view.dispatch(tr);
|
|
133
133
|
}
|
|
@@ -144,7 +144,10 @@ function columnResize() {
|
|
|
144
144
|
drag = {
|
|
145
145
|
view,
|
|
146
146
|
target,
|
|
147
|
-
maxTotal: maxGridTotal(
|
|
147
|
+
maxTotal: maxGridTotal(
|
|
148
|
+
target.table,
|
|
149
|
+
sectionGeometryAt(view.state, target.tablePos)
|
|
150
|
+
),
|
|
148
151
|
startX: event.clientX,
|
|
149
152
|
dxaPerPx: dxaPerPx(target.gridCols, target.tableDom),
|
|
150
153
|
cols,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the built-in comment composer stands: the stretch of text it was opened over, or nothing
|
|
3
|
+
* when it is shut.
|
|
4
|
+
*
|
|
5
|
+
* The stretch is the point of holding it here rather than in the component that draws the form.
|
|
6
|
+
* A comment is written about text the reader picked out, and writing it takes as long as it takes:
|
|
7
|
+
* the caret moves on, the page is scrolled, a word elsewhere is fixed. The stretch is moved along
|
|
8
|
+
* by every edit that happens meanwhile, so the comment lands on the text it was written for, and
|
|
9
|
+
* the form goes away by itself once that text is gone or the document stops taking comments.
|
|
10
|
+
*
|
|
11
|
+
* The commands here are the editor's own. A consumer opening its own composer from its own toolbar
|
|
12
|
+
* is a public surface this package has not settled yet, so nothing below is exported from
|
|
13
|
+
* `./commands`.
|
|
14
|
+
*/
|
|
15
|
+
import type { Command, EditorState, Plugin } from "prosemirror-state";
|
|
16
|
+
import type { CommentRange } from "../commands/comments/model";
|
|
17
|
+
/**
|
|
18
|
+
* Opens the composer over the selected text. False where a comment could not go there anyway, so
|
|
19
|
+
* the menu entry that runs it is drawn dead rather than opening a form nothing would come of. It
|
|
20
|
+
* is the same question that entry is drawn from, asked of the same selection.
|
|
21
|
+
*/
|
|
22
|
+
export declare const openCommentComposer: Command;
|
|
23
|
+
/** Closes it. Called on Cancel, on the comment being written, and on the panel being put away */
|
|
24
|
+
export declare const closeCommentComposer: Command;
|
|
25
|
+
export declare function isCommentComposerOpen(state: EditorState): boolean;
|
|
26
|
+
/** The stretch the open composer is writing about. Null when no composer stands */
|
|
27
|
+
export declare function commentComposerRange(state: EditorState): CommentRange | null;
|
|
28
|
+
export declare function commentComposer(): Plugin<CommentRange | null>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// src/editor/plugins/commentComposer.ts
|
|
2
|
+
import { canAddComment } from "../commands/comments/editing.js";
|
|
3
|
+
import { panelPlugin } from "./panelState.js";
|
|
4
|
+
function isCommentRange(value) {
|
|
5
|
+
if (typeof value !== "object" || value === null) return false;
|
|
6
|
+
const { from, to } = value;
|
|
7
|
+
return typeof from === "number" && typeof to === "number";
|
|
8
|
+
}
|
|
9
|
+
var composer = panelPlugin({
|
|
10
|
+
name: "docxEditorCommentComposer",
|
|
11
|
+
isAnchor: isCommentRange,
|
|
12
|
+
// The stretch moves with the text around it. Its two ends are biased inwards, so text typed
|
|
13
|
+
// against either edge stays outside the comment and an edit that swallows the stretch leaves
|
|
14
|
+
// the two ends together, which is the form closing
|
|
15
|
+
onDocChange: (anchor, tr) => {
|
|
16
|
+
const from = tr.mapping.map(anchor.from, 1);
|
|
17
|
+
const to = tr.mapping.map(anchor.to, -1);
|
|
18
|
+
return to > from ? { from, to } : null;
|
|
19
|
+
},
|
|
20
|
+
// Asked after every transaction, which is what catches the mode being switched to read-only:
|
|
21
|
+
// that changes no text, so the mapping above never sees it. It is also what keeps the form from
|
|
22
|
+
// ever standing over a stretch that takes no comment, whoever opened it
|
|
23
|
+
closeWhen: (state, anchor) => !canAddComment(state, anchor)
|
|
24
|
+
});
|
|
25
|
+
var openCommentComposer = (state, dispatch) => {
|
|
26
|
+
if (!canAddComment(state)) return false;
|
|
27
|
+
const { from, to } = state.selection;
|
|
28
|
+
return composer.open({ from, to })(state, dispatch);
|
|
29
|
+
};
|
|
30
|
+
var closeCommentComposer = composer.close;
|
|
31
|
+
function isCommentComposerOpen(state) {
|
|
32
|
+
return composer.anchor(state) !== null;
|
|
33
|
+
}
|
|
34
|
+
function commentComposerRange(state) {
|
|
35
|
+
return composer.anchor(state);
|
|
36
|
+
}
|
|
37
|
+
function commentComposer() {
|
|
38
|
+
return composer.plugin;
|
|
39
|
+
}
|
|
40
|
+
export {
|
|
41
|
+
closeCommentComposer,
|
|
42
|
+
commentComposer,
|
|
43
|
+
commentComposerRange,
|
|
44
|
+
isCommentComposerOpen,
|
|
45
|
+
openCommentComposer
|
|
46
|
+
};
|
|
@@ -1,3 +1,30 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* What the comments in a document come to, worked out once per edit: the threads in document
|
|
3
|
+
* order, the lookup a command asking after one of them uses, and the ranges drawn over the text
|
|
4
|
+
* each was written for.
|
|
5
|
+
*
|
|
6
|
+
* All three are read off the same walk. Which comments a document holds is decided by the document
|
|
7
|
+
* and nothing else, so it is a projection (`./documentProjection`) rather than something worked
|
|
8
|
+
* out again for every command asked, every decoration drawn and every render of the list beside
|
|
9
|
+
* the page.
|
|
10
|
+
*/
|
|
11
|
+
import type { Plugin } from "prosemirror-state";
|
|
12
|
+
import { DecorationSet } from "prosemirror-view";
|
|
13
|
+
import { type DocumentComment } from "../commands/comments/model";
|
|
14
|
+
export interface DocumentComments {
|
|
15
|
+
comments: readonly DocumentComment[];
|
|
16
|
+
/**
|
|
17
|
+
* The comments by id. A document can hold the same id twice - a reference copied and pasted
|
|
18
|
+
* carries the one it was written with - and the first of them wins, which is the one a search
|
|
19
|
+
* through the list in document order arrived at.
|
|
20
|
+
*/
|
|
21
|
+
byId: ReadonlyMap<string, DocumentComment>;
|
|
22
|
+
/** The stretch each comment was written for, drawn as it stood when the edit landed */
|
|
23
|
+
decorations: DecorationSet;
|
|
24
|
+
}
|
|
25
|
+
export declare const commentProjection: import("./documentProjection").DocumentProjection<DocumentComments>;
|
|
26
|
+
/**
|
|
27
|
+
* What a state built by `createEditor` registers: the comments it goes on to answer, and the
|
|
28
|
+
* ranges drawn over the text identified by the Word comment markers holding them.
|
|
29
|
+
*/
|
|
3
30
|
export declare function commentDecorations(): Plugin;
|
|
@@ -1,23 +1,97 @@
|
|
|
1
1
|
// src/editor/plugins/commentDecorations.ts
|
|
2
|
-
import { Plugin } from "prosemirror-state";
|
|
3
2
|
import { Decoration, DecorationSet } from "prosemirror-view";
|
|
3
|
+
import { storyKey, storyOf, storyText } from "../../docx/story.js";
|
|
4
4
|
import { editorClassNames } from "../../styles/classNames.js";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
5
|
+
import {
|
|
6
|
+
repliesAttr,
|
|
7
|
+
stringAttr
|
|
8
|
+
} from "../commands/comments/model.js";
|
|
9
|
+
import { documentProjection } from "./documentProjection.js";
|
|
10
|
+
function markerPositions(doc) {
|
|
11
|
+
const starts = /* @__PURE__ */ new Map();
|
|
12
|
+
const ends = /* @__PURE__ */ new Map();
|
|
13
|
+
const references = [];
|
|
14
|
+
doc.descendants((node, pos) => {
|
|
15
|
+
const id = stringAttr(node.attrs.id);
|
|
16
|
+
if (id === null) return true;
|
|
17
|
+
if (node.type.name === "commentStart" && !starts.has(id)) {
|
|
18
|
+
starts.set(id, pos);
|
|
19
|
+
} else if (node.type.name === "commentEnd" && !ends.has(id)) {
|
|
20
|
+
ends.set(id, pos);
|
|
21
|
+
} else if (node.type.name === "commentReference") {
|
|
22
|
+
references.push({ node, pos });
|
|
18
23
|
}
|
|
24
|
+
return true;
|
|
25
|
+
});
|
|
26
|
+
return { starts, ends, references };
|
|
27
|
+
}
|
|
28
|
+
function bodyText(doc, id) {
|
|
29
|
+
return storyText(storyOf(doc, storyKey("comment", id)));
|
|
30
|
+
}
|
|
31
|
+
function commentsIn(doc) {
|
|
32
|
+
const markers = markerPositions(doc);
|
|
33
|
+
return markers.references.map(({ node, pos }) => {
|
|
34
|
+
const id = stringAttr(node.attrs.id) ?? "";
|
|
35
|
+
const start = markers.starts.get(id);
|
|
36
|
+
const end = markers.ends.get(id);
|
|
37
|
+
const hasRange = start !== void 0 && end !== void 0 && start < end;
|
|
38
|
+
const point = start ?? end ?? pos;
|
|
39
|
+
return {
|
|
40
|
+
id,
|
|
41
|
+
author: stringAttr(node.attrs.author),
|
|
42
|
+
authorId: stringAttr(node.attrs.authorId),
|
|
43
|
+
initials: stringAttr(node.attrs.initials),
|
|
44
|
+
date: stringAttr(node.attrs.date),
|
|
45
|
+
text: bodyText(doc, id),
|
|
46
|
+
from: hasRange ? start + 1 : point,
|
|
47
|
+
to: hasRange ? end : point,
|
|
48
|
+
referencePos: pos,
|
|
49
|
+
resolved: node.attrs.resolved === true,
|
|
50
|
+
replies: repliesAttr(node.attrs.replies).map((reply) => ({
|
|
51
|
+
id: reply.id,
|
|
52
|
+
author: reply.author,
|
|
53
|
+
authorId: reply.authorId,
|
|
54
|
+
initials: reply.initials,
|
|
55
|
+
date: reply.date,
|
|
56
|
+
text: bodyText(doc, reply.id)
|
|
57
|
+
}))
|
|
58
|
+
};
|
|
19
59
|
});
|
|
20
60
|
}
|
|
61
|
+
function deriveComments(doc) {
|
|
62
|
+
const comments = commentsIn(doc);
|
|
63
|
+
const byId = /* @__PURE__ */ new Map();
|
|
64
|
+
for (const comment of comments) {
|
|
65
|
+
if (!byId.has(comment.id)) byId.set(comment.id, comment);
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
comments,
|
|
69
|
+
byId,
|
|
70
|
+
decorations: DecorationSet.create(
|
|
71
|
+
doc,
|
|
72
|
+
comments.filter((comment) => comment.from < comment.to).map(
|
|
73
|
+
(comment) => Decoration.inline(comment.from, comment.to, {
|
|
74
|
+
class: editorClassNames.commentRange,
|
|
75
|
+
"data-comment-id": comment.id
|
|
76
|
+
})
|
|
77
|
+
)
|
|
78
|
+
)
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
var commentProjection = documentProjection(
|
|
82
|
+
"docxEditorComments",
|
|
83
|
+
deriveComments,
|
|
84
|
+
{
|
|
85
|
+
// The set the last edit derived. Written as a method so that `this` is the plugin holding it
|
|
86
|
+
decorations(state) {
|
|
87
|
+
return this.getState(state)?.decorations;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
);
|
|
91
|
+
function commentDecorations() {
|
|
92
|
+
return commentProjection.plugin;
|
|
93
|
+
}
|
|
21
94
|
export {
|
|
22
|
-
commentDecorations
|
|
95
|
+
commentDecorations,
|
|
96
|
+
commentProjection
|
|
23
97
|
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One convention for a value the whole document decides: the comments beside it, the notes under
|
|
3
|
+
* it, the list markers drawn over it.
|
|
4
|
+
*
|
|
5
|
+
* Such a value is worked out by walking the document, and what it is worked out from changes only
|
|
6
|
+
* when the document does. Held here, it is derived once per edit rather than once per read, and a
|
|
7
|
+
* caret moving, a menu opening or a component rendering again all read back the value the last
|
|
8
|
+
* edit left. What a caller writes is unchanged: `read` answers with a value either way.
|
|
9
|
+
*
|
|
10
|
+
* A projection derives from the document alone. Anything that also depends on the selection, the
|
|
11
|
+
* protection or another plugin belongs in that plugin, which is asked again after every
|
|
12
|
+
* transaction rather than after an edit alone.
|
|
13
|
+
*/
|
|
14
|
+
import type { Node as PMNode } from "prosemirror-model";
|
|
15
|
+
import { type EditorState, Plugin, type PluginSpec } from "prosemirror-state";
|
|
16
|
+
export interface DocumentProjection<T> {
|
|
17
|
+
plugin: Plugin<T>;
|
|
18
|
+
/**
|
|
19
|
+
* The derived value. A state built without the plugin - one a consumer assembled itself, or one
|
|
20
|
+
* a test made out of a bare document - is answered by deriving on the spot, so a public reader
|
|
21
|
+
* standing on a projection keeps answering whoever asks it.
|
|
22
|
+
*/
|
|
23
|
+
read(state: EditorState): T;
|
|
24
|
+
}
|
|
25
|
+
export declare function documentProjection<T>(name: string, derive: (doc: PMNode) => T,
|
|
26
|
+
/**
|
|
27
|
+
* The view props drawn from the derived value, which is how a projection that paints what it
|
|
28
|
+
* derived - comment ranges over the text they were written for - stays one plugin. `this` in
|
|
29
|
+
* such a prop is the plugin, so it reads the value back with `this.getState(state)`.
|
|
30
|
+
*/
|
|
31
|
+
props?: PluginSpec<T>["props"]): DocumentProjection<T>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// src/editor/plugins/documentProjection.ts
|
|
2
|
+
import {
|
|
3
|
+
Plugin,
|
|
4
|
+
PluginKey
|
|
5
|
+
} from "prosemirror-state";
|
|
6
|
+
function documentProjection(name, derive, props) {
|
|
7
|
+
const key = new PluginKey(name);
|
|
8
|
+
return {
|
|
9
|
+
plugin: new Plugin({
|
|
10
|
+
key,
|
|
11
|
+
state: {
|
|
12
|
+
init: (_config, state) => derive(state.doc),
|
|
13
|
+
apply: (tr, held) => tr.docChanged ? derive(tr.doc) : held
|
|
14
|
+
},
|
|
15
|
+
props
|
|
16
|
+
}),
|
|
17
|
+
read: (state) => key.getState(state) ?? derive(state.doc)
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export {
|
|
21
|
+
documentProjection
|
|
22
|
+
};
|
|
@@ -3,12 +3,14 @@ import { isHistoryTransaction } from "prosemirror-history";
|
|
|
3
3
|
import { Plugin, PluginKey } from "prosemirror-state";
|
|
4
4
|
import { Decoration, DecorationSet } from "prosemirror-view";
|
|
5
5
|
import { editShut, selectionIntents } from "../../schema/guards.js";
|
|
6
|
+
import { readHtmlSlice, withPastedContent } from "../clipboard/htmlReader.js";
|
|
6
7
|
import {
|
|
7
8
|
hasResolvableClipboardImages,
|
|
8
9
|
resolveClipboardImages
|
|
9
10
|
} from "../clipboard/images.js";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
11
|
+
import { plainTextPaste } from "../clipboard/parser.js";
|
|
12
|
+
import { readContextOf } from "../clipboard/readContext.js";
|
|
13
|
+
import { documentBodyWidthPx, sectionBodyHeightPx } from "../documentStyles.js";
|
|
12
14
|
import { insertPlainTextAt } from "../plainText.js";
|
|
13
15
|
var LOAD_TIMEOUT_MS = 15e3;
|
|
14
16
|
var imagePasteKey = new PluginKey("docxImagePaste");
|
|
@@ -87,15 +89,20 @@ function imagePaste() {
|
|
|
87
89
|
if (!range) return;
|
|
88
90
|
removeMarker(task);
|
|
89
91
|
if (task.view.isDestroyed) return;
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
92
|
+
const content = resolved === null || resolved.images.size === 0 ? null : readHtmlSlice(
|
|
93
|
+
readContextOf(
|
|
94
|
+
task.view.state,
|
|
95
|
+
task.view.dom.ownerDocument,
|
|
96
|
+
resolved.images
|
|
97
|
+
),
|
|
98
|
+
resolved.source
|
|
95
99
|
);
|
|
96
|
-
if (
|
|
100
|
+
if (content !== null) {
|
|
97
101
|
task.view.dispatch(
|
|
98
|
-
|
|
102
|
+
withPastedContent(
|
|
103
|
+
task.view.state.tr.replaceRange(range.from, range.to, content.slice),
|
|
104
|
+
content
|
|
105
|
+
).scrollIntoView()
|
|
99
106
|
);
|
|
100
107
|
} else {
|
|
101
108
|
insertPlainTextAt(task.view, task.text, range.from, range.to);
|
|
@@ -222,6 +229,7 @@ function imagePaste() {
|
|
|
222
229
|
props: {
|
|
223
230
|
decorations: (state) => imagePasteKey.getState(state)?.decorations,
|
|
224
231
|
handlePaste(view, event) {
|
|
232
|
+
if (plainTextPaste(view, event)) return false;
|
|
225
233
|
if (selectionIntents(view.state.selection, "replace").some(
|
|
226
234
|
(intent) => editShut(view.state, intent)
|
|
227
235
|
)) {
|
|
@@ -253,8 +261,10 @@ function imagePaste() {
|
|
|
253
261
|
view,
|
|
254
262
|
html,
|
|
255
263
|
text,
|
|
264
|
+
// One sheet is drawn at one width, the first section's, and a pasted image is fitted
|
|
265
|
+
// to it; how tall a page is, though, is the paper of the section it lands in
|
|
256
266
|
maxWidthPx: documentBodyWidthPx(view.state),
|
|
257
|
-
maxHeightPx:
|
|
267
|
+
maxHeightPx: sectionBodyHeightPx(view.state, from),
|
|
258
268
|
canceled: false,
|
|
259
269
|
controller: null
|
|
260
270
|
});
|
|
@@ -9,15 +9,19 @@
|
|
|
9
9
|
* It closes on the document changing, which is what applying a link or taking one off does: the
|
|
10
10
|
* panel is about the selection it opened over, and after an edit that selection has been answered.
|
|
11
11
|
*/
|
|
12
|
-
import {
|
|
12
|
+
import type { Command, EditorState, Plugin } from "prosemirror-state";
|
|
13
|
+
/** The panel is about the selection it opened over, so standing open is all there is to hold */
|
|
14
|
+
declare const OPEN = true;
|
|
13
15
|
/** Whether the link panel is open */
|
|
14
16
|
export declare function isLinkPanelOpen(state: EditorState): boolean;
|
|
15
17
|
/**
|
|
16
18
|
* Opens the panel. It reports false where a link could not go on anyway - a caret standing in no
|
|
17
19
|
* link, a selection a lock leaves nothing of - so that Cmd+K there is a key this editor did not
|
|
18
|
-
* take, and a control drawn from it is drawn dead.
|
|
20
|
+
* take, and a control drawn from it is drawn dead. A panel already open is one more such place:
|
|
21
|
+
* the button that opened it has nothing left to do while it stands.
|
|
19
22
|
*/
|
|
20
23
|
export declare const openLinkPanel: Command;
|
|
21
24
|
/** Closes it. Called on applying, on Escape, and on a click outside */
|
|
22
25
|
export declare const closeLinkPanel: Command;
|
|
23
|
-
export declare function linkPanel(): Plugin<
|
|
26
|
+
export declare function linkPanel(): Plugin<typeof OPEN | null>;
|
|
27
|
+
export {};
|
|
@@ -1,35 +1,21 @@
|
|
|
1
1
|
// src/editor/plugins/linkPanel.ts
|
|
2
|
-
import {
|
|
3
|
-
Plugin,
|
|
4
|
-
PluginKey
|
|
5
|
-
} from "prosemirror-state";
|
|
6
2
|
import { canSetLink } from "../commands/linkCommands.js";
|
|
7
|
-
|
|
3
|
+
import { panelPlugin } from "./panelState.js";
|
|
4
|
+
var OPEN = true;
|
|
5
|
+
var panel = panelPlugin({
|
|
6
|
+
name: "docxEditorLinkPanel",
|
|
7
|
+
isAnchor: (value) => value === OPEN,
|
|
8
|
+
// An edit answers the panel: the link went on, came off, or the text it was about moved
|
|
9
|
+
onDocChange: "close",
|
|
10
|
+
canOpen: (state) => canSetLink(state)
|
|
11
|
+
});
|
|
8
12
|
function isLinkPanelOpen(state) {
|
|
9
|
-
return
|
|
13
|
+
return panel.anchor(state) === OPEN;
|
|
10
14
|
}
|
|
11
|
-
var openLinkPanel = (state, dispatch) =>
|
|
12
|
-
|
|
13
|
-
if (dispatch) dispatch(state.tr.setMeta(panelKey, true));
|
|
14
|
-
return true;
|
|
15
|
-
};
|
|
16
|
-
var closeLinkPanel = (state, dispatch) => {
|
|
17
|
-
if (!isLinkPanelOpen(state)) return false;
|
|
18
|
-
if (dispatch) dispatch(state.tr.setMeta(panelKey, false));
|
|
19
|
-
return true;
|
|
20
|
-
};
|
|
15
|
+
var openLinkPanel = (state, dispatch) => !isLinkPanelOpen(state) && panel.open(OPEN)(state, dispatch);
|
|
16
|
+
var closeLinkPanel = panel.close;
|
|
21
17
|
function linkPanel() {
|
|
22
|
-
return
|
|
23
|
-
key: panelKey,
|
|
24
|
-
state: {
|
|
25
|
-
init: () => false,
|
|
26
|
-
apply: (tr, open) => {
|
|
27
|
-
const meta = tr.getMeta(panelKey);
|
|
28
|
-
if (typeof meta === "boolean") return meta;
|
|
29
|
-
return tr.docChanged ? false : open;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
});
|
|
18
|
+
return panel.plugin;
|
|
33
19
|
}
|
|
34
20
|
export {
|
|
35
21
|
closeLinkPanel,
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
import type { Node as PMNode } from "prosemirror-model";
|
|
7
7
|
import { type EditorState, Plugin } from "prosemirror-state";
|
|
8
8
|
import { DecorationSet } from "prosemirror-view";
|
|
9
|
-
import type
|
|
9
|
+
import { type RunFormat } from "../../model/format";
|
|
10
|
+
import type { LevelAlign, LevelSuffix, Numbering } from "../../numbering/parseNumbering";
|
|
10
11
|
/** A number to draw on screen and the paragraph position it attaches to */
|
|
11
12
|
export interface PlacedMarker {
|
|
12
13
|
from: number;
|
|
@@ -19,6 +20,12 @@ export interface PlacedMarker {
|
|
|
19
20
|
indentEndPt: number | null;
|
|
20
21
|
/** The first-line indentation, under the same rule */
|
|
21
22
|
textIndentPt: number | null;
|
|
23
|
+
/** What the level puts between the number and the text (`w:suff`) */
|
|
24
|
+
suffix: LevelSuffix;
|
|
25
|
+
/** Where the number sits inside the width kept for it (`w:lvlJc`) */
|
|
26
|
+
align: LevelAlign;
|
|
27
|
+
/** The formatting the level puts on the number alone (`lvl/rPr`) */
|
|
28
|
+
run: RunFormat | null;
|
|
22
29
|
}
|
|
23
30
|
/** Walks a document to find the paragraphs that get numbers and what those numbers are */
|
|
24
31
|
export declare function paragraphMarkers(doc: PMNode, numbering: Numbering): PlacedMarker[];
|
|
@@ -27,9 +34,8 @@ export declare function markerDecorations(doc: PMNode, numbering: Numbering): De
|
|
|
27
34
|
export declare function documentNumbering(state: EditorState): Numbering;
|
|
28
35
|
/**
|
|
29
36
|
* Whether a new list can be started in this document.
|
|
30
|
-
* A document
|
|
31
|
-
*
|
|
32
|
-
* in the first place.
|
|
37
|
+
* A document with nowhere to write a new definition would be blocked at export time, so in such
|
|
38
|
+
* a document the commands that start a list do not apply in the first place.
|
|
33
39
|
*/
|
|
34
40
|
export declare function canStartNewList(state: EditorState): boolean;
|
|
35
41
|
/** Draws the numbers this document's lists put in front of their paragraphs */
|