@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.
@@ -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 };