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