@prosekit/core 0.8.0 → 0.8.2
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/dist/editor-CjVyjJqw.d.ts +739 -0
- package/dist/editor-DbMrpnmL.js +1319 -0
- package/dist/prosekit-core-test.d.ts +33 -2
- package/dist/prosekit-core-test.js +94 -116
- package/dist/prosekit-core.d.ts +1451 -203
- package/dist/prosekit-core.js +1441 -1438
- package/package.json +21 -11
- package/dist/_tsup-dts-rollup.d.ts +0 -3063
- package/dist/chunk-B3WEP4DD.js +0 -1188
@@ -0,0 +1,1319 @@
|
|
1
|
+
import { AllSelection, EditorState, NodeSelection, Selection, TextSelection } from "@prosekit/pm/state";
|
2
|
+
import { DOMParser, DOMSerializer, Fragment, Mark, ProseMirrorNode, Schema, Slice } from "@prosekit/pm/model";
|
3
|
+
import { EditorView } from "@prosekit/pm/view";
|
4
|
+
import { isElementLike } from "@ocavue/utils";
|
5
|
+
import OrderedMap from "orderedmap";
|
6
|
+
import mapValues from "just-map-values";
|
7
|
+
|
8
|
+
//#region src/error.ts
|
9
|
+
/**
|
10
|
+
* Base class for all ProseKit errors.
|
11
|
+
*
|
12
|
+
* @internal
|
13
|
+
*/
|
14
|
+
var ProseKitError = class extends Error {};
|
15
|
+
/**
|
16
|
+
* @internal
|
17
|
+
*/
|
18
|
+
var EditorNotFoundError = class extends ProseKitError {
|
19
|
+
constructor() {
|
20
|
+
super("Unable to find editor. Pass it as an argument or call this function inside a ProseKit component.");
|
21
|
+
}
|
22
|
+
};
|
23
|
+
/**
|
24
|
+
* @internal
|
25
|
+
*/
|
26
|
+
var DOMDocumentNotFoundError = class extends ProseKitError {
|
27
|
+
constructor() {
|
28
|
+
super("Unable to find browser Document. When not in the browser environment, you need to pass a DOM Document.");
|
29
|
+
}
|
30
|
+
};
|
31
|
+
|
32
|
+
//#endregion
|
33
|
+
//#region src/utils/get-mark-type.ts
|
34
|
+
/**
|
35
|
+
* @internal
|
36
|
+
*/
|
37
|
+
function getMarkType(schema, type) {
|
38
|
+
if (typeof type === "string") {
|
39
|
+
const markType = schema.marks[type];
|
40
|
+
if (!markType) throw new ProseKitError(`Cannot find mark type "${type}"`);
|
41
|
+
return markType;
|
42
|
+
}
|
43
|
+
return type;
|
44
|
+
}
|
45
|
+
|
46
|
+
//#endregion
|
47
|
+
//#region src/utils/assert.ts
|
48
|
+
/**
|
49
|
+
* @internal
|
50
|
+
*/
|
51
|
+
function assert(condition, message = "Assertion failed") {
|
52
|
+
if (!condition) throw new ProseKitError(message);
|
53
|
+
}
|
54
|
+
|
55
|
+
//#endregion
|
56
|
+
//#region src/utils/get-node-type.ts
|
57
|
+
/**
|
58
|
+
* @internal
|
59
|
+
*/
|
60
|
+
function getNodeType(schema, type) {
|
61
|
+
if (typeof type === "string") {
|
62
|
+
const nodeType = schema.nodes[type];
|
63
|
+
if (!nodeType) throw new ProseKitError(`Cannot find ProseMirror node type "${type}"`);
|
64
|
+
return nodeType;
|
65
|
+
}
|
66
|
+
return type;
|
67
|
+
}
|
68
|
+
|
69
|
+
//#endregion
|
70
|
+
//#region src/utils/attrs-match.ts
|
71
|
+
function attrsMatch(nodeOrMark, attrs) {
|
72
|
+
const currentAttrs = nodeOrMark.attrs;
|
73
|
+
for (const [key, value] of Object.entries(attrs)) if (currentAttrs[key] !== value) return false;
|
74
|
+
return true;
|
75
|
+
}
|
76
|
+
|
77
|
+
//#endregion
|
78
|
+
//#region src/utils/is-node-active.ts
|
79
|
+
function isNodeActive(state, type, attrs) {
|
80
|
+
const $pos = state.selection.$from;
|
81
|
+
const nodeType = getNodeType(state.schema, type);
|
82
|
+
for (let depth = $pos.depth; depth >= 0; depth--) {
|
83
|
+
const node = $pos.node(depth);
|
84
|
+
if (node.type === nodeType && (!attrs || attrsMatch(node, attrs))) return true;
|
85
|
+
}
|
86
|
+
return false;
|
87
|
+
}
|
88
|
+
|
89
|
+
//#endregion
|
90
|
+
//#region src/types/priority.ts
|
91
|
+
/**
|
92
|
+
* ProseKit extension priority.
|
93
|
+
*
|
94
|
+
* @public
|
95
|
+
*/
|
96
|
+
let Priority = /* @__PURE__ */ function(Priority$1) {
|
97
|
+
Priority$1[Priority$1["lowest"] = 0] = "lowest";
|
98
|
+
Priority$1[Priority$1["low"] = 1] = "low";
|
99
|
+
Priority$1[Priority$1["default"] = 2] = "default";
|
100
|
+
Priority$1[Priority$1["high"] = 3] = "high";
|
101
|
+
Priority$1[Priority$1["highest"] = 4] = "highest";
|
102
|
+
return Priority$1;
|
103
|
+
}({});
|
104
|
+
|
105
|
+
//#endregion
|
106
|
+
//#region src/facets/facet.ts
|
107
|
+
let facetCount = 0;
|
108
|
+
/**
|
109
|
+
* @internal
|
110
|
+
*/
|
111
|
+
var Facet = class {
|
112
|
+
/**
|
113
|
+
* @internal
|
114
|
+
*/
|
115
|
+
index = facetCount++;
|
116
|
+
/**
|
117
|
+
* @internal
|
118
|
+
*/
|
119
|
+
parent;
|
120
|
+
/**
|
121
|
+
* @internal
|
122
|
+
*/
|
123
|
+
singleton;
|
124
|
+
/**
|
125
|
+
* A index path to retrieve the current facet in a tree from the root.
|
126
|
+
*
|
127
|
+
* @internal
|
128
|
+
*/
|
129
|
+
path;
|
130
|
+
/**
|
131
|
+
* @internal
|
132
|
+
*/
|
133
|
+
constructor(parent, singleton, _reducer, _reduce) {
|
134
|
+
this._reducer = _reducer;
|
135
|
+
this._reduce = _reduce;
|
136
|
+
assert((_reduce || _reducer) && !(_reduce && _reducer));
|
137
|
+
this.parent = parent;
|
138
|
+
this.singleton = singleton;
|
139
|
+
this.path = parent ? [...parent.path, this.index] : [];
|
140
|
+
}
|
141
|
+
get reducer() {
|
142
|
+
return this._reducer ?? this._reduce?.();
|
143
|
+
}
|
144
|
+
};
|
145
|
+
/**
|
146
|
+
* @internal
|
147
|
+
*/
|
148
|
+
function defineFacet(options) {
|
149
|
+
return new Facet(options.parent, options.singleton ?? false, options.reducer, options.reduce);
|
150
|
+
}
|
151
|
+
|
152
|
+
//#endregion
|
153
|
+
//#region src/facets/root.ts
|
154
|
+
function rootReducer(inputs) {
|
155
|
+
let schema;
|
156
|
+
let commands;
|
157
|
+
let stateFunc;
|
158
|
+
let view;
|
159
|
+
for (const input of inputs) {
|
160
|
+
schema = input.schema || schema;
|
161
|
+
commands = input.commands || commands;
|
162
|
+
stateFunc = input.state || stateFunc;
|
163
|
+
view = input.view || view;
|
164
|
+
}
|
165
|
+
const state = schema && (stateFunc?.({ schema }) ?? { schema });
|
166
|
+
return {
|
167
|
+
schema,
|
168
|
+
state,
|
169
|
+
commands,
|
170
|
+
view
|
171
|
+
};
|
172
|
+
}
|
173
|
+
const rootFacet = new Facet(null, true, rootReducer);
|
174
|
+
|
175
|
+
//#endregion
|
176
|
+
//#region src/facets/schema.ts
|
177
|
+
const schemaFacet = defineFacet({
|
178
|
+
reducer: (specs) => {
|
179
|
+
assert(specs.length <= 1);
|
180
|
+
const spec = specs[0];
|
181
|
+
const schema = spec ? new Schema(spec) : null;
|
182
|
+
return { schema };
|
183
|
+
},
|
184
|
+
parent: rootFacet,
|
185
|
+
singleton: true
|
186
|
+
});
|
187
|
+
|
188
|
+
//#endregion
|
189
|
+
//#region src/facets/base-extension.ts
|
190
|
+
/**
|
191
|
+
* @internal
|
192
|
+
*/
|
193
|
+
var BaseExtension = class {
|
194
|
+
priority;
|
195
|
+
_type;
|
196
|
+
trees = [
|
197
|
+
null,
|
198
|
+
null,
|
199
|
+
null,
|
200
|
+
null,
|
201
|
+
null
|
202
|
+
];
|
203
|
+
/**
|
204
|
+
* @internal
|
205
|
+
*/
|
206
|
+
getTree(priority) {
|
207
|
+
const pri = priority ?? this.priority ?? Priority.default;
|
208
|
+
return this.trees[pri] ||= this.createTree(pri);
|
209
|
+
}
|
210
|
+
/**
|
211
|
+
* @internal
|
212
|
+
*/
|
213
|
+
findFacetOutput(facet) {
|
214
|
+
let node = this.getTree();
|
215
|
+
for (const index of facet.path) node = node?.children.get(index);
|
216
|
+
return node?.getOutput() ?? null;
|
217
|
+
}
|
218
|
+
get schema() {
|
219
|
+
const output = this.findFacetOutput(schemaFacet);
|
220
|
+
return output?.find(Boolean)?.schema ?? null;
|
221
|
+
}
|
222
|
+
};
|
223
|
+
|
224
|
+
//#endregion
|
225
|
+
//#region src/utils/array.ts
|
226
|
+
function uniqPush(prev, next) {
|
227
|
+
const result = [...prev];
|
228
|
+
for (const item of next) if (!result.includes(item)) result.push(item);
|
229
|
+
return result;
|
230
|
+
}
|
231
|
+
/**
|
232
|
+
* @internal
|
233
|
+
*/
|
234
|
+
function arraySubtract(a, b) {
|
235
|
+
return a.filter((x) => !b.includes(x));
|
236
|
+
}
|
237
|
+
function toReversed(arr) {
|
238
|
+
return arr.toReversed?.() ?? [...arr].reverse();
|
239
|
+
}
|
240
|
+
|
241
|
+
//#endregion
|
242
|
+
//#region src/utils/type-assertion.ts
|
243
|
+
/**
|
244
|
+
* Checks if the given object is a `ProseMirrorNode` instance.
|
245
|
+
*/
|
246
|
+
function isProseMirrorNode(node) {
|
247
|
+
return node instanceof ProseMirrorNode;
|
248
|
+
}
|
249
|
+
/**
|
250
|
+
* Checks if the given object is a `Mark` instance.
|
251
|
+
*
|
252
|
+
* @public
|
253
|
+
*/
|
254
|
+
function isMark(mark) {
|
255
|
+
return mark instanceof Mark;
|
256
|
+
}
|
257
|
+
/**
|
258
|
+
* Checks if the given object is a `Fragment` instance.
|
259
|
+
*
|
260
|
+
* @public
|
261
|
+
*/
|
262
|
+
function isFragment(fragment) {
|
263
|
+
return fragment instanceof Fragment;
|
264
|
+
}
|
265
|
+
/**
|
266
|
+
* Checks if the given object is a `Slice` instance.
|
267
|
+
*
|
268
|
+
* @public
|
269
|
+
*/
|
270
|
+
function isSlice(slice) {
|
271
|
+
return slice instanceof Slice;
|
272
|
+
}
|
273
|
+
/**
|
274
|
+
* Checks if the given object is a `Selection` instance.
|
275
|
+
*
|
276
|
+
* @public
|
277
|
+
*/
|
278
|
+
function isSelection(sel) {
|
279
|
+
return sel instanceof Selection;
|
280
|
+
}
|
281
|
+
/**
|
282
|
+
* Checks if the given object is a `TextSelection` instance.
|
283
|
+
*
|
284
|
+
* @public
|
285
|
+
*/
|
286
|
+
function isTextSelection(sel) {
|
287
|
+
return sel instanceof TextSelection;
|
288
|
+
}
|
289
|
+
/**
|
290
|
+
* Checks if the given object is a `NodeSelection` instance.
|
291
|
+
*
|
292
|
+
* @public
|
293
|
+
*/
|
294
|
+
function isNodeSelection(sel) {
|
295
|
+
return sel instanceof NodeSelection;
|
296
|
+
}
|
297
|
+
/**
|
298
|
+
* Checks if the given object is a `AllSelection` instance.
|
299
|
+
*
|
300
|
+
* @public
|
301
|
+
*/
|
302
|
+
function isAllSelection(sel) {
|
303
|
+
return sel instanceof AllSelection;
|
304
|
+
}
|
305
|
+
/**
|
306
|
+
* @internal
|
307
|
+
*/
|
308
|
+
function isNotNullish(value) {
|
309
|
+
return value != null;
|
310
|
+
}
|
311
|
+
|
312
|
+
//#endregion
|
313
|
+
//#region src/facets/facet-node.ts
|
314
|
+
function zip5(a, b, mapper) {
|
315
|
+
return [
|
316
|
+
mapper(a[0], b[0]),
|
317
|
+
mapper(a[1], b[1]),
|
318
|
+
mapper(a[2], b[2]),
|
319
|
+
mapper(a[3], b[3]),
|
320
|
+
mapper(a[4], b[4])
|
321
|
+
];
|
322
|
+
}
|
323
|
+
function unionInput(a, b) {
|
324
|
+
if (!a && !b) return null;
|
325
|
+
return uniqPush(a ?? [], b ?? []);
|
326
|
+
}
|
327
|
+
function subtractInput(a, b) {
|
328
|
+
if (!a) return null;
|
329
|
+
if (!b) return [...a];
|
330
|
+
return arraySubtract(a, b);
|
331
|
+
}
|
332
|
+
function unionChildren(a, b) {
|
333
|
+
const merged = new Map(a);
|
334
|
+
for (const [key, valueB] of b.entries()) {
|
335
|
+
const valueA = a.get(key);
|
336
|
+
merged.set(key, valueA ? unionFacetNode(valueA, valueB) : valueB);
|
337
|
+
}
|
338
|
+
return merged;
|
339
|
+
}
|
340
|
+
function subtractChildren(a, b) {
|
341
|
+
const merged = new Map(a);
|
342
|
+
for (const [key, valueB] of b.entries()) {
|
343
|
+
const valueA = a.get(key);
|
344
|
+
if (valueA) merged.set(key, subtractFacetNode(valueA, valueB));
|
345
|
+
}
|
346
|
+
return merged;
|
347
|
+
}
|
348
|
+
/**
|
349
|
+
* Takes two facet nodes and returns a new facet node containing inputs and
|
350
|
+
* children from both nodes.
|
351
|
+
*
|
352
|
+
* The reducers of the first facet node will be reused.
|
353
|
+
*
|
354
|
+
* @internal
|
355
|
+
*/
|
356
|
+
function unionFacetNode(a, b) {
|
357
|
+
assert(a.facet === b.facet);
|
358
|
+
return new FacetNode(a.facet, zip5(a.inputs, b.inputs, unionInput), unionChildren(a.children, b.children), a.reducers);
|
359
|
+
}
|
360
|
+
/**
|
361
|
+
* Takes two facet nodes and returns a new facet node containing inputs and
|
362
|
+
* children from the first node but not the second.
|
363
|
+
*
|
364
|
+
* The reducers of the first facet node will be reused.
|
365
|
+
*
|
366
|
+
* @internal
|
367
|
+
*/
|
368
|
+
function subtractFacetNode(a, b) {
|
369
|
+
assert(a.facet === b.facet);
|
370
|
+
return new FacetNode(a.facet, zip5(a.inputs, b.inputs, subtractInput), subtractChildren(a.children, b.children), a.reducers);
|
371
|
+
}
|
372
|
+
var FacetNode = class {
|
373
|
+
output = null;
|
374
|
+
constructor(facet, inputs = [
|
375
|
+
null,
|
376
|
+
null,
|
377
|
+
null,
|
378
|
+
null,
|
379
|
+
null
|
380
|
+
], children = /* @__PURE__ */ new Map(), reducers = [
|
381
|
+
null,
|
382
|
+
null,
|
383
|
+
null,
|
384
|
+
null,
|
385
|
+
null
|
386
|
+
]) {
|
387
|
+
this.facet = facet;
|
388
|
+
this.inputs = inputs;
|
389
|
+
this.children = children;
|
390
|
+
this.reducers = reducers;
|
391
|
+
}
|
392
|
+
calcOutput() {
|
393
|
+
const inputs = [
|
394
|
+
null,
|
395
|
+
null,
|
396
|
+
null,
|
397
|
+
null,
|
398
|
+
null
|
399
|
+
];
|
400
|
+
const output = [
|
401
|
+
null,
|
402
|
+
null,
|
403
|
+
null,
|
404
|
+
null,
|
405
|
+
null
|
406
|
+
];
|
407
|
+
for (let pri = 0; pri < 5; pri++) {
|
408
|
+
const input = this.inputs[pri];
|
409
|
+
if (input) inputs[pri] = [...input];
|
410
|
+
}
|
411
|
+
for (const child of this.children.values()) {
|
412
|
+
const childOutput = child.getOutput();
|
413
|
+
for (let pri = 0; pri < 5; pri++) if (childOutput[pri]) {
|
414
|
+
const input = inputs[pri] ||= [];
|
415
|
+
input.push(childOutput[pri]);
|
416
|
+
}
|
417
|
+
}
|
418
|
+
if (this.facet.singleton) {
|
419
|
+
const reducer = this.reducers[Priority.default] ||= this.facet.reducer;
|
420
|
+
const input = inputs.filter(isNotNullish).flat();
|
421
|
+
output[Priority.default] = reducer(input);
|
422
|
+
} else for (let pri = 0; pri < 5; pri++) {
|
423
|
+
const input = inputs[pri];
|
424
|
+
if (input) {
|
425
|
+
const reducer = this.reducers[pri] ||= this.facet.reducer;
|
426
|
+
output[pri] = reducer(input);
|
427
|
+
}
|
428
|
+
}
|
429
|
+
return output;
|
430
|
+
}
|
431
|
+
getOutput() {
|
432
|
+
if (!this.output) this.output = this.calcOutput();
|
433
|
+
return this.output;
|
434
|
+
}
|
435
|
+
getSingletonOutput() {
|
436
|
+
assert(this.facet.singleton);
|
437
|
+
return this.getOutput()[Priority.default];
|
438
|
+
}
|
439
|
+
getRootOutput() {
|
440
|
+
assert(this.isRoot());
|
441
|
+
const output = this.getSingletonOutput();
|
442
|
+
assert(output);
|
443
|
+
return output;
|
444
|
+
}
|
445
|
+
isRoot() {
|
446
|
+
return !this.facet.parent;
|
447
|
+
}
|
448
|
+
};
|
449
|
+
|
450
|
+
//#endregion
|
451
|
+
//#region src/facets/facet-extension.ts
|
452
|
+
/**
|
453
|
+
* @internal
|
454
|
+
*/
|
455
|
+
var FacetExtensionImpl = class extends BaseExtension {
|
456
|
+
/**
|
457
|
+
* @internal
|
458
|
+
*/
|
459
|
+
constructor(facet, payloads) {
|
460
|
+
super();
|
461
|
+
this.facet = facet;
|
462
|
+
this.payloads = payloads;
|
463
|
+
}
|
464
|
+
/**
|
465
|
+
* @internal
|
466
|
+
*/
|
467
|
+
createTree(priority) {
|
468
|
+
const pri = this.priority ?? priority;
|
469
|
+
const inputs = [
|
470
|
+
null,
|
471
|
+
null,
|
472
|
+
null,
|
473
|
+
null,
|
474
|
+
null
|
475
|
+
];
|
476
|
+
inputs[pri] = [...this.payloads];
|
477
|
+
let node = new FacetNode(this.facet, inputs);
|
478
|
+
while (node.facet.parent) {
|
479
|
+
const children = new Map([[node.facet.index, node]]);
|
480
|
+
node = new FacetNode(node.facet.parent, void 0, children);
|
481
|
+
}
|
482
|
+
return node;
|
483
|
+
}
|
484
|
+
};
|
485
|
+
/**
|
486
|
+
* @internal
|
487
|
+
*/
|
488
|
+
function defineFacetPayload(facet, payloads) {
|
489
|
+
return new FacetExtensionImpl(facet, payloads);
|
490
|
+
}
|
491
|
+
|
492
|
+
//#endregion
|
493
|
+
//#region src/facets/state.ts
|
494
|
+
const stateFacet = defineFacet({
|
495
|
+
reduce: () => {
|
496
|
+
let callbacks = [];
|
497
|
+
const state = (ctx) => {
|
498
|
+
const configs = callbacks.map((cb) => cb(ctx));
|
499
|
+
const config = {
|
500
|
+
schema: ctx.schema,
|
501
|
+
storedMarks: [],
|
502
|
+
plugins: []
|
503
|
+
};
|
504
|
+
for (const c of configs) {
|
505
|
+
config.schema = config.schema ?? c.schema;
|
506
|
+
config.doc = config.doc ?? c.doc;
|
507
|
+
config.selection = config.selection ?? c.selection;
|
508
|
+
config.storedMarks = [...config.storedMarks, ...c.storedMarks ?? []];
|
509
|
+
config.plugins = uniqPush(config.plugins ?? [], c.plugins ?? []);
|
510
|
+
}
|
511
|
+
assert(config.doc || config.schema, "Can't create state without a schema nor a document");
|
512
|
+
if (config.doc) config.schema = void 0;
|
513
|
+
return config;
|
514
|
+
};
|
515
|
+
return function reducer(inputs) {
|
516
|
+
callbacks = inputs;
|
517
|
+
return { state };
|
518
|
+
};
|
519
|
+
},
|
520
|
+
singleton: true,
|
521
|
+
parent: rootFacet
|
522
|
+
});
|
523
|
+
|
524
|
+
//#endregion
|
525
|
+
//#region src/utils/get-dom-api.ts
|
526
|
+
function findGlobalBrowserDocument() {
|
527
|
+
if (typeof document !== "undefined") return document;
|
528
|
+
if (typeof globalThis !== "undefined" && globalThis.document) return globalThis.document;
|
529
|
+
}
|
530
|
+
function findGlobalBrowserWindow() {
|
531
|
+
if (typeof window !== "undefined") return window;
|
532
|
+
if (typeof globalThis !== "undefined" && globalThis.window) return globalThis.window;
|
533
|
+
}
|
534
|
+
function findBrowserDocument(options) {
|
535
|
+
return options?.document ?? findGlobalBrowserDocument() ?? findGlobalBrowserWindow()?.document;
|
536
|
+
}
|
537
|
+
function findBrowserWindow(options) {
|
538
|
+
return options?.document?.defaultView ?? findGlobalBrowserWindow() ?? findBrowserDocument(options)?.defaultView ?? void 0;
|
539
|
+
}
|
540
|
+
function getBrowserDocument(options) {
|
541
|
+
const doc = findBrowserDocument(options);
|
542
|
+
if (doc) return doc;
|
543
|
+
throw new DOMDocumentNotFoundError();
|
544
|
+
}
|
545
|
+
function getBrowserWindow(options) {
|
546
|
+
const win = findBrowserWindow(options);
|
547
|
+
if (win) return win;
|
548
|
+
throw new DOMDocumentNotFoundError();
|
549
|
+
}
|
550
|
+
|
551
|
+
//#endregion
|
552
|
+
//#region src/utils/parse.ts
|
553
|
+
/**
|
554
|
+
* Return a JSON object representing this state.
|
555
|
+
*
|
556
|
+
* @public
|
557
|
+
*
|
558
|
+
* @example
|
559
|
+
*
|
560
|
+
* ```ts
|
561
|
+
* const state = editor.state
|
562
|
+
* const json = jsonFromState(state)
|
563
|
+
* ```
|
564
|
+
*/
|
565
|
+
function jsonFromState(state) {
|
566
|
+
return state.toJSON();
|
567
|
+
}
|
568
|
+
/**
|
569
|
+
* Parse a JSON object to a ProseMirror state.
|
570
|
+
*
|
571
|
+
* @public
|
572
|
+
*
|
573
|
+
* @example
|
574
|
+
*
|
575
|
+
* ```ts
|
576
|
+
* const json = { state: { type: 'doc', content: [{ type: 'paragraph' }], selection: { type: 'text', from: 1, to: 1 } } }
|
577
|
+
* const state = stateFromJSON(json, { schema: editor.schema })
|
578
|
+
* ```
|
579
|
+
*/
|
580
|
+
function stateFromJSON(json, options) {
|
581
|
+
return EditorState.fromJSON({ schema: options.schema }, json);
|
582
|
+
}
|
583
|
+
/**
|
584
|
+
* Return a JSON object representing this node.
|
585
|
+
*
|
586
|
+
* @public
|
587
|
+
*
|
588
|
+
* @example
|
589
|
+
*
|
590
|
+
* ```ts
|
591
|
+
* const node = editor.state.doc
|
592
|
+
* const json = jsonFromNode(node)
|
593
|
+
* ```
|
594
|
+
*/
|
595
|
+
function jsonFromNode(node) {
|
596
|
+
return node.toJSON();
|
597
|
+
}
|
598
|
+
/**
|
599
|
+
* Parse a JSON object to a ProseMirror node.
|
600
|
+
*
|
601
|
+
* @public
|
602
|
+
*
|
603
|
+
* @example
|
604
|
+
*
|
605
|
+
* ```ts
|
606
|
+
* const json = { type: 'doc', content: [{ type: 'paragraph' }] }
|
607
|
+
* const node = nodeFromJSON(json, { schema: editor.schema })
|
608
|
+
* ```
|
609
|
+
*/
|
610
|
+
function nodeFromJSON(json, options) {
|
611
|
+
return options.schema.nodeFromJSON(json);
|
612
|
+
}
|
613
|
+
/**
|
614
|
+
* Parse a HTML element to a ProseMirror node.
|
615
|
+
*
|
616
|
+
* @public
|
617
|
+
*
|
618
|
+
* @example
|
619
|
+
*
|
620
|
+
* ```ts
|
621
|
+
* const element = document.getElementById('content')
|
622
|
+
* const node = nodeFromElement(element, { schema: editor.schema })
|
623
|
+
*/
|
624
|
+
function nodeFromElement(element, options) {
|
625
|
+
const { DOMParser: CustomDOMParser, schema,...parseOptions } = options;
|
626
|
+
return (CustomDOMParser || DOMParser).fromSchema(schema).parse(element, parseOptions);
|
627
|
+
}
|
628
|
+
/**
|
629
|
+
* Serialize a ProseMirror node to a HTML element.
|
630
|
+
*
|
631
|
+
* @public
|
632
|
+
*
|
633
|
+
* @example
|
634
|
+
*
|
635
|
+
* ```ts
|
636
|
+
* const node = editor.state.doc
|
637
|
+
* const element = elementFromNode(node)
|
638
|
+
* ```
|
639
|
+
*/
|
640
|
+
function elementFromNode(node, options) {
|
641
|
+
const Serializer = options?.DOMSerializer || DOMSerializer;
|
642
|
+
const document$1 = getBrowserDocument(options);
|
643
|
+
const schema = node.type.schema;
|
644
|
+
const serializer = Serializer.fromSchema(schema);
|
645
|
+
if (schema.topNodeType !== node.type) return serializer.serializeNode(node, { document: document$1 });
|
646
|
+
else return serializer.serializeFragment(node.content, { document: document$1 }, document$1.createElement("div"));
|
647
|
+
}
|
648
|
+
/**
|
649
|
+
* Parse a HTML string to a HTML element.
|
650
|
+
*
|
651
|
+
* @internal
|
652
|
+
*/
|
653
|
+
function elementFromHTML(html, options) {
|
654
|
+
const win = getBrowserWindow(options);
|
655
|
+
const parser = new win.DOMParser();
|
656
|
+
return parser.parseFromString(`<body><div>${html}</div></body>`, "text/html").body.firstElementChild;
|
657
|
+
}
|
658
|
+
/**
|
659
|
+
* @internal
|
660
|
+
*/
|
661
|
+
function htmlFromElement(element) {
|
662
|
+
return element.outerHTML;
|
663
|
+
}
|
664
|
+
/**
|
665
|
+
* Parse a HTML string to a ProseMirror node.
|
666
|
+
*
|
667
|
+
* @public
|
668
|
+
*
|
669
|
+
* @example
|
670
|
+
*
|
671
|
+
* ```ts
|
672
|
+
* const html = '<p>Hello, world!</p>'
|
673
|
+
* const node = nodeFromHTML(html, { schema: editor.schema })
|
674
|
+
*/
|
675
|
+
function nodeFromHTML(html, options) {
|
676
|
+
return nodeFromElement(elementFromHTML(html, options), options);
|
677
|
+
}
|
678
|
+
/**
|
679
|
+
* Serialize a ProseMirror node to a HTML string
|
680
|
+
*
|
681
|
+
* @public
|
682
|
+
*
|
683
|
+
* @example
|
684
|
+
*
|
685
|
+
* ```ts
|
686
|
+
* const node = document.getElementById('content')
|
687
|
+
* const html = htmlFromNode(node)
|
688
|
+
* ```
|
689
|
+
*/
|
690
|
+
function htmlFromNode(node, options) {
|
691
|
+
return elementFromNode(node, options).outerHTML;
|
692
|
+
}
|
693
|
+
/**
|
694
|
+
* Serialize a HTML element to a ProseMirror document JSON object.
|
695
|
+
*
|
696
|
+
* @public
|
697
|
+
*
|
698
|
+
* @example
|
699
|
+
*
|
700
|
+
* ```ts
|
701
|
+
* const element = document.getElementById('content')
|
702
|
+
* const json = jsonFromElement(element, { schema: editor.schema })
|
703
|
+
* ```
|
704
|
+
*/
|
705
|
+
function jsonFromElement(element, options) {
|
706
|
+
return jsonFromNode(nodeFromElement(element, options));
|
707
|
+
}
|
708
|
+
/**
|
709
|
+
* Parse a ProseMirror document JSON object to a HTML element.
|
710
|
+
*
|
711
|
+
* @public
|
712
|
+
*
|
713
|
+
* @example
|
714
|
+
*
|
715
|
+
* ```ts
|
716
|
+
* const json = { type: 'doc', content: [{ type: 'paragraph' }] }
|
717
|
+
* const element = elementFromJSON(json, { schema: editor.schema })
|
718
|
+
* ```
|
719
|
+
*/
|
720
|
+
function elementFromJSON(json, options) {
|
721
|
+
return elementFromNode(nodeFromJSON(json, options), options);
|
722
|
+
}
|
723
|
+
/**
|
724
|
+
* Parse a HTML string to a ProseMirror document JSON object.
|
725
|
+
*
|
726
|
+
* @public
|
727
|
+
*
|
728
|
+
* @example
|
729
|
+
*
|
730
|
+
* ```ts
|
731
|
+
* const html = '<p>Hello, world!</p>'
|
732
|
+
* const json = jsonFromHTML(html, { schema: editor.schema })
|
733
|
+
* ```
|
734
|
+
*/
|
735
|
+
function jsonFromHTML(html, options) {
|
736
|
+
return jsonFromElement(elementFromHTML(html, options), options);
|
737
|
+
}
|
738
|
+
/**
|
739
|
+
* Parse a ProseMirror document JSON object to a HTML string.
|
740
|
+
*
|
741
|
+
* @public
|
742
|
+
*
|
743
|
+
* @example
|
744
|
+
*
|
745
|
+
* ```ts
|
746
|
+
* const json = { type: 'doc', content: [{ type: 'paragraph' }] }
|
747
|
+
* const html = htmlFromJSON(json, { schema: editor.schema })
|
748
|
+
* ```
|
749
|
+
*/
|
750
|
+
function htmlFromJSON(json, options) {
|
751
|
+
return htmlFromElement(elementFromJSON(json, options));
|
752
|
+
}
|
753
|
+
|
754
|
+
//#endregion
|
755
|
+
//#region src/utils/editor-content.ts
|
756
|
+
function getEditorContentJSON(schema, content) {
|
757
|
+
if (typeof content === "string") return jsonFromHTML(content, { schema });
|
758
|
+
else if (isElementLike(content)) return jsonFromElement(content, { schema });
|
759
|
+
else return content;
|
760
|
+
}
|
761
|
+
function getEditorContentNode(schema, content) {
|
762
|
+
if (isProseMirrorNode(content)) return content;
|
763
|
+
return schema.nodeFromJSON(getEditorContentJSON(schema, content));
|
764
|
+
}
|
765
|
+
function getEditorContentDoc(schema, content) {
|
766
|
+
const doc = getEditorContentNode(schema, content);
|
767
|
+
assert(doc.type.schema === schema, "Document schema does not match editor schema");
|
768
|
+
assert(doc.type === schema.topNodeType, `Document type does not match editor top node type. Expected ${schema.topNodeType.name}, got ${doc.type.name}`);
|
769
|
+
return doc;
|
770
|
+
}
|
771
|
+
function getEditorSelection(doc, selection) {
|
772
|
+
if (isSelection(selection)) {
|
773
|
+
assert(selection.$head.doc === doc, "Selection and doc do not match");
|
774
|
+
return selection;
|
775
|
+
}
|
776
|
+
if (selection === "start") return Selection.atStart(doc);
|
777
|
+
if (selection === "end") return Selection.atEnd(doc);
|
778
|
+
return Selection.fromJSON(doc, selection);
|
779
|
+
}
|
780
|
+
|
781
|
+
//#endregion
|
782
|
+
//#region src/extensions/default-state.ts
|
783
|
+
/**
|
784
|
+
* Define a default state for the editor.
|
785
|
+
*
|
786
|
+
* @param options
|
787
|
+
*
|
788
|
+
* @public
|
789
|
+
*/
|
790
|
+
function defineDefaultState({ defaultSelection, defaultContent, defaultDoc, defaultHTML }) {
|
791
|
+
const defaultDocContent = defaultContent || defaultDoc || defaultHTML;
|
792
|
+
return defineFacetPayload(stateFacet, [({ schema }) => {
|
793
|
+
const config = {};
|
794
|
+
if (defaultDocContent) {
|
795
|
+
const json = getEditorContentJSON(schema, defaultDocContent);
|
796
|
+
config.doc = schema.nodeFromJSON(json);
|
797
|
+
if (defaultSelection) config.selection = Selection.fromJSON(config.doc, defaultSelection);
|
798
|
+
}
|
799
|
+
return config;
|
800
|
+
}]);
|
801
|
+
}
|
802
|
+
|
803
|
+
//#endregion
|
804
|
+
//#region src/utils/deep-equals.ts
|
805
|
+
function deepEquals(a, b) {
|
806
|
+
if (a === b) return true;
|
807
|
+
if (!a || !b) return false;
|
808
|
+
if (Array.isArray(a) && Array.isArray(b)) return a.length === b.length && a.every((x, i) => deepEquals(x, b[i]));
|
809
|
+
if (a instanceof OrderedMap && b instanceof OrderedMap) return a.size === b.size && deepEquals(a.toObject(), b.toObject());
|
810
|
+
if (typeof a === "object" && typeof b === "object") {
|
811
|
+
const aKeys = Object.keys(a);
|
812
|
+
const bKeys = Object.keys(b);
|
813
|
+
return aKeys.length === bKeys.length && aKeys.every((key) => deepEquals(a[key], b[key]));
|
814
|
+
}
|
815
|
+
return false;
|
816
|
+
}
|
817
|
+
|
818
|
+
//#endregion
|
819
|
+
//#region src/utils/is-subset.ts
|
820
|
+
/**
|
821
|
+
* Check if `subset` is a subset of `superset`.
|
822
|
+
*
|
823
|
+
* @internal
|
824
|
+
*/
|
825
|
+
function isSubset(subset, superset) {
|
826
|
+
return Object.keys(subset).every((key) => subset[key] === superset[key]);
|
827
|
+
}
|
828
|
+
|
829
|
+
//#endregion
|
830
|
+
//#region src/utils/includes-mark.ts
|
831
|
+
function includesMark(marks, markType, attrs) {
|
832
|
+
attrs = attrs || {};
|
833
|
+
return marks.some((mark) => {
|
834
|
+
return mark.type === markType && isSubset(attrs, mark.attrs);
|
835
|
+
});
|
836
|
+
}
|
837
|
+
|
838
|
+
//#endregion
|
839
|
+
//#region src/utils/is-mark-absent.ts
|
840
|
+
/**
|
841
|
+
* Returns true if the given mark is missing in some part of the range.
|
842
|
+
* Returns false if the entire range has the given mark.
|
843
|
+
* Returns true if the mark is not allowed in the range.
|
844
|
+
*
|
845
|
+
* @internal
|
846
|
+
*/
|
847
|
+
function isMarkAbsent(node, from, to, markType, attrs) {
|
848
|
+
let missing = false;
|
849
|
+
let available = false;
|
850
|
+
node.nodesBetween(from, to, (node$1, pos, parent) => {
|
851
|
+
if (missing) return false;
|
852
|
+
const allowed = parent?.type.allowsMarkType(markType) && !node$1.marks.some((m) => m.type !== markType && m.type.excludes(markType));
|
853
|
+
if (allowed) {
|
854
|
+
available = true;
|
855
|
+
if (!includesMark(node$1.marks, markType, attrs)) missing = true;
|
856
|
+
}
|
857
|
+
});
|
858
|
+
return available ? missing : true;
|
859
|
+
}
|
860
|
+
|
861
|
+
//#endregion
|
862
|
+
//#region src/utils/is-mark-active.ts
|
863
|
+
/**
|
864
|
+
* @internal
|
865
|
+
*/
|
866
|
+
function isMarkActive(state, type, attrs) {
|
867
|
+
const { from, $from, to, empty } = state.selection;
|
868
|
+
const markType = getMarkType(state.schema, type);
|
869
|
+
if (empty) {
|
870
|
+
const marks = state.storedMarks || $from.marks();
|
871
|
+
return includesMark(marks, markType, attrs);
|
872
|
+
} else return !isMarkAbsent(state.doc, from, to, markType, attrs);
|
873
|
+
}
|
874
|
+
|
875
|
+
//#endregion
|
876
|
+
//#region src/editor/action.ts
|
877
|
+
/**
|
878
|
+
* @internal
|
879
|
+
*/
|
880
|
+
function createNodeActions(schema, getState, createNode = defaultCreateNode) {
|
881
|
+
return mapValues(schema.nodes, (type) => createNodeAction(type, getState, createNode));
|
882
|
+
}
|
883
|
+
function createNodeAction(type, getState, createNode) {
|
884
|
+
const action = (...args) => buildNode(type, args, createNode);
|
885
|
+
action.isActive = (attrs) => {
|
886
|
+
const state = getState();
|
887
|
+
return state ? isNodeActive(state, type, attrs) : false;
|
888
|
+
};
|
889
|
+
return action;
|
890
|
+
}
|
891
|
+
/**
|
892
|
+
* @internal
|
893
|
+
*/
|
894
|
+
function createMarkActions(schema, getState, applyMark = defaultApplyMark) {
|
895
|
+
return mapValues(schema.marks, (type) => createMarkAction(type, getState, applyMark));
|
896
|
+
}
|
897
|
+
function createMarkAction(type, getState, applyMark) {
|
898
|
+
const action = (...args) => buildMark(type, args, applyMark);
|
899
|
+
action.isActive = (attrs) => {
|
900
|
+
const state = getState();
|
901
|
+
return state ? isMarkActive(state, type, attrs) : false;
|
902
|
+
};
|
903
|
+
return action;
|
904
|
+
}
|
905
|
+
function buildMark(type, args, applyMark) {
|
906
|
+
const [attrs, children] = normalizeArgs(args);
|
907
|
+
const mark = type.create(attrs);
|
908
|
+
return applyMark(mark, flattenChildren(type.schema, children));
|
909
|
+
}
|
910
|
+
const defaultApplyMark = (mark, children) => {
|
911
|
+
return children.map((node) => node.mark(mark.addToSet(node.marks)));
|
912
|
+
};
|
913
|
+
function buildNode(type, args, createNode) {
|
914
|
+
const [attrs, children] = normalizeArgs(args);
|
915
|
+
return createNode(type, attrs, flattenChildren(type.schema, children));
|
916
|
+
}
|
917
|
+
const defaultCreateNode = (type, attrs, children) => {
|
918
|
+
const node = type.createAndFill(attrs, children);
|
919
|
+
assert(node, `Failed to create node ${type.name}`);
|
920
|
+
return node;
|
921
|
+
};
|
922
|
+
function flattenChildren(schema, children) {
|
923
|
+
const nodes = [];
|
924
|
+
for (const child of children) if (typeof child === "string") {
|
925
|
+
if (child) nodes.push(schema.text(child, null));
|
926
|
+
} else if (Array.isArray(child)) nodes.push(...flattenChildren(schema, child));
|
927
|
+
else if (isProseMirrorNode(child)) nodes.push(child);
|
928
|
+
else throw new ProseKitError(`Invalid node child: ${typeof child}`);
|
929
|
+
return nodes;
|
930
|
+
}
|
931
|
+
function normalizeArgs(args) {
|
932
|
+
const [attrs, ...children] = args;
|
933
|
+
if (isNodeChild(attrs)) {
|
934
|
+
children.unshift(attrs);
|
935
|
+
return [null, children];
|
936
|
+
} else if (typeof attrs === "object") return [attrs, children];
|
937
|
+
else return [null, children];
|
938
|
+
}
|
939
|
+
function isNodeChild(value) {
|
940
|
+
if (!value) return false;
|
941
|
+
return typeof value === "string" || Array.isArray(value) || isProseMirrorNode(value);
|
942
|
+
}
|
943
|
+
|
944
|
+
//#endregion
|
945
|
+
//#region src/facets/union-extension.ts
|
946
|
+
var UnionExtensionImpl = class extends BaseExtension {
|
947
|
+
/**
|
948
|
+
* @internal
|
949
|
+
*/
|
950
|
+
constructor(extension = []) {
|
951
|
+
super();
|
952
|
+
this.extension = extension;
|
953
|
+
}
|
954
|
+
/**
|
955
|
+
* @internal
|
956
|
+
*/
|
957
|
+
createTree(priority) {
|
958
|
+
const pri = this.priority ?? priority;
|
959
|
+
const extensions = [...this.extension];
|
960
|
+
extensions.sort((a, b) => (a.priority ?? pri) - (b.priority ?? pri));
|
961
|
+
const children = extensions.map((ext) => ext.getTree(pri));
|
962
|
+
assert(children.length > 0);
|
963
|
+
let node = children[0];
|
964
|
+
for (let i = 1; i < children.length; i++) node = unionFacetNode(node, children[i]);
|
965
|
+
return node;
|
966
|
+
}
|
967
|
+
};
|
968
|
+
|
969
|
+
//#endregion
|
970
|
+
//#region src/editor/union.ts
|
971
|
+
function union(...exts) {
|
972
|
+
const extensions = exts.flat();
|
973
|
+
assert(extensions.length > 0, "At least one extension is required");
|
974
|
+
return new UnionExtensionImpl(extensions);
|
975
|
+
}
|
976
|
+
|
977
|
+
//#endregion
|
978
|
+
//#region src/editor/editor.ts
|
979
|
+
/**
|
980
|
+
* @internal
|
981
|
+
*/
|
982
|
+
function setupEditorExtension(options) {
|
983
|
+
if (options.defaultContent || options.defaultDoc || options.defaultHTML) return union(options.extension, defineDefaultState(options));
|
984
|
+
return options.extension;
|
985
|
+
}
|
986
|
+
/**
|
987
|
+
* @public
|
988
|
+
*/
|
989
|
+
function createEditor(options) {
|
990
|
+
const extension = setupEditorExtension(options);
|
991
|
+
const instance = new EditorInstance(extension);
|
992
|
+
return new Editor(instance);
|
993
|
+
}
|
994
|
+
/**
|
995
|
+
* An internal class to make TypeScript generic type easier to use.
|
996
|
+
*
|
997
|
+
* @internal
|
998
|
+
*/
|
999
|
+
var EditorInstance = class {
|
1000
|
+
view = null;
|
1001
|
+
schema;
|
1002
|
+
nodes;
|
1003
|
+
marks;
|
1004
|
+
commands = {};
|
1005
|
+
tree;
|
1006
|
+
directEditorProps;
|
1007
|
+
afterMounted = [];
|
1008
|
+
constructor(extension) {
|
1009
|
+
this.tree = extension.getTree();
|
1010
|
+
const payload = this.tree.getRootOutput();
|
1011
|
+
const schema = payload.schema;
|
1012
|
+
const stateConfig = payload.state;
|
1013
|
+
assert(schema && stateConfig, "Schema must be defined");
|
1014
|
+
const state = EditorState.create(stateConfig);
|
1015
|
+
if (payload.commands) for (const [name, commandCreator] of Object.entries(payload.commands)) this.defineCommand(name, commandCreator);
|
1016
|
+
this.nodes = createNodeActions(state.schema, this.getState);
|
1017
|
+
this.marks = createMarkActions(state.schema, this.getState);
|
1018
|
+
this.schema = state.schema;
|
1019
|
+
this.directEditorProps = {
|
1020
|
+
state,
|
1021
|
+
...payload.view
|
1022
|
+
};
|
1023
|
+
}
|
1024
|
+
getState = () => {
|
1025
|
+
return this.view?.state || this.directEditorProps.state;
|
1026
|
+
};
|
1027
|
+
getDoc() {
|
1028
|
+
return this.getState().doc;
|
1029
|
+
}
|
1030
|
+
getProp(propName) {
|
1031
|
+
return this.view?.someProp(propName) ?? this.directEditorProps[propName];
|
1032
|
+
}
|
1033
|
+
updateState(state) {
|
1034
|
+
if (this.view) this.view.updateState(state);
|
1035
|
+
else this.directEditorProps.state = state;
|
1036
|
+
}
|
1037
|
+
dispatch = (tr) => {
|
1038
|
+
if (this.view) this.view.dispatch(tr);
|
1039
|
+
else this.directEditorProps.state = this.directEditorProps.state.apply(tr);
|
1040
|
+
};
|
1041
|
+
setContent(content, selection) {
|
1042
|
+
const doc = getEditorContentDoc(this.schema, content);
|
1043
|
+
doc.check();
|
1044
|
+
const sel = getEditorSelection(doc, selection || "start");
|
1045
|
+
const oldState = this.getState();
|
1046
|
+
if (doc.eq(oldState.doc) && (!selection || sel.eq(oldState.selection))) return;
|
1047
|
+
const newState = EditorState.create({
|
1048
|
+
doc,
|
1049
|
+
selection: sel,
|
1050
|
+
plugins: oldState.plugins
|
1051
|
+
});
|
1052
|
+
this.updateState(newState);
|
1053
|
+
}
|
1054
|
+
/**
|
1055
|
+
* Return a JSON object representing the editor's current document.
|
1056
|
+
*/
|
1057
|
+
getDocJSON = () => {
|
1058
|
+
const state = this.getState();
|
1059
|
+
return jsonFromNode(state.doc);
|
1060
|
+
};
|
1061
|
+
/**
|
1062
|
+
* Return a HTML string representing the editor's current document.
|
1063
|
+
*/
|
1064
|
+
getDocHTML = (options) => {
|
1065
|
+
const serializer = this.getProp("clipboardSerializer");
|
1066
|
+
const DOMSerializer$1 = serializer ? { fromSchema: () => serializer } : void 0;
|
1067
|
+
const doc = this.getDoc();
|
1068
|
+
return htmlFromNode(doc, {
|
1069
|
+
...options,
|
1070
|
+
DOMSerializer: DOMSerializer$1
|
1071
|
+
});
|
1072
|
+
};
|
1073
|
+
updateExtension(extension, add) {
|
1074
|
+
const view = this.view;
|
1075
|
+
if (!view || view.isDestroyed) return;
|
1076
|
+
const tree = extension.getTree();
|
1077
|
+
const payload = tree.getRootOutput();
|
1078
|
+
if (payload?.schema) throw new ProseKitError("Schema cannot be changed");
|
1079
|
+
if (payload?.view) throw new ProseKitError("View cannot be changed");
|
1080
|
+
const oldPayload = this.tree.getRootOutput();
|
1081
|
+
const oldPlugins = [...view.state?.plugins ?? []];
|
1082
|
+
this.tree = add ? unionFacetNode(this.tree, tree) : subtractFacetNode(this.tree, tree);
|
1083
|
+
const newPayload = this.tree.getRootOutput();
|
1084
|
+
const newPlugins = [...newPayload?.state?.plugins ?? []];
|
1085
|
+
if (!deepEquals(oldPlugins, newPlugins)) {
|
1086
|
+
const state = view.state.reconfigure({ plugins: newPlugins });
|
1087
|
+
view.updateState(state);
|
1088
|
+
}
|
1089
|
+
if (newPayload?.commands && !deepEquals(oldPayload?.commands, newPayload?.commands)) {
|
1090
|
+
const commands = newPayload.commands;
|
1091
|
+
const names = Object.keys(commands);
|
1092
|
+
for (const name of names) this.defineCommand(name, commands[name]);
|
1093
|
+
}
|
1094
|
+
}
|
1095
|
+
use(extension) {
|
1096
|
+
if (!this.mounted) {
|
1097
|
+
let canceled = false;
|
1098
|
+
let lazyRemove = null;
|
1099
|
+
const lazyCreate = () => {
|
1100
|
+
if (!canceled) lazyRemove = this.use(extension);
|
1101
|
+
};
|
1102
|
+
this.afterMounted.push(lazyCreate);
|
1103
|
+
return () => {
|
1104
|
+
canceled = true;
|
1105
|
+
lazyRemove?.();
|
1106
|
+
};
|
1107
|
+
}
|
1108
|
+
this.updateExtension(extension, true);
|
1109
|
+
return () => this.updateExtension(extension, false);
|
1110
|
+
}
|
1111
|
+
mount(place) {
|
1112
|
+
if (this.view) throw new ProseKitError("Editor is already mounted");
|
1113
|
+
this.view = new EditorView({ mount: place }, this.directEditorProps);
|
1114
|
+
this.afterMounted.forEach((callback) => callback());
|
1115
|
+
}
|
1116
|
+
unmount() {
|
1117
|
+
if (!this.view) return;
|
1118
|
+
this.directEditorProps.state = this.view.state;
|
1119
|
+
this.view.destroy();
|
1120
|
+
this.view = null;
|
1121
|
+
}
|
1122
|
+
get mounted() {
|
1123
|
+
return !!this.view && !this.view.isDestroyed;
|
1124
|
+
}
|
1125
|
+
get assertView() {
|
1126
|
+
if (!this.view) throw new ProseKitError("Editor is not mounted");
|
1127
|
+
return this.view;
|
1128
|
+
}
|
1129
|
+
definePlugins(plugins) {
|
1130
|
+
const view = this.assertView;
|
1131
|
+
const state = view.state;
|
1132
|
+
const newPlugins = [...plugins, ...state.plugins];
|
1133
|
+
const newState = state.reconfigure({ plugins: newPlugins });
|
1134
|
+
view.setProps({ state: newState });
|
1135
|
+
}
|
1136
|
+
removePlugins(plugins) {
|
1137
|
+
const view = this.view;
|
1138
|
+
if (!view) return;
|
1139
|
+
const state = view.state;
|
1140
|
+
const newPlugins = state.plugins.filter((p) => !plugins.includes(p));
|
1141
|
+
const newState = state.reconfigure({ plugins: newPlugins });
|
1142
|
+
view.setProps({ state: newState });
|
1143
|
+
}
|
1144
|
+
exec(command) {
|
1145
|
+
const state = this.getState();
|
1146
|
+
return command(state, this.dispatch, this.view ?? void 0);
|
1147
|
+
}
|
1148
|
+
canExec(command) {
|
1149
|
+
const state = this.getState();
|
1150
|
+
return command(state, void 0, this.view ?? void 0);
|
1151
|
+
}
|
1152
|
+
defineCommand(name, commandCreator) {
|
1153
|
+
const action = (...args) => {
|
1154
|
+
const command = commandCreator(...args);
|
1155
|
+
return this.exec(command);
|
1156
|
+
};
|
1157
|
+
const canExec = (...args) => {
|
1158
|
+
const command = commandCreator(...args);
|
1159
|
+
return this.canExec(command);
|
1160
|
+
};
|
1161
|
+
action.canApply = canExec;
|
1162
|
+
action.canExec = canExec;
|
1163
|
+
this.commands[name] = action;
|
1164
|
+
}
|
1165
|
+
removeCommand(name) {
|
1166
|
+
delete this.commands[name];
|
1167
|
+
}
|
1168
|
+
};
|
1169
|
+
/**
|
1170
|
+
* @public
|
1171
|
+
*/
|
1172
|
+
var Editor = class {
|
1173
|
+
instance;
|
1174
|
+
/**
|
1175
|
+
* @internal
|
1176
|
+
*/
|
1177
|
+
constructor(instance) {
|
1178
|
+
if (!(instance instanceof EditorInstance)) throw new TypeError("Invalid EditorInstance");
|
1179
|
+
this.instance = instance;
|
1180
|
+
}
|
1181
|
+
/**
|
1182
|
+
* Whether the editor is mounted.
|
1183
|
+
*/
|
1184
|
+
get mounted() {
|
1185
|
+
return this.instance.mounted;
|
1186
|
+
}
|
1187
|
+
/**
|
1188
|
+
* The editor view.
|
1189
|
+
*/
|
1190
|
+
get view() {
|
1191
|
+
return this.instance.assertView;
|
1192
|
+
}
|
1193
|
+
/**
|
1194
|
+
* The editor schema.
|
1195
|
+
*/
|
1196
|
+
get schema() {
|
1197
|
+
return this.instance.schema;
|
1198
|
+
}
|
1199
|
+
/**
|
1200
|
+
* The editor's current state.
|
1201
|
+
*/
|
1202
|
+
get state() {
|
1203
|
+
return this.instance.getState();
|
1204
|
+
}
|
1205
|
+
/**
|
1206
|
+
* Whether the editor is focused.
|
1207
|
+
*/
|
1208
|
+
get focused() {
|
1209
|
+
return this.instance.view?.hasFocus() ?? false;
|
1210
|
+
}
|
1211
|
+
/**
|
1212
|
+
* Mount the editor to the given HTML element.
|
1213
|
+
* Pass `null` or `undefined` to unmount the editor.
|
1214
|
+
*/
|
1215
|
+
mount = (place) => {
|
1216
|
+
if (place) this.instance.mount(place);
|
1217
|
+
else this.instance.unmount();
|
1218
|
+
};
|
1219
|
+
/**
|
1220
|
+
* Unmount the editor. This is equivalent to `mount(null)`.
|
1221
|
+
*/
|
1222
|
+
unmount = () => {
|
1223
|
+
this.instance.unmount();
|
1224
|
+
};
|
1225
|
+
/**
|
1226
|
+
* Focus the editor.
|
1227
|
+
*/
|
1228
|
+
focus = () => {
|
1229
|
+
this.instance.view?.focus();
|
1230
|
+
};
|
1231
|
+
/**
|
1232
|
+
* Blur the editor.
|
1233
|
+
*/
|
1234
|
+
blur = () => {
|
1235
|
+
this.instance.view?.dom.blur();
|
1236
|
+
};
|
1237
|
+
/**
|
1238
|
+
* Register an extension to the editor. Return a function to unregister the
|
1239
|
+
* extension.
|
1240
|
+
*/
|
1241
|
+
use = (extension) => {
|
1242
|
+
return this.instance.use(extension);
|
1243
|
+
};
|
1244
|
+
/**
|
1245
|
+
* Update the editor's state.
|
1246
|
+
*
|
1247
|
+
* @remarks
|
1248
|
+
*
|
1249
|
+
* This is an advanced method. Use it only if you have a specific reason to
|
1250
|
+
* directly manipulate the editor's state.
|
1251
|
+
*/
|
1252
|
+
updateState = (state) => {
|
1253
|
+
this.instance.updateState(state);
|
1254
|
+
};
|
1255
|
+
/**
|
1256
|
+
* Update the editor's document and selection.
|
1257
|
+
*
|
1258
|
+
* @param content - The new document to set. It can be one of the following:
|
1259
|
+
* - A ProseMirror node instance
|
1260
|
+
* - A ProseMirror node JSON object
|
1261
|
+
* - An HTML string
|
1262
|
+
* - An HTML element instance
|
1263
|
+
* @param selection - Optional. Specifies the new selection. It can be one of the following:
|
1264
|
+
* - A ProseMirror selection instance
|
1265
|
+
* - A ProseMirror selection JSON object
|
1266
|
+
* - The string "start" (to set selection at the beginning, default value)
|
1267
|
+
* - The string "end" (to set selection at the end)
|
1268
|
+
*/
|
1269
|
+
setContent = (content, selection) => {
|
1270
|
+
return this.instance.setContent(content, selection);
|
1271
|
+
};
|
1272
|
+
/**
|
1273
|
+
* Return a JSON object representing the editor's current document.
|
1274
|
+
*/
|
1275
|
+
getDocJSON = () => {
|
1276
|
+
return this.instance.getDocJSON();
|
1277
|
+
};
|
1278
|
+
/**
|
1279
|
+
* Return a HTML string representing the editor's current document.
|
1280
|
+
*/
|
1281
|
+
getDocHTML = (options) => {
|
1282
|
+
return this.instance.getDocHTML(options);
|
1283
|
+
};
|
1284
|
+
/**
|
1285
|
+
* Execute the given command. Return `true` if the command was successfully
|
1286
|
+
* executed, otherwise `false`.
|
1287
|
+
*/
|
1288
|
+
exec = (command) => {
|
1289
|
+
return this.instance.exec(command);
|
1290
|
+
};
|
1291
|
+
/**
|
1292
|
+
* Check if the given command can be executed. Return `true` if the command
|
1293
|
+
* can be executed, otherwise `false`.
|
1294
|
+
*/
|
1295
|
+
canExec = (command) => {
|
1296
|
+
return this.instance.canExec(command);
|
1297
|
+
};
|
1298
|
+
/**
|
1299
|
+
* All {@link CommandAction}s defined by the editor.
|
1300
|
+
*/
|
1301
|
+
get commands() {
|
1302
|
+
return this.instance.commands;
|
1303
|
+
}
|
1304
|
+
/**
|
1305
|
+
* All {@link NodeAction}s defined by the editor.
|
1306
|
+
*/
|
1307
|
+
get nodes() {
|
1308
|
+
return this.instance.nodes;
|
1309
|
+
}
|
1310
|
+
/**
|
1311
|
+
* All {@link MarkAction}s defined by the editor.
|
1312
|
+
*/
|
1313
|
+
get marks() {
|
1314
|
+
return this.instance.marks;
|
1315
|
+
}
|
1316
|
+
};
|
1317
|
+
|
1318
|
+
//#endregion
|
1319
|
+
export { Editor, EditorInstance, EditorNotFoundError, Priority, ProseKitError, assert, createEditor, createMarkActions, createNodeActions, defineDefaultState, defineFacet, defineFacetPayload, elementFromJSON, elementFromNode, getMarkType, getNodeType, htmlFromJSON, htmlFromNode, isAllSelection, isFragment, isMark, isMarkAbsent, isMarkActive, isNodeActive, isNodeSelection, isNotNullish, isProseMirrorNode, isSelection, isSlice, isTextSelection, jsonFromHTML, jsonFromNode, jsonFromState, nodeFromElement, nodeFromHTML, nodeFromJSON, rootFacet, schemaFacet, setupEditorExtension, stateFacet, stateFromJSON, toReversed, union };
|