@portone/docx-editor 0.2.0 → 0.2.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.
- package/CHANGELOG.md +65 -0
- package/CONTRIBUTING.md +4 -0
- package/dist/docx/commentOnlyChange.d.ts +3 -1
- package/dist/docx/commentOnlyChange.js +38 -21
- package/dist/docx/comments/constants.d.ts +3 -3
- package/dist/docx/comments/constants.js +4 -3
- package/dist/docx/comments/grammar.d.ts +80 -0
- package/dist/docx/comments/grammar.js +213 -0
- package/dist/docx/comments/people.js +12 -14
- package/dist/docx/comments/reading.d.ts +5 -3
- package/dist/docx/comments/reading.js +23 -20
- package/dist/docx/comments/verifying.d.ts +44 -0
- package/dist/docx/comments/verifying.js +206 -0
- package/dist/docx/comments/writing.js +73 -46
- package/dist/docx/exportDocx.js +2 -1
- package/dist/docx/formatting/direct.js +3 -6
- package/dist/docx/headersFooters.js +8 -10
- package/dist/docx/notes.js +3 -5
- package/dist/docx/paraProps.js +51 -46
- package/dist/docx/propsXml.d.ts +20 -2
- package/dist/docx/propsXml.js +72 -32
- package/dist/docx/relationships.js +8 -3
- package/dist/docx/runProps.js +34 -34
- package/dist/docx/sdt.js +16 -14
- package/dist/docx/serializeBlock.js +1 -2
- package/dist/docx/serializeParagraph.d.ts +2 -1
- package/dist/docx/serializeParagraph.js +25 -14
- package/dist/docx/serializeTable.js +38 -24
- package/dist/docx/storyProjection.d.ts +36 -0
- package/dist/docx/storyProjection.js +23 -0
- package/dist/docx/tableFormatting/editing.js +49 -73
- package/dist/docx/tableTemplate.js +24 -3
- package/dist/docx/theme.d.ts +0 -6
- package/dist/docx/theme.js +0 -8
- package/dist/editor/clipboard/images.js +17 -3
- package/dist/editor/clipboard/inlineFormatting.d.ts +7 -0
- package/dist/editor/clipboard/inlineFormatting.js +1 -0
- package/dist/editor/commands/breakCommands.js +4 -3
- package/dist/editor/commands/canRunCommand.d.ts +2 -2
- package/dist/editor/commands/canRunCommand.js +1 -1
- package/dist/editor/commands/comments/editing.js +3 -6
- package/dist/editor/commands/historyCommands.js +2 -1
- package/dist/editor/commands/lockCommands.js +10 -1
- package/dist/editor/commands/tabCommands.js +4 -3
- package/dist/editor/createEditor.js +3 -9
- package/dist/editor/externalClipboard.js +166 -4
- package/dist/editor/insertImage.js +4 -3
- package/dist/editor/plugins/documentProtection.d.ts +3 -3
- package/dist/editor/plugins/lockedContent.d.ts +4 -4
- package/dist/editor/plugins/lockedContent.js +1 -1
- package/dist/numbering/listTemplate.js +32 -10
- package/dist/ooxml/element.d.ts +52 -0
- package/dist/ooxml/element.js +49 -0
- package/dist/ooxml/fragment.d.ts +53 -0
- package/dist/ooxml/fragment.js +76 -0
- package/dist/ooxml/image.js +4 -3
- package/dist/ooxml/names.d.ts +27 -0
- package/dist/ooxml/names.js +29 -0
- package/dist/ooxml/precedence.d.ts +31 -0
- package/dist/ooxml/precedence.js +51 -0
- package/dist/ooxml/xml.d.ts +31 -0
- package/dist/ooxml/xml.js +27 -0
- package/dist/page/blockKinds.d.ts +42 -0
- package/dist/page/blockKinds.js +0 -0
- package/dist/page/measureBlocks.d.ts +14 -1
- package/dist/page/measureBlocks.js +31 -11
- package/dist/page/pageDecorations.d.ts +16 -0
- package/dist/page/pageDecorations.js +1 -0
- package/dist/page/pageLayout.d.ts +8 -43
- package/dist/page/pageLayout.js +28 -51
- package/dist/page/tableMeasurements.d.ts +10 -2
- package/dist/page/tableMeasurements.js +20 -16
- package/dist/page/usePageLayout.d.ts +15 -0
- package/dist/page/usePageLayout.js +30 -2
- package/dist/schema/attrRoles.d.ts +35 -0
- package/dist/schema/attrRoles.js +106 -0
- package/dist/schema/docxSchema.d.ts +6 -0
- package/dist/schema/docxSchema.js +212 -101
- package/dist/schema/editGuard.d.ts +99 -0
- package/dist/schema/editGuard.js +43 -0
- package/dist/schema/guards.d.ts +68 -0
- package/dist/schema/guards.js +82 -0
- package/dist/schema/locks.d.ts +8 -35
- package/dist/schema/locks.js +29 -64
- package/dist/schema/preservedGuards.d.ts +30 -0
- package/dist/schema/preservedGuards.js +50 -0
- package/dist/schema/protection.d.ts +10 -3
- package/dist/schema/protection.js +2 -1
- package/dist/schema/sourceEquality.d.ts +27 -0
- package/dist/schema/sourceEquality.js +65 -0
- package/dist/table/cellFormatting.js +2 -1
- package/dist/table/commands.d.ts +2 -2
- package/dist/table/commands.js +1 -1
- package/dist/table/index.d.ts +2 -2
- package/dist/table/merge.d.ts +2 -2
- package/dist/table/merge.js +1 -1
- package/package.json +4 -1
- package/dist/editor/plugins/bookmarkProtection.d.ts +0 -3
- package/dist/editor/plugins/bookmarkProtection.js +0 -28
- package/dist/editor/plugins/noteProtection.d.ts +0 -3
- package/dist/editor/plugins/noteProtection.js +0 -25
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one list an edit is judged against.
|
|
3
|
+
*
|
|
4
|
+
* A guard answers, for one rule, whether an edit would leave the document in a state the file
|
|
5
|
+
* cannot be written back from. `transactionAllowed` runs the list over a built transaction, both
|
|
6
|
+
* where an edit is dispatched (`editor/plugins/lockedContent`) and where a command is dry-run
|
|
7
|
+
* (`editor/commands/canRunCommand`), and `editShut` runs it over the intent a command has before
|
|
8
|
+
* it builds anything. Both ask the same list, which is what keeps a disabled control and a refused
|
|
9
|
+
* edit saying the same thing.
|
|
10
|
+
*
|
|
11
|
+
* What a guard is written against - `EditGuard`, `EditIntent` and the reach primitives - stands in
|
|
12
|
+
* `./editGuard` so that a module writing a guard need not read this one, and is handed on from
|
|
13
|
+
* here so that a caller has one door to the whole seam.
|
|
14
|
+
*
|
|
15
|
+
* `editShut` is not yet what every command asks. Only `editor/commands/breakCommands`,
|
|
16
|
+
* `editor/commands/tabCommands` and `editor/insertImage` ask it today; the rest still compose
|
|
17
|
+
* `editsShut` with a lock predicate of their own (`./locks`), and each decides for itself whether
|
|
18
|
+
* a stretch a guard shuts is trimmed out of the edit or refuses the whole of it. Until they move
|
|
19
|
+
* over, a guard added to the list below reaches those commands through `transactionAllowed` alone,
|
|
20
|
+
* which refuses the transaction they built rather than telling them not to build it.
|
|
21
|
+
*/
|
|
22
|
+
import type { EditorState, Selection, Transaction } from "prosemirror-state";
|
|
23
|
+
import { type EditGuard, type EditIntent } from "./editGuard";
|
|
24
|
+
import { type ProtectionState } from "./protection";
|
|
25
|
+
export type { EditGuard, EditGuardName, EditIntent } from "./editGuard";
|
|
26
|
+
export { stepReaches } from "./editGuard";
|
|
27
|
+
/**
|
|
28
|
+
* Whether the transaction reaches a comment node, which is how a body, a reply and a resolution
|
|
29
|
+
* change. Every comment lives in its three nodes, so a change that reaches none of them cannot
|
|
30
|
+
* have changed a comment.
|
|
31
|
+
*/
|
|
32
|
+
export declare function transactionTouchesComments(tr: Transaction): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Whether the protection lets this transaction through (`./protection`).
|
|
35
|
+
*
|
|
36
|
+
* The whole-document judgement is reached for only when a comment is touched at all
|
|
37
|
+
* (`transactionTouchesComments`). A change that touches none is a body edit: through under `none`,
|
|
38
|
+
* refused under `comments`, and nothing about ownership to ask.
|
|
39
|
+
*/
|
|
40
|
+
export declare function protectionAllowsTransaction(tr: Transaction, rules: ProtectionState): boolean;
|
|
41
|
+
export declare const EDIT_GUARDS: readonly EditGuard[];
|
|
42
|
+
/**
|
|
43
|
+
* Whether every guard would let this transaction through, decided and nothing else.
|
|
44
|
+
*
|
|
45
|
+
* The refusal a guard itself answers with carries a side effect - the composition it ends
|
|
46
|
+
* (`editor/plugins/lockedContent`) - which a query about a button's state may not set off, so the
|
|
47
|
+
* decision stands apart from it and every caller building an edit asks this rather than handing
|
|
48
|
+
* the transaction to a state.
|
|
49
|
+
*
|
|
50
|
+
* The whole-change judgements come first and take no pass, since a pass lifts one guard's reading
|
|
51
|
+
* of a step rather than another guard's reading of the change. What is left is judged step by
|
|
52
|
+
* step, each step over the document it was built against.
|
|
53
|
+
*/
|
|
54
|
+
export declare function transactionAllowed(tr: Transaction, state: EditorState): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Whether any guard shuts this intent where it stands.
|
|
57
|
+
*
|
|
58
|
+
* This is what a command asks before it reports that it applies: a command reporting true and then
|
|
59
|
+
* being refused by the guard draws a live control that swallows the click.
|
|
60
|
+
*/
|
|
61
|
+
export declare function editShut(state: EditorState, intent: EditIntent): boolean;
|
|
62
|
+
/**
|
|
63
|
+
* What a command doing this to whatever is selected means to do, one intent per selected stretch.
|
|
64
|
+
*
|
|
65
|
+
* A stretch of no length holds nothing to mark or to put away, so whatever the command would do to
|
|
66
|
+
* a stretch it is an insertion there.
|
|
67
|
+
*/
|
|
68
|
+
export declare function selectionIntents(selection: Selection, kind: "mark" | "replace"): EditIntent[];
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// src/schema/guards.ts
|
|
2
|
+
import {
|
|
3
|
+
transactionReaches
|
|
4
|
+
} from "./editGuard.js";
|
|
5
|
+
import { lockGuard } from "./locks.js";
|
|
6
|
+
import { bookmarkGuard, noteGuard } from "./preservedGuards.js";
|
|
7
|
+
import {
|
|
8
|
+
isCommentNode,
|
|
9
|
+
protectionAllows
|
|
10
|
+
} from "./protection.js";
|
|
11
|
+
import { editsShut, protectionOf } from "./protectionState.js";
|
|
12
|
+
import { stepReaches } from "./editGuard.js";
|
|
13
|
+
function transactionTouchesComments(tr) {
|
|
14
|
+
return transactionReaches(tr, isCommentNode);
|
|
15
|
+
}
|
|
16
|
+
function protectionAllowsTransaction(tr, rules) {
|
|
17
|
+
switch (rules.protection) {
|
|
18
|
+
case "readOnly":
|
|
19
|
+
return false;
|
|
20
|
+
case "none":
|
|
21
|
+
return !transactionTouchesComments(tr) || protectionAllows(tr.before, tr.doc, rules);
|
|
22
|
+
case "comments":
|
|
23
|
+
return transactionTouchesComments(tr) && protectionAllows(tr.before, tr.doc, rules);
|
|
24
|
+
default: {
|
|
25
|
+
const unmodelled = rules.protection;
|
|
26
|
+
return unmodelled;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
var protectionGuard = {
|
|
31
|
+
name: "protection",
|
|
32
|
+
change: (tr, state) => protectionAllowsTransaction(tr, protectionOf(state)),
|
|
33
|
+
shuts: (_intent, state) => editsShut(state)
|
|
34
|
+
};
|
|
35
|
+
var EDIT_GUARDS = [
|
|
36
|
+
protectionGuard,
|
|
37
|
+
lockGuard,
|
|
38
|
+
bookmarkGuard,
|
|
39
|
+
noteGuard
|
|
40
|
+
];
|
|
41
|
+
function judgesSteps(guard) {
|
|
42
|
+
return guard.step !== void 0;
|
|
43
|
+
}
|
|
44
|
+
function lifted(guard, tr) {
|
|
45
|
+
return guard.liftedBy?.some((pass) => tr.getMeta(pass) === true) === true;
|
|
46
|
+
}
|
|
47
|
+
function transactionAllowed(tr, state) {
|
|
48
|
+
if (!tr.docChanged) return true;
|
|
49
|
+
if (EDIT_GUARDS.some((guard) => guard.change?.(tr, state) === false)) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
return EDIT_GUARDS.filter(judgesSteps).filter((guard) => !lifted(guard, tr)).every(
|
|
53
|
+
(guard) => (
|
|
54
|
+
// Each step counts positions in the document it was built against, which `docs` holds
|
|
55
|
+
tr.steps.every(
|
|
56
|
+
(step, index) => guard.step(
|
|
57
|
+
step,
|
|
58
|
+
tr.docs[index] ?? state.doc,
|
|
59
|
+
tr.docs[index + 1] ?? tr.doc,
|
|
60
|
+
state
|
|
61
|
+
)
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
function editShut(state, intent) {
|
|
67
|
+
return EDIT_GUARDS.some((guard) => guard.shuts(intent, state));
|
|
68
|
+
}
|
|
69
|
+
function selectionIntents(selection, kind) {
|
|
70
|
+
return selection.ranges.map(
|
|
71
|
+
(range) => range.$from.pos === range.$to.pos ? { kind: "insert", at: range.$from.pos } : { kind, from: range.$from.pos, to: range.$to.pos }
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
export {
|
|
75
|
+
EDIT_GUARDS,
|
|
76
|
+
editShut,
|
|
77
|
+
protectionAllowsTransaction,
|
|
78
|
+
selectionIntents,
|
|
79
|
+
stepReaches,
|
|
80
|
+
transactionAllowed,
|
|
81
|
+
transactionTouchesComments
|
|
82
|
+
};
|
package/dist/schema/locks.d.ts
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
* Evaluates OOXML content and deletion locks for inline controls and whole table cells. Commands
|
|
3
3
|
* query these predicates before editing; `editor/plugins/lockedContent` enforces them at runtime.
|
|
4
4
|
*
|
|
5
|
-
* `
|
|
6
|
-
*
|
|
5
|
+
* `lockGuard` is what `./guards` registers all of this as, so that a caller asking the one guard
|
|
6
|
+
* list asks the locks along with every other rule an edit is judged by.
|
|
7
7
|
*/
|
|
8
8
|
import type { Mark, Node as PMNode } from "prosemirror-model";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
9
|
+
import { PluginKey, type Selection } from "prosemirror-state";
|
|
10
|
+
import type { StepGuard } from "./editGuard";
|
|
11
11
|
/**
|
|
12
12
|
* The pass that lets a transaction through the guard, which is how a lock can be lifted at all.
|
|
13
13
|
* A plugin key is used as the name so that it cannot collide with a consumer's own metadata.
|
|
@@ -104,37 +104,10 @@ export declare function selectionShut(selection: Selection, doc: PMNode): boolea
|
|
|
104
104
|
*/
|
|
105
105
|
export declare function replacementShut(selection: Selection, doc: PMNode): boolean;
|
|
106
106
|
/**
|
|
107
|
-
*
|
|
108
|
-
* rewrites the one where it stands, which is how a body, a reply and a resolution change.
|
|
107
|
+
* The locks the document carries, as `./guards` registers them.
|
|
109
108
|
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
* nothing more - over the stretch it rewrote rather than over the whole document.
|
|
113
|
-
*
|
|
114
|
-
* A step of a kind this does not know - one a consumer brought - is answered as reaching one,
|
|
115
|
-
* since what it rewrote is not known either. The whole-document judgement then has the say, and
|
|
116
|
-
* an unknown step costs a comparison rather than a hole in the guard.
|
|
117
|
-
*/
|
|
118
|
-
export declare function transactionTouchesComments(tr: Transaction): boolean;
|
|
119
|
-
/**
|
|
120
|
-
* Whether the protection lets this transaction through (`./protection`).
|
|
121
|
-
*
|
|
122
|
-
* The whole-document judgement is reached for only when a comment is touched at all
|
|
123
|
-
* (`transactionTouchesComments`). A change that touches none is a body edit: through under `none`,
|
|
124
|
-
* refused under `comments`, and nothing about ownership to ask.
|
|
125
|
-
*/
|
|
126
|
-
export declare function protectionAllowsTransaction(tr: Transaction, rules: ProtectionState): boolean;
|
|
127
|
-
/**
|
|
128
|
-
* Whether the guard would let this transaction through, decided and nothing else.
|
|
129
|
-
*
|
|
130
|
-
* The refusal the guard itself answers with carries a side effect - the composition it ends
|
|
131
|
-
* (`editor/plugins/lockedContent`) - which a query about a button's state may not set off, so the
|
|
132
|
-
* decision stands apart from it and every caller building an edit asks this rather than handing
|
|
133
|
-
* the transaction to a state.
|
|
134
|
-
*
|
|
135
|
-
* The protection is judged before the passes and without them: a replayed edit is still an edit,
|
|
136
|
-
* and a pass that lifts a lock lifts no protection. Its judgement is over the whole change rather
|
|
137
|
-
* than step by step, which is what lets it tell a comment from everything else.
|
|
109
|
+
* Both passes lift it: unlocking is the one edit that may reach into a lock, and every step the
|
|
110
|
+
* history replays is the reverse of a step that passed the guard when it was made.
|
|
138
111
|
*/
|
|
139
|
-
export declare
|
|
112
|
+
export declare const lockGuard: StepGuard;
|
|
140
113
|
export {};
|
package/dist/schema/locks.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
// src/schema/locks.ts
|
|
2
|
-
import {
|
|
3
|
-
PluginKey
|
|
4
|
-
} from "prosemirror-state";
|
|
2
|
+
import { PluginKey } from "prosemirror-state";
|
|
5
3
|
import {
|
|
6
4
|
AddMarkStep,
|
|
7
5
|
AddNodeMarkStep,
|
|
@@ -12,11 +10,6 @@ import {
|
|
|
12
10
|
ReplaceStep
|
|
13
11
|
} from "prosemirror-transform";
|
|
14
12
|
import { docxSchema } from "./index.js";
|
|
15
|
-
import {
|
|
16
|
-
isCommentNode,
|
|
17
|
-
protectionAllows
|
|
18
|
-
} from "./protection.js";
|
|
19
|
-
import { protectionOf } from "./protectionState.js";
|
|
20
13
|
var unlockAllowed = new PluginKey("docxEditorUnlockAllowed");
|
|
21
14
|
var historyReplay = new PluginKey("docxEditorHistoryReplay");
|
|
22
15
|
var OPEN = { contents: false, deletion: false };
|
|
@@ -123,17 +116,21 @@ function insertionInsideLocked(doc, pos) {
|
|
|
123
116
|
const after = lockedMarkOf($pos.nodeAfter);
|
|
124
117
|
return before !== null && after !== null && before.eq(after);
|
|
125
118
|
}
|
|
119
|
+
function markShut(doc, from, to) {
|
|
120
|
+
return from === to ? insertionInsideLocked(doc, from) : rangeTouchesLocked(doc, from, to);
|
|
121
|
+
}
|
|
122
|
+
function replaceShut(doc, from, to) {
|
|
123
|
+
return from === to ? insertionInsideLocked(doc, from) : rangeShut(doc, { from, to, takesAway: true });
|
|
124
|
+
}
|
|
126
125
|
function selectionShut(selection, doc) {
|
|
127
126
|
return selection.ranges.some(
|
|
128
|
-
(range) =>
|
|
127
|
+
(range) => markShut(doc, range.$from.pos, range.$to.pos)
|
|
129
128
|
);
|
|
130
129
|
}
|
|
131
130
|
function replacementShut(selection, doc) {
|
|
132
|
-
return selection.ranges.some(
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
return from === to ? insertionInsideLocked(doc, from) : rangeShut(doc, { from, to, takesAway: true });
|
|
136
|
-
});
|
|
131
|
+
return selection.ranges.some(
|
|
132
|
+
(range) => replaceShut(doc, range.$from.pos, range.$to.pos)
|
|
133
|
+
);
|
|
137
134
|
}
|
|
138
135
|
function editedRanges(step) {
|
|
139
136
|
if (step instanceof ReplaceAroundStep) {
|
|
@@ -192,58 +189,28 @@ function stepAllowed(step, doc) {
|
|
|
192
189
|
}
|
|
193
190
|
return true;
|
|
194
191
|
}
|
|
195
|
-
function
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
return found;
|
|
206
|
-
}
|
|
207
|
-
function transactionTouchesComments(tr) {
|
|
208
|
-
return tr.steps.some((step, index) => {
|
|
209
|
-
const before = tr.docs[index];
|
|
210
|
-
const after = tr.docs[index + 1] ?? tr.doc;
|
|
211
|
-
if (step instanceof AttrStep || step instanceof AddNodeMarkStep || step instanceof RemoveNodeMarkStep) {
|
|
212
|
-
const node = before.nodeAt(step.pos);
|
|
213
|
-
return node !== null && isCommentNode(node);
|
|
214
|
-
}
|
|
215
|
-
if (!(step instanceof ReplaceStep || step instanceof ReplaceAroundStep || step instanceof AddMarkStep || step instanceof RemoveMarkStep)) {
|
|
216
|
-
return true;
|
|
217
|
-
}
|
|
218
|
-
let touched = false;
|
|
219
|
-
step.getMap().forEach((oldStart, oldEnd, newStart, newEnd) => {
|
|
220
|
-
touched ||= rangeHoldsComment(before, oldStart, oldEnd) || rangeHoldsComment(after, newStart, newEnd);
|
|
221
|
-
});
|
|
222
|
-
return touched;
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
function protectionAllowsTransaction(tr, rules) {
|
|
226
|
-
switch (rules.protection) {
|
|
227
|
-
case "readOnly":
|
|
228
|
-
return false;
|
|
229
|
-
case "none":
|
|
230
|
-
return !transactionTouchesComments(tr) || protectionAllows(tr.before, tr.doc, rules);
|
|
231
|
-
case "comments":
|
|
232
|
-
return transactionTouchesComments(tr) && protectionAllows(tr.before, tr.doc, rules);
|
|
192
|
+
function intentShut(doc, intent) {
|
|
193
|
+
switch (intent.kind) {
|
|
194
|
+
case "insert":
|
|
195
|
+
return insertionInsideLocked(doc, intent.at);
|
|
196
|
+
case "block":
|
|
197
|
+
return insideLockedCell(doc, intent.at);
|
|
198
|
+
case "mark":
|
|
199
|
+
return markShut(doc, intent.from, intent.to);
|
|
200
|
+
case "replace":
|
|
201
|
+
return replaceShut(doc, intent.from, intent.to);
|
|
233
202
|
default: {
|
|
234
|
-
const unmodelled =
|
|
203
|
+
const unmodelled = intent;
|
|
235
204
|
return unmodelled;
|
|
236
205
|
}
|
|
237
206
|
}
|
|
238
207
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
);
|
|
246
|
-
}
|
|
208
|
+
var lockGuard = {
|
|
209
|
+
name: "lock",
|
|
210
|
+
liftedBy: [unlockAllowed, historyReplay],
|
|
211
|
+
step: (step, before) => stepAllowed(step, before),
|
|
212
|
+
shuts: (intent, state) => intentShut(state.doc, intent)
|
|
213
|
+
};
|
|
247
214
|
export {
|
|
248
215
|
carriesLock,
|
|
249
216
|
controlSpans,
|
|
@@ -251,12 +218,10 @@ export {
|
|
|
251
218
|
insertionInsideLocked,
|
|
252
219
|
insideLockedCell,
|
|
253
220
|
isLockedCell,
|
|
221
|
+
lockGuard,
|
|
254
222
|
lockedMarkOf,
|
|
255
|
-
protectionAllowsTransaction,
|
|
256
223
|
rangeTouchesLocked,
|
|
257
224
|
replacementShut,
|
|
258
225
|
selectionShut,
|
|
259
|
-
transactionAllowed,
|
|
260
|
-
transactionTouchesComments,
|
|
261
226
|
unlockAllowed
|
|
262
227
|
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The guards over the nodes a document is opened with and the editor only preserves: the two ends
|
|
3
|
+
* of a bookmark range, and the reference standing where a footnote or an endnote is called.
|
|
4
|
+
*
|
|
5
|
+
* Neither is content the editor writes, so nothing in it can put such a node back once it is gone,
|
|
6
|
+
* and a document that lost one of a bookmark's two ends cannot be written back as a file at all
|
|
7
|
+
* (`docx/exportDocx` refuses it). What each guard holds is therefore the whole list of the nodes it
|
|
8
|
+
* answers for, in the order the document carries them.
|
|
9
|
+
*/
|
|
10
|
+
import type { Node as PMNode } from "prosemirror-model";
|
|
11
|
+
import { type ChangeGuard, type EditGuardName } from "./editGuard";
|
|
12
|
+
/** Everything about one preserved node that has to read the same after a change as before it */
|
|
13
|
+
type Signature = (node: PMNode) => string;
|
|
14
|
+
/**
|
|
15
|
+
* A guard that lets a change through only while it leaves the list of these nodes reading exactly
|
|
16
|
+
* as it did, which holds their number, their contents and their order at once.
|
|
17
|
+
*
|
|
18
|
+
* The comparison walks the whole document, so it is reached for only once a step of the change has
|
|
19
|
+
* touched such a node: ordinary typing costs the stretches its own steps rewrote and nothing more.
|
|
20
|
+
* It is judged over the change rather than step by step because a marker moved whole is two steps,
|
|
21
|
+
* one taking it out and one putting it back, and neither of them alone leaves the list as it was.
|
|
22
|
+
*
|
|
23
|
+
* Nothing lifts it. A pass is how an edit reaches past a lock the document put on itself, and
|
|
24
|
+
* there is no such thing to reach past here: the file simply has no way to say what a lost marker
|
|
25
|
+
* was.
|
|
26
|
+
*/
|
|
27
|
+
export declare function preservedNodeGuard(name: EditGuardName, holds: (node: PMNode) => boolean, signature: Signature): ChangeGuard;
|
|
28
|
+
export declare const bookmarkGuard: ChangeGuard;
|
|
29
|
+
export declare const noteGuard: ChangeGuard;
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// src/schema/preservedGuards.ts
|
|
2
|
+
import {
|
|
3
|
+
rangeHolds,
|
|
4
|
+
transactionReaches
|
|
5
|
+
} from "./editGuard.js";
|
|
6
|
+
function signatures(doc, holds, signature) {
|
|
7
|
+
const found = [];
|
|
8
|
+
doc.descendants((node) => {
|
|
9
|
+
if (holds(node)) found.push(signature(node));
|
|
10
|
+
return true;
|
|
11
|
+
});
|
|
12
|
+
return found;
|
|
13
|
+
}
|
|
14
|
+
function same(a, b) {
|
|
15
|
+
return a.length === b.length && a.every((value, index) => value === b[index]);
|
|
16
|
+
}
|
|
17
|
+
function preservedNodeGuard(name, holds, signature) {
|
|
18
|
+
const listOf = (doc) => signatures(doc, holds, signature);
|
|
19
|
+
return {
|
|
20
|
+
name,
|
|
21
|
+
change: (tr) => !transactionReaches(tr, holds) || same(listOf(tr.before), listOf(tr.doc)),
|
|
22
|
+
shuts: (intent, state) => intent.kind === "replace" && rangeHolds(state.doc, intent.from, intent.to, holds)
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
var BOOKMARK_XML = /<(?:[\w.-]+:)?bookmark(?:Start|End)\b/;
|
|
26
|
+
function isBookmarkMarker(node) {
|
|
27
|
+
if (node.type.name === "bookmarkBlock") return true;
|
|
28
|
+
return node.type.name === "rawInline" && typeof node.attrs.xml === "string" && BOOKMARK_XML.test(node.attrs.xml);
|
|
29
|
+
}
|
|
30
|
+
function bookmarkSignature(node) {
|
|
31
|
+
return node.type.name === "bookmarkBlock" ? `block:${node.attrs.srcId}:${node.attrs.name}` : `inline:${node.attrs.xml}`;
|
|
32
|
+
}
|
|
33
|
+
var bookmarkGuard = preservedNodeGuard(
|
|
34
|
+
"bookmark",
|
|
35
|
+
isBookmarkMarker,
|
|
36
|
+
bookmarkSignature
|
|
37
|
+
);
|
|
38
|
+
function isNoteReference(node) {
|
|
39
|
+
return node.type.name === "noteReference";
|
|
40
|
+
}
|
|
41
|
+
var noteGuard = preservedNodeGuard(
|
|
42
|
+
"note",
|
|
43
|
+
isNoteReference,
|
|
44
|
+
(node) => JSON.stringify(node.attrs)
|
|
45
|
+
);
|
|
46
|
+
export {
|
|
47
|
+
bookmarkGuard,
|
|
48
|
+
noteGuard,
|
|
49
|
+
preservedNodeGuard
|
|
50
|
+
};
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
*
|
|
12
12
|
* The protection is editor state rather than document state: it is the reader's standing, not the
|
|
13
13
|
* file's, so nothing here is written to or read from OOXML. `editor/plugins/documentProtection`
|
|
14
|
-
* holds it in the state and `./protectionState` reads it back; the guard (`
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* holds it in the state and `./protectionState` reads it back; the guard list (`./guards`) asks the
|
|
15
|
+
* judgements here. They take documents alone, so that the `./core` entry can make the same
|
|
16
|
+
* judgement over a file without an editor.
|
|
17
17
|
*/
|
|
18
18
|
import { type Node as PMNode } from "prosemirror-model";
|
|
19
19
|
export type EditingProtection = "none" | "readOnly" | "comments";
|
|
@@ -31,6 +31,13 @@ export interface ProtectionState {
|
|
|
31
31
|
}
|
|
32
32
|
/** Whether this node is one of the three a comment stands in the story as */
|
|
33
33
|
export declare function isCommentNode(node: PMNode): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* The document with every comment taken out of it.
|
|
36
|
+
*
|
|
37
|
+
* `Fragment.fromArray` joins the text a marker had split, so a document a comment was put into
|
|
38
|
+
* reads the same as it did before the comment, and the two compare equal.
|
|
39
|
+
*/
|
|
40
|
+
export declare function withoutComments(node: PMNode): PMNode;
|
|
34
41
|
/** Whether the two documents differ in nothing but their comments */
|
|
35
42
|
export declare function changesOnlyComments(before: PMNode, after: PMNode): boolean;
|
|
36
43
|
/**
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether two nodes would be written back as the same XML.
|
|
3
|
+
*
|
|
4
|
+
* `Node.eq` answers a different question: whether they are the same node, display values and all.
|
|
5
|
+
* That is the right question inside an editing session, where both sides came from one import and
|
|
6
|
+
* a difference can only be an edit. It is the wrong one across a re-derivation, because the values
|
|
7
|
+
* worked out from the surroundings (`./attrRoles`) move without the document moving, and a block
|
|
8
|
+
* judged changed is a block rewritten, which costs it the markup the writer does not model.
|
|
9
|
+
*
|
|
10
|
+
* So the comparison is `Node.eq` with the display attrs left out, and nothing else left out: the
|
|
11
|
+
* type, the text, the marks and the source and session attrs all have to agree, and the recursion
|
|
12
|
+
* runs the same way, so a node that says the same thing in a different shape is still a change.
|
|
13
|
+
*/
|
|
14
|
+
import { type Node as PMNode } from "prosemirror-model";
|
|
15
|
+
/**
|
|
16
|
+
* Whether the two would write the same XML: the same type, text, marks and source attrs, all the
|
|
17
|
+
* way down. What the editor works out for the screen is not compared.
|
|
18
|
+
*/
|
|
19
|
+
export declare function sameSource(a: PMNode, b: PMNode): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* The node with every worked-out attr back at the value the schema starts it on, on itself and on
|
|
22
|
+
* everything inside it.
|
|
23
|
+
*
|
|
24
|
+
* `sameSource` needs no such copy, and takes none. This is for a caller that has to hand a whole
|
|
25
|
+
* document to something comparing the ordinary way.
|
|
26
|
+
*/
|
|
27
|
+
export declare function withoutDisplayAttrs(node: PMNode): PMNode;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// src/schema/sourceEquality.ts
|
|
2
|
+
import {
|
|
3
|
+
Fragment
|
|
4
|
+
} from "prosemirror-model";
|
|
5
|
+
import { displayAttrsOf } from "./attrRoles.js";
|
|
6
|
+
function sameValue(a, b) {
|
|
7
|
+
if (a === b) return true;
|
|
8
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
9
|
+
return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((item, at) => sameValue(item, b[at]));
|
|
10
|
+
}
|
|
11
|
+
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
const left = Object.entries(a);
|
|
15
|
+
return left.length === Object.keys(b).length && left.every(
|
|
16
|
+
([key, value]) => key in b && sameValue(value, b[key])
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
function sameAttrs(display, a, b) {
|
|
20
|
+
return Object.keys(a).filter((name) => !display.includes(name)).every((name) => sameValue(a[name], b[name]));
|
|
21
|
+
}
|
|
22
|
+
function sameMarks(a, b) {
|
|
23
|
+
return a.length === b.length && a.every((mark, at) => {
|
|
24
|
+
const other = b[at];
|
|
25
|
+
return other !== void 0 && mark.type === other.type && sameAttrs(displayAttrsOf(mark.type), mark.attrs, other.attrs);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
function sameSource(a, b) {
|
|
29
|
+
if (a === b) return true;
|
|
30
|
+
if (a.type !== b.type || !sameMarks(a.marks, b.marks) || !sameAttrs(displayAttrsOf(a.type), a.attrs, b.attrs)) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
if (a.isText) return a.text === b.text;
|
|
34
|
+
if (a.childCount !== b.childCount) return false;
|
|
35
|
+
for (let at = 0; at < a.childCount; at += 1) {
|
|
36
|
+
if (!sameSource(a.child(at), b.child(at))) return false;
|
|
37
|
+
}
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
function withoutDisplay(type, attrs) {
|
|
41
|
+
return Object.fromEntries(
|
|
42
|
+
Object.entries(attrs).map(
|
|
43
|
+
([name, value]) => displayAttrsOf(type).includes(name) ? [name, type.spec.attrs?.[name]?.default ?? null] : [name, value]
|
|
44
|
+
)
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
function withoutDisplayMarkAttrs(mark) {
|
|
48
|
+
return displayAttrsOf(mark.type).length === 0 ? mark : mark.type.create(withoutDisplay(mark.type, mark.attrs));
|
|
49
|
+
}
|
|
50
|
+
var childrenOf = (node) => Array.from({ length: node.childCount }, (_, at) => node.child(at));
|
|
51
|
+
function withoutDisplayAttrs(node) {
|
|
52
|
+
const marks = node.marks.map(withoutDisplayMarkAttrs);
|
|
53
|
+
if (node.isText) return node.mark(marks);
|
|
54
|
+
return node.type.create(
|
|
55
|
+
withoutDisplay(node.type, node.attrs),
|
|
56
|
+
Fragment.fromArray(
|
|
57
|
+
childrenOf(node).map((child) => withoutDisplayAttrs(child))
|
|
58
|
+
),
|
|
59
|
+
marks
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
export {
|
|
63
|
+
sameSource,
|
|
64
|
+
withoutDisplayAttrs
|
|
65
|
+
};
|
|
@@ -8,7 +8,8 @@ import {
|
|
|
8
8
|
NO_FILL,
|
|
9
9
|
toCellFormat
|
|
10
10
|
} from "../model/format.js";
|
|
11
|
-
import {
|
|
11
|
+
import { transactionAllowed } from "../schema/guards.js";
|
|
12
|
+
import { isLockedCell } from "../schema/locks.js";
|
|
12
13
|
import { editsShut } from "../schema/protectionState.js";
|
|
13
14
|
import { cellDefaultsAt, tableCellSources } from "./gridBorders.js";
|
|
14
15
|
function text(value) {
|
package/dist/table/commands.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Building a transaction and running it are kept apart.
|
|
3
3
|
* Following the ProseMirror convention, calling a command without `dispatch` only
|
|
4
|
-
* reports whether it can run right now, and that answer takes in
|
|
5
|
-
* (`schema/
|
|
4
|
+
* reports whether it can run right now, and that answer takes in every guard an edit
|
|
5
|
+
* is judged by (`schema/guards`), so no caller has to ask about them separately.
|
|
6
6
|
*/
|
|
7
7
|
import type { EditorState, Transaction } from "prosemirror-state";
|
|
8
8
|
export type TableCommand = (state: EditorState, dispatch?: (tr: Transaction) => void) => boolean;
|
package/dist/table/commands.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
deleteTable as pmDeleteTable,
|
|
4
4
|
isInTable as pmIsInTable
|
|
5
5
|
} from "prosemirror-tables";
|
|
6
|
-
import { transactionAllowed } from "../schema/
|
|
6
|
+
import { transactionAllowed } from "../schema/guards.js";
|
|
7
7
|
import {
|
|
8
8
|
buildAddColumnAfterTransaction,
|
|
9
9
|
buildAddColumnBeforeTransaction,
|
package/dist/table/index.d.ts
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Only what the toolbar needs is exported. Following the ProseMirror convention,
|
|
5
5
|
* calling a command without `dispatch` only reports whether it can run right now, and that answer
|
|
6
|
-
* takes in
|
|
7
|
-
* apply, rather than being refused after the click
|
|
6
|
+
* takes in every guard an edit is judged by (`schema/guards`): a structural edit that would carry a
|
|
7
|
+
* locked cell off reports that it does not apply, rather than being refused after the click.
|
|
8
8
|
*
|
|
9
9
|
* Everything here works on a table already in the document; putting a new one into the body is
|
|
10
10
|
* `insertTable` in `./commands`, which reads the page geometry the editor holds.
|
package/dist/table/merge.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type { EditorState, Transaction } from "prosemirror-state";
|
|
|
6
6
|
/**
|
|
7
7
|
* Whether several cells can be merged into one.
|
|
8
8
|
* The selection has to be a rectangle spanning more than one cell, no merged cell may stick out
|
|
9
|
-
* past that rectangle, and the
|
|
9
|
+
* past that rectangle, and the guards have to let the merge through: a locked cell may not be
|
|
10
10
|
* swallowed by another (`schema/locks`).
|
|
11
11
|
*
|
|
12
12
|
* The two queries are defined from the very transaction the commands run, so they cannot drift
|
|
@@ -16,7 +16,7 @@ import type { EditorState, Transaction } from "prosemirror-state";
|
|
|
16
16
|
export declare function canMergeCells(state: EditorState): boolean;
|
|
17
17
|
/**
|
|
18
18
|
* Whether a split is possible. The cursor has to sit inside a cell that is merged horizontally or
|
|
19
|
-
* vertically, and the
|
|
19
|
+
* vertically, and the guards have to let the split through: the cells a split makes would each
|
|
20
20
|
* carry the original's lock, which is a lock planted in places it was never put (`schema/locks`).
|
|
21
21
|
*/
|
|
22
22
|
export declare function canSplitCell(state: EditorState): boolean;
|
package/dist/table/merge.js
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
widthNumber,
|
|
12
12
|
withWidthNumber
|
|
13
13
|
} from "../model/format.js";
|
|
14
|
-
import { transactionAllowed } from "../schema/
|
|
14
|
+
import { transactionAllowed } from "../schema/guards.js";
|
|
15
15
|
import { inheritCellAttrs } from "./format.js";
|
|
16
16
|
import { cellWidthForGridCol, gridSpanWidth, tableGridCols } from "./widths.js";
|
|
17
17
|
function tableAfter(tr, tableStart) {
|