@nerd-bible/wordgard 0.3.3

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,1451 @@
1
+ import { GardState, Transaction, GardSelection } from 'wordgard/state';
2
+ import { phrases } from 'wordgard/phrases';
3
+ import { Mark, ChangeSet, Leaf, Pos, Plot, Node } from 'wordgard/doc';
4
+ import { findClusterBreak } from '@marijn/find-cluster-break';
5
+ import { Alignment, Direction, Emphasis, Strong, Underline } from 'wordgard/types';
6
+
7
+ const commandHandler = /*@__PURE__*/GardState.Facet.define({
8
+ combine(handlers) {
9
+ let map = new Map();
10
+ for (let [cmd, handler] of handlers) {
11
+ let list = map.get(cmd);
12
+ if (!list)
13
+ map.set(cmd, list = []);
14
+ list.push(handler);
15
+ }
16
+ return map;
17
+ }
18
+ });
19
+ const Command = /*@__PURE__*/(function (Command) {
20
+ function handler(command, handler) {
21
+ return commandHandler.of([command, handler]);
22
+ }
23
+ Command.handler = handler;
24
+ function bind(command, param) {
25
+ return { command, param };
26
+ }
27
+ Command.bind = bind;
28
+ function dispatch(wg, command, p) {
29
+ let { command: cmd, param } = typeof command == "object" ? command : { command, param: p ?? null };
30
+ let handlers = wg.state.facet(commandHandler).get(cmd);
31
+ if (handlers)
32
+ for (let handler of handlers) {
33
+ let result = handler(wg, param);
34
+ if (result) {
35
+ if (typeof result != "boolean")
36
+ wg.dispatch(result);
37
+ return true;
38
+ }
39
+ }
40
+ let result = cmd(wg, param);
41
+ if (typeof result != "boolean")
42
+ wg.dispatch(result);
43
+ return !!result;
44
+ }
45
+ Command.dispatch = dispatch;
46
+ ;return Command})({});
47
+
48
+ function liftEmptyBlock(state) {
49
+ if (!state.selection.isCursor)
50
+ return false;
51
+ let sel = state.sel, block = sel.head.textblockParent;
52
+ if (!block || !sel.head.isAtStart(block) || !sel.head.isAtEnd(block))
53
+ return false;
54
+ let start = block.before, end = block.after, before = [], after = [];
55
+ for (let level = block.parent, index = block.index, atStart = true, atEnd = true, first = true; level; first = false, index = level.index, level = level.parent) {
56
+ if (!first && state.schema.canContain(level.node.type, block.node.type))
57
+ return {
58
+ changes: [
59
+ { from: start, to: block.before, insert: before },
60
+ { from: block.after, to: end, insert: after }
61
+ ],
62
+ scrollIntoView: true,
63
+ userEvent: "unwrap.empty"
64
+ };
65
+ if (level.node.type.isInline || level.node.type.isolating)
66
+ break;
67
+ if (index)
68
+ atStart = false;
69
+ if (atStart)
70
+ start--;
71
+ else
72
+ before.push(Plot.End);
73
+ if (index < level.node.content.length - 1)
74
+ atEnd = false;
75
+ if (atEnd)
76
+ end++;
77
+ else
78
+ after.unshift(level.node.tag.split(false));
79
+ }
80
+ return false;
81
+ }
82
+ function splitTextblock(state, splitListItem = true) {
83
+ let { from, to } = state.sel.replacementRange, { schema } = state.doc;
84
+ let before = from.textblockParent;
85
+ if (!before || !before.parent)
86
+ return false;
87
+ let tokens = [];
88
+ for (let p = from.parent;; p = p.parent) {
89
+ tokens.push(Plot.End);
90
+ if (p == before)
91
+ break;
92
+ }
93
+ if (splitListItem && !before.parent.node.type.hasRole(Node.Role.List) &&
94
+ before.isFirst && before.parent.parent?.node.type.hasRole(Node.Role.List))
95
+ tokens.push(Plot.End, before.parent.node.tag.split(false));
96
+ let after = to.textblockParent;
97
+ if (after) {
98
+ let atEnd = true, insert = tokens.length;
99
+ for (let p = to.parent, index = to.index;; index = p.index + 1, p = p.parent) {
100
+ if (index < p.node.content.length)
101
+ atEnd = false;
102
+ let tag = p.node.tag.split(atEnd), nextTag = atEnd && !p.node.type.spec.preserveOnSplitAtEnd ? null : tag;
103
+ if (!nextTag || !schema.canContain(p.parent.node.type, tag.type)) {
104
+ if (!atEnd)
105
+ return false;
106
+ let defaultType = schema.defaultContentPlot(p.parent.node.type);
107
+ if (defaultType)
108
+ tag = schema.withMarksFrom(tag, defaultType);
109
+ else
110
+ return false;
111
+ }
112
+ tokens.splice(insert, 0, tag);
113
+ if (p == after)
114
+ break;
115
+ }
116
+ }
117
+ let changes = [{
118
+ from: from.pos, to: to.pos,
119
+ insert: tokens
120
+ }];
121
+ if (from.isAtStart(before)) {
122
+ let deflt = schema.defaultContentPlot(before.parent.node.type);
123
+ if (deflt && !deflt.eq(before.node.tag))
124
+ changes.unshift({
125
+ from: before.before, to: before.start,
126
+ insert: [schema.withMarksFrom(before.node.tag, deflt)]
127
+ });
128
+ }
129
+ let changeSet = ChangeSet.create(state.doc, { correct: changes, local: true });
130
+ return {
131
+ changes: changeSet,
132
+ selection: GardSelection.cursor(changeSet.mapPos(to.pos, 1)),
133
+ scrollIntoView: true,
134
+ userEvent: "split.textblock"
135
+ };
136
+ }
137
+ function deleteSelection(state) {
138
+ let { ranges } = state.selection;
139
+ if (ranges.every(r => r.from == r.to))
140
+ return false;
141
+ return autoJoinBlocks(state, {
142
+ changes: {
143
+ correct: ranges.filter(r => r.from < r.to).map(r => ({ from: r.from, to: r.to, fit: true })),
144
+ local: true
145
+ },
146
+ selection: (cx, changes) => state.selection instanceof GardSelection.Text
147
+ ? GardSelection.near(cx, changes.mapPos(state.selection.head, -1), 1)
148
+ : state.selection.map(changes, cx),
149
+ scrollIntoView: true,
150
+ userEvent: "delete.selection"
151
+ });
152
+ }
153
+ function deleteEmptyTextblock(state, dir = -1) {
154
+ if (!state.selection.isCursor)
155
+ return false;
156
+ let block = state.sel.head.textblockParent;
157
+ if (!block || block.start < block.end || block.before == 0 && block.after == state.doc.length)
158
+ return false;
159
+ return {
160
+ changes: { from: block.before, to: block.after, fit: true },
161
+ selection: (cx, changes) => GardSelection.near(cx, changes.mapPos(state.selection.head), dir),
162
+ scrollIntoView: true,
163
+ userEvent: dir < 0 ? "delete.backward" : "delete.forward"
164
+ };
165
+ }
166
+ function joinBackward(state) {
167
+ if (!state.selection.isCursor)
168
+ return false;
169
+ let { head } = state.sel, block = head.textblockParent;
170
+ if (!block || !head.isAtStart(block))
171
+ return false;
172
+ let scan = block, target = scan.node;
173
+ while (!scan.index) {
174
+ if (!scan.parent)
175
+ return false;
176
+ scan = scan.parent;
177
+ if (scan.node.type.isolating || !scan.node.type.isBlock)
178
+ return false;
179
+ }
180
+ let before = scan.previousSibling, parent = scan.parent.node, pos = scan.start - 1;
181
+ while (before.isLeaf || !before.isTextblock) {
182
+ if (before.isLeaf || state.isAtom(before.type) || before.type.isolating || !before.type.isBlock)
183
+ return false;
184
+ let last = before.content.length - 1;
185
+ if (last < 0)
186
+ return false;
187
+ parent = before;
188
+ before = before.content[last];
189
+ pos--;
190
+ }
191
+ let { schema } = state.doc;
192
+ let changes = [
193
+ joinBlocks(state.doc.resolve(pos - 1).parent, block),
194
+ clearNonFitting(schema, block, before.type)
195
+ ];
196
+ if (!before.content.length && !before.tag.eq(target.tag) && schema.canContain(parent.type, target.type))
197
+ changes.push({
198
+ from: pos - before.length, to: pos - before.length + 1,
199
+ insert: [schema.withMarksFrom(before.tag, target.tag)]
200
+ });
201
+ let changeSet = ChangeSet.create(state.doc, changes);
202
+ return {
203
+ changes: changeSet,
204
+ selection: GardSelection.cursor(changeSet.mapPos(head.pos), -1),
205
+ scrollIntoView: true,
206
+ userEvent: "join.backward"
207
+ };
208
+ }
209
+ function joinListItems(state) {
210
+ if (!state.selection.isCursor)
211
+ return false;
212
+ let { head } = state.sel;
213
+ if (head.index || head.inText)
214
+ return false;
215
+ for (let scan = head.parent;;) {
216
+ let next = scan.parent;
217
+ if (!next)
218
+ return false;
219
+ if (scan.node.type.isBlock && next.node.type.hasRole(Node.Role.List)) {
220
+ const prev = scan.previousSibling;
221
+ if (!prev || !prev.isLeaf && scan.node.content.some(ch => !state.schema.canContain(prev.type, ch.type)))
222
+ return false;
223
+ return {
224
+ changes: { from: scan.before - 1, to: scan.before + 1 },
225
+ userEvent: "join.backward.list",
226
+ scrollIntoView: true
227
+ };
228
+ }
229
+ if (scan.index)
230
+ return false;
231
+ scan = next;
232
+ }
233
+ }
234
+ function joinForward(state) {
235
+ if (!state.selection.isCursor)
236
+ return false;
237
+ let { head } = state.sel, block = head.textblockParent;
238
+ if (!block || !head.isAtEnd(block))
239
+ return false;
240
+ let scan = block, target = scan.node;
241
+ for (;;) {
242
+ if (!scan.parent)
243
+ return false;
244
+ if (scan.index < scan.parent.node.content.length - 1)
245
+ break;
246
+ scan = scan.parent;
247
+ if (scan.node.type.isolating || !scan.node.type.isBlock)
248
+ return false;
249
+ }
250
+ let after = scan.nextSibling, parent = scan.parent.node, pos = scan.after;
251
+ while (after.isLeaf || !after.isTextblock) {
252
+ if (after.isLeaf || after.type.isolating || state.isAtom(after.type) || !after.type.isBlock || !after.content.length)
253
+ return false;
254
+ parent = after;
255
+ after = after.content[0];
256
+ pos++;
257
+ }
258
+ let blockAfter = state.doc.resolveNode(pos);
259
+ let { schema } = state.doc;
260
+ let changes = [
261
+ joinBlocks(block, blockAfter),
262
+ clearNonFitting(schema, blockAfter, target.type)
263
+ ];
264
+ if (!target.content.length && !target.tag.eq(after.tag) && schema.canContain(parent.type, after.type))
265
+ changes.push({
266
+ from: block.before, to: block.start,
267
+ insert: [schema.withMarksFrom(target.tag, after.tag)]
268
+ });
269
+ return {
270
+ changes,
271
+ scrollIntoView: true,
272
+ userEvent: "join.forward"
273
+ };
274
+ }
275
+ function deleteBackward(state, word = false) {
276
+ if (!state.selection.isCursor)
277
+ return false;
278
+ let sel = state.sel;
279
+ let { parent: scan, index, pos } = sel.head;
280
+ if (!sel.head.inText)
281
+ while (!index) {
282
+ if (scan.node.type.isolating || !scan.parent)
283
+ return false;
284
+ index = scan.index;
285
+ scan = scan.parent;
286
+ pos--;
287
+ }
288
+ let next = sel.head.inText ? sel.head.nodeBefore : scan.node.content[--index];
289
+ for (;;) {
290
+ if (next.isPlot && next.type.isolating)
291
+ return false;
292
+ if (next.isLeaf || state.isAtom(next.type))
293
+ break;
294
+ let last = next.content.length - 1;
295
+ if (last < 0)
296
+ return false;
297
+ next = next.content[last];
298
+ pos--;
299
+ }
300
+ if (next.is(Leaf.Text)) {
301
+ let size = 0;
302
+ if (word) {
303
+ for (let i = next.param.length, type;;) {
304
+ let ch = next.param[i - 1];
305
+ if (/\s/.test(ch)) {
306
+ if (type)
307
+ break;
308
+ }
309
+ else {
310
+ let next = /[\p{Alphabetic}\p{Number}]/u.test(ch) ? "a" : "p";
311
+ if (!type)
312
+ type = next;
313
+ else if (type != next)
314
+ break;
315
+ }
316
+ i--;
317
+ size++;
318
+ if (i == 0) {
319
+ if (!index)
320
+ break;
321
+ next = scan.node.content[--index];
322
+ if (!next.is(Leaf.Text))
323
+ break;
324
+ i = next.param.length;
325
+ }
326
+ }
327
+ }
328
+ else {
329
+ size = next.length - findClusterBreak(next.param, next.length, false);
330
+ }
331
+ return {
332
+ changes: { from: pos - size, to: pos },
333
+ scrollIntoView: true,
334
+ userEvent: "delete.backward"
335
+ };
336
+ }
337
+ let from = pos - next.length, to = pos;
338
+ let parent = state.doc.resolve(pos).parent;
339
+ while (parent && parent.node.type.isBlock && parent.node.content.length == 1) {
340
+ if (!parent.parent)
341
+ return false;
342
+ parent = parent.parent;
343
+ from--;
344
+ to++;
345
+ }
346
+ return {
347
+ changes: { from, to },
348
+ scrollIntoView: true,
349
+ userEvent: "delete.backward"
350
+ };
351
+ }
352
+ function deleteForward(state, word = false) {
353
+ if (!state.selection.isCursor)
354
+ return false;
355
+ let sel = state.sel;
356
+ let { parent: scan, index, pos } = sel.head;
357
+ if (!sel.head.inText)
358
+ while (index == scan.node.content.length) {
359
+ if (scan.node.type.isolating || !scan.parent)
360
+ return false;
361
+ index = scan.index + 1;
362
+ scan = scan.parent;
363
+ pos++;
364
+ }
365
+ let next = sel.head.inText ? sel.head.nodeAfter : scan.node.content[index];
366
+ for (;;) {
367
+ if (next.isPlot && next.type.isolating)
368
+ return false;
369
+ if (next.isLeaf || state.isAtom(next.type))
370
+ break;
371
+ if (!next.content.length)
372
+ return false;
373
+ next = next.content[0];
374
+ pos++;
375
+ }
376
+ if (next.is(Leaf.Text)) {
377
+ let size = 0;
378
+ if (word) {
379
+ for (let i = 0, type;;) {
380
+ let ch = next.param[i];
381
+ if (/\s/.test(ch)) {
382
+ if (type)
383
+ break;
384
+ }
385
+ else {
386
+ let next = /[\p{Alphabetic}\p{Number}]/u.test(ch) ? "a" : "p";
387
+ if (!type)
388
+ type = next;
389
+ else if (type != next)
390
+ break;
391
+ }
392
+ i++;
393
+ size++;
394
+ if (i == next.param.length) {
395
+ if (index == scan.node.content.length - 1)
396
+ break;
397
+ next = scan.node.content[++index];
398
+ if (!next.is(Leaf.Text))
399
+ break;
400
+ i = 0;
401
+ }
402
+ }
403
+ }
404
+ else {
405
+ size = findClusterBreak(next.param, 0);
406
+ }
407
+ return {
408
+ changes: { from: pos, to: pos + size },
409
+ scrollIntoView: true,
410
+ userEvent: "delete.forward"
411
+ };
412
+ }
413
+ let from = pos, to = pos + next.length;
414
+ let parent = state.doc.resolve(pos).parent;
415
+ while (parent && parent.node.type.isBlock && parent.node.content.length == 1) {
416
+ if (!parent.parent)
417
+ return false;
418
+ parent = parent.parent;
419
+ from--;
420
+ to++;
421
+ }
422
+ return {
423
+ changes: { from, to },
424
+ scrollIntoView: true,
425
+ userEvent: "delete.forward"
426
+ };
427
+ }
428
+ function selectedTextblocks(state) {
429
+ let textblocks = [], lastBlock = -1;
430
+ for (let { from, to } of state.selection.ranges) {
431
+ state.doc.iterate(from, to, (node, pos, parent) => {
432
+ if (node.isPlot && node.isTextblock && pos > lastBlock) {
433
+ textblocks.push(state.doc.resolveNode(pos));
434
+ lastBlock = pos;
435
+ }
436
+ });
437
+ }
438
+ return textblocks;
439
+ }
440
+ function clearNonFitting(schema, node, type) {
441
+ let changes = [];
442
+ for (let i = 0, pos = node.start; i < node.node.content.length; i++) {
443
+ let child = node.node.content[i], end = pos + child.length;
444
+ if (!schema.canContain(type, child.type))
445
+ changes.push({ from: pos, to: end });
446
+ pos = end;
447
+ }
448
+ return changes;
449
+ }
450
+ function findWrappable(from, to, wrapper) {
451
+ let dFrom = from.depth, dTo = to.depth;
452
+ let pFrom = from.parent, pTo = to.parent;
453
+ while (dFrom > dTo) {
454
+ pFrom = pFrom.parent;
455
+ dFrom--;
456
+ }
457
+ while (dTo > dFrom) {
458
+ pTo = pTo.parent;
459
+ dTo--;
460
+ }
461
+ let { schema } = from.doc;
462
+ for (;;) {
463
+ if (!pFrom.parent || pFrom.node.type.isolating)
464
+ return null;
465
+ if (pFrom.parent.start == pTo.parent.start && schema.canContain(pFrom.parent.node.type, wrapper.type))
466
+ break;
467
+ pFrom = pFrom.parent;
468
+ pTo = pTo.parent;
469
+ }
470
+ for (let i = pFrom.index; i < pTo.index + 1; i++) {
471
+ let ch = pFrom.parent.node.content[i];
472
+ if (!schema.findWrapping(wrapper.type, ch.type))
473
+ return null;
474
+ }
475
+ return { from: Pos.create(pFrom.parent, pFrom.before, pFrom.index, 0),
476
+ to: Pos.create(pFrom.parent, pTo.after, pTo.index + 1, 0) };
477
+ }
478
+ function wrapBlockRange(range, wrapper) {
479
+ let changes = [], parent = range.from.parent.node;
480
+ for (let i = range.from.index, openWrappers = 0, pos = range.from.pos;; i++) {
481
+ let tokens = [];
482
+ for (let j = 0; j < openWrappers; j++)
483
+ tokens.push(Plot.End);
484
+ if (i == range.from.index) {
485
+ tokens.push(wrapper);
486
+ }
487
+ else if (i == range.to.index) {
488
+ tokens.push(Plot.End);
489
+ changes.push({ from: pos, insert: tokens });
490
+ break;
491
+ }
492
+ let child = parent.content[i];
493
+ let { schema } = range.from.doc;
494
+ let wrapping = schema.findWrapping(wrapper.type, child.type);
495
+ for (let tag of wrapping)
496
+ tokens.push(tag);
497
+ openWrappers = wrapping.length;
498
+ changes.push({ from: pos, insert: tokens });
499
+ pos += child.length;
500
+ }
501
+ return changes;
502
+ }
503
+ function textblockChild(schema, type) {
504
+ let wrap = schema.findWrapping(type, Leaf.Text);
505
+ return wrap && wrap.length == 1 ? wrap[0] : null;
506
+ }
507
+ function findUnwrappable(schema, from, to, query) {
508
+ let dFrom = from.depth, dTo = to.depth;
509
+ let fromStart = from.parent.node.inlineContent ? from.parent.start : from.pos;
510
+ let fromTextblock = from.textblockParent?.node.type;
511
+ let toEnd = to.parent.node.inlineContent ? to.parent.end : to.pos;
512
+ let innerCandidates = [];
513
+ let outerCandidates = [];
514
+ let { doc } = from;
515
+ doc.iterate(fromStart, toEnd, (node, p, parent) => {
516
+ if (node.type.isBlock && node.isPlot && !node.inlineContent && parent &&
517
+ (fromTextblock ? doc.schema.canContain(parent.type, fromTextblock) : textblockChild(doc.schema, parent.type)) &&
518
+ (!query || schema.matchNode(node.type, query))) {
519
+ let pos = doc.resolveNode(p), depth = pos.depth;
520
+ if (pos.before >= fromStart - (dFrom - depth + 1) && pos.after <= toEnd + (dTo - depth + 1))
521
+ innerCandidates.push(pos);
522
+ else
523
+ outerCandidates.push(pos);
524
+ }
525
+ });
526
+ let candidates = innerCandidates.length
527
+ ? innerCandidates.sort((a, b) => (b.after - b.before) - (a.after - a.before))
528
+ : outerCandidates.sort((a, b) => (a.after - a.before) - (b.after - b.before));
529
+ if (!candidates.length)
530
+ return null;
531
+ for (let i = 1; i < candidates.length; i++) {
532
+ let cur = candidates[i];
533
+ for (let j = 0; j < i; j++) {
534
+ let other = candidates[j];
535
+ if (cur.after > other.before && cur.before < other.after) {
536
+ candidates.splice(i--, 1);
537
+ break;
538
+ }
539
+ }
540
+ }
541
+ return candidates;
542
+ }
543
+ function doUnwrapBlock(block, from, to) {
544
+ let changes = [], { schema } = block.doc;
545
+ let outer = block.parent.node, wrapText = textblockChild(schema, outer.type);
546
+ let gapStart = block.before;
547
+ let skippedDepth = 0;
548
+ let replaceGap = (to, tokens) => {
549
+ for (let i = 0; i < skippedDepth; i++)
550
+ tokens.unshift(Plot.End);
551
+ skippedDepth = 0;
552
+ if (to > gapStart || tokens.length)
553
+ changes.push({ from: gapStart, to, insert: tokens });
554
+ };
555
+ let parent = block, index = 0, pos = block.start;
556
+ for (;;) {
557
+ if (index == parent.node.content.length) {
558
+ if (parent == block) { let tokens = [];
559
+ if (gapStart == block.before && outer.content.length == 1) {
560
+ let deflt = schema.createDefault(outer.type);
561
+ if (deflt)
562
+ tokens.push(deflt);
563
+ }
564
+ replaceGap(block.after, tokens);
565
+ break;
566
+ }
567
+ else { if (gapStart == pos && skippedDepth > 0) {
568
+ gapStart++;
569
+ skippedDepth--;
570
+ }
571
+ pos++;
572
+ index = parent.index + 1;
573
+ parent = parent.parent;
574
+ }
575
+ }
576
+ else {
577
+ let next = parent.node.content[index];
578
+ if (schema.canContain(outer.type, next.type) || wrapText && next.isPlot && next.inlineContent) {
579
+ if (from != null && pos + next.length <= from) { pos += next.length;
580
+ gapStart = pos;
581
+ skippedDepth = 1;
582
+ for (let cx = parent; cx != block; cx = cx.parent)
583
+ skippedDepth++;
584
+ index++;
585
+ }
586
+ else if (to != null && pos >= to) { let tokens = [], upto = pos;
587
+ for (let cx = parent, i = tokens.length, atStart = !index;; cx = cx.parent) {
588
+ if (cx.index > 0)
589
+ atStart = false;
590
+ if (atStart)
591
+ upto--;
592
+ else
593
+ tokens.splice(i, 0, cx.node.tag.split(false));
594
+ if (cx == block)
595
+ break;
596
+ }
597
+ replaceGap(upto, tokens);
598
+ break;
599
+ }
600
+ else {
601
+ if (schema.canContain(outer.type, next.type)) {
602
+ replaceGap(pos, []);
603
+ }
604
+ else {
605
+ replaceGap(pos + 1, [wrapText]);
606
+ changes.push(clearNonFitting(schema, Pos.Plot.create(parent, next, pos, index), wrapText.type));
607
+ }
608
+ pos += next.length;
609
+ index++;
610
+ gapStart = pos;
611
+ }
612
+ }
613
+ else if (next.isLeaf || next.type.isolating) {
614
+ pos += next.length;
615
+ index++;
616
+ }
617
+ else {
618
+ parent = Pos.Plot.create(parent, next, pos, index);
619
+ index = 0;
620
+ pos++;
621
+ }
622
+ }
623
+ }
624
+ return changes;
625
+ }
626
+ function joinBlocks(before, after) {
627
+ let changes = [{ from: before.end, to: after.start }];
628
+ let dBefore = before.depth, dAfter = after.depth;
629
+ let tokensAfter = [], posAfter = after.after, end = posAfter;
630
+ if (dBefore > dAfter) {
631
+ let extraContext = [];
632
+ for (let i = dBefore - dAfter, level = before.parent; i > 0; i--, level = level.parent)
633
+ extraContext.push(level.node.tag);
634
+ let nodeAfter = after.nextSibling;
635
+ for (let i = dBefore - dAfter - 1, joining = true; i >= 0; i--) {
636
+ let context = extraContext[i];
637
+ if (!joining || !nodeAfter || nodeAfter.isLeaf || nodeAfter.type != context.type || !context.type.spec.autoJoin ||
638
+ (typeof context.type.spec.autoJoin == "function" && !context.type.spec.autoJoin(context, nodeAfter.tag)))
639
+ joining = false;
640
+ if (joining)
641
+ end++;
642
+ else
643
+ tokensAfter.push(Plot.End);
644
+ }
645
+ }
646
+ else if (dAfter > dBefore) {
647
+ for (let i = dAfter - dBefore, level = after, atEnd = true; i > 0; i--, level = level.parent) {
648
+ if (level.nextSibling)
649
+ atEnd = false;
650
+ if (atEnd)
651
+ end++;
652
+ else
653
+ tokensAfter.push(level.parent.node.tag);
654
+ }
655
+ }
656
+ if (tokensAfter.length || end > posAfter)
657
+ changes.push({ from: posAfter, to: end, insert: tokensAfter });
658
+ return changes;
659
+ }
660
+ function canAddMarkInRange(doc, from, to, mark) {
661
+ let found = false, type = mark instanceof Mark.Type ? mark : mark.type;
662
+ doc.iterate(from, to, node => {
663
+ if (found || mark.isInSet(node.tag.marks))
664
+ return false;
665
+ if (doc.schema.markAllowed(type, node.type))
666
+ found = true;
667
+ return true;
668
+ });
669
+ return found;
670
+ }
671
+ function autoJoinBlocks(state, tr) {
672
+ if (!tr.changes)
673
+ return tr;
674
+ let changes = ChangeSet.create(state.doc, tr.changes), doc = changes.apply(state.doc);
675
+ if (changes.empty)
676
+ return tr;
677
+ let append = [];
678
+ let cursor = doc.resolve(0), check = (pos) => {
679
+ cursor = cursor.advance(pos - cursor.pos);
680
+ let before = cursor.nodeBefore, after = cursor.nodeAfter;
681
+ if (before && after && before.isPlot && before.type.isBlock && after.isPlot && after.type == before.type) {
682
+ let { autoJoin } = after.type.spec;
683
+ if (autoJoin && (typeof autoJoin != "function" || autoJoin(before.tag, after.tag))) {
684
+ let from = pos - 1, to = pos + 1;
685
+ for (;;) {
686
+ let last = before.lastChild, first = after.firstChild;
687
+ if (!first || !last || first.isLeaf || last.isLeaf || first.type != last.type || first.type.isInline)
688
+ break;
689
+ autoJoin = last.type.spec.autoJoin;
690
+ if (!autoJoin || (typeof autoJoin == "function" && !autoJoin(last.tag, first.tag)))
691
+ break;
692
+ from--, to++;
693
+ before = last;
694
+ after = first;
695
+ }
696
+ append.push({ from, to });
697
+ }
698
+ }
699
+ };
700
+ changes.iterGaps(() => { }, (_fromA, _toA, fromB, toB) => {
701
+ check(fromB);
702
+ if (toB > fromB)
703
+ check(toB);
704
+ });
705
+ if (!append.length)
706
+ return { ...tr, changes };
707
+ return Transaction.merge(state, tr, { changes: append, sequential: true });
708
+ }
709
+
710
+ const insertText = ({ state }, { from, to, insert, userEvent }) => {
711
+ if (state.readOnly)
712
+ return false;
713
+ let { selection } = state;
714
+ let marks = (from == selection.from && to == selection.to && state.sel.activeMarks) ||
715
+ state.doc.resolve(from).marks(state.doc.resolve(to));
716
+ return {
717
+ changes: { from, to, insert: [Leaf.Text.of(insert, marks)], fit: true },
718
+ scrollIntoView: true,
719
+ selection: (cx, changes) => GardSelection.near(cx, changes.mapPos(to, 1), -1),
720
+ userEvent
721
+ };
722
+ };
723
+ const insertLineBreak = ({ state }) => {
724
+ if (state.readOnly)
725
+ return false;
726
+ let { doc, sel } = state;
727
+ let brk = doc.schema.lineBreak, parent = sel.from.parent.node.type;
728
+ let { from, to } = state.selection.replacementRange;
729
+ let insertBreak = brk && doc.schema.canContain(parent, brk.type);
730
+ if (!(insertBreak || parent.preserveWhitespace && sel.to.parent.start == sel.from.parent.start))
731
+ return false;
732
+ let insert = insertBreak ? brk.withMarks(state.sel.activeMarks) : Leaf.text("\n", state.sel.activeMarks);
733
+ let changes = ChangeSet.create(state.doc, { from, to, insert: [insert], fit: true });
734
+ let pos = changes.findInserted(t => insertBreak ? t.type == brk.type : t.isText);
735
+ return {
736
+ changes,
737
+ selection: GardSelection.cursor(pos == null ? from : pos + 1, -1),
738
+ scrollIntoView: true,
739
+ userEvent: insertBreak ? "insert.linebreak" : "input"
740
+ };
741
+ };
742
+ const enter = ({ state }) => {
743
+ if (state.readOnly)
744
+ return false;
745
+ let { sel, doc } = state;
746
+ if (!sel.head.parent.node.inlineContent || !sel.anchor.parent.node.inlineContent) {
747
+ let { from, to } = sel.replacementRange;
748
+ let wrap = doc.schema.findWrapping(from.parent.node.type, Leaf.Text);
749
+ if (!wrap)
750
+ return false;
751
+ let content = [];
752
+ for (let i = wrap.length - 1; i >= 0; i--)
753
+ content = [wrap[i].create(content)];
754
+ let changes = ChangeSet.create(state.doc, { from: from.pos, to: to.pos, insert: content, fit: true });
755
+ let placed = content.length ? changes.findInserted(t => t == content[0].tag) : null;
756
+ return {
757
+ changes,
758
+ selection: placed != null ? (cx => GardSelection.near(cx, placed + wrap.length, -1)) : undefined,
759
+ scrollIntoView: true,
760
+ userEvent: "insert.textblock"
761
+ };
762
+ }
763
+ return liftEmptyBlock(state) || splitTextblock(state);
764
+ };
765
+ const deleteUnit = ({ state }, dir) => {
766
+ if (state.readOnly)
767
+ return false;
768
+ return deleteSelection(state) || (dir == "forward"
769
+ ? joinForward(state) || deleteForward(state) || deleteEmptyTextblock(state, 1)
770
+ : joinListItems(state) || joinBackward(state) || deleteBackward(state) || deleteEmptyTextblock(state, -1));
771
+ };
772
+ const deleteWord = ({ state }, dir) => {
773
+ if (state.readOnly)
774
+ return false;
775
+ return deleteSelection(state) || (dir == "forward"
776
+ ? joinForward(state) || deleteForward(state, true) || deleteEmptyTextblock(state, 1)
777
+ : joinListItems(state) || joinBackward(state) || deleteBackward(state, true) || deleteEmptyTextblock(state, -1));
778
+ };
779
+ const deleteToLineEnd = (wg, dir) => {
780
+ if (wg.state.readOnly)
781
+ return false;
782
+ let tr = deleteSelection(wg.state), { selection } = wg.state;
783
+ if (tr)
784
+ return (wg.dispatch(tr), true);
785
+ if (!(selection instanceof GardSelection.Text))
786
+ return false;
787
+ let end = wg.moveToLineBoundary(selection, dir == "forward");
788
+ if (!end || end.head == selection.head)
789
+ return false;
790
+ return {
791
+ changes: { correct: dir == "forward" ? { from: selection.head, to: end.head } : { from: end.head, to: selection.head } },
792
+ scrollIntoView: true,
793
+ userEvent: "delete." + dir
794
+ };
795
+ };
796
+ const deleteLine = wg => {
797
+ if (wg.state.readOnly)
798
+ return false;
799
+ let tr = deleteSelection(wg.state), { selection } = wg.state;
800
+ if (tr)
801
+ return (wg.dispatch(tr), true);
802
+ if (!(selection instanceof GardSelection.Text))
803
+ return false;
804
+ let start = wg.moveToLineBoundary(selection, false), end = wg.moveToLineBoundary(selection, true);
805
+ if (!start || !end || start.head >= end.head)
806
+ return false;
807
+ return {
808
+ changes: { correct: { from: start.head, to: end.head } },
809
+ scrollIntoView: true,
810
+ userEvent: "delete.line"
811
+ };
812
+ };
813
+ const transposeChars = ({ state }) => {
814
+ if (state.readOnly || !state.selection.isCursor)
815
+ return false;
816
+ let { sel } = state, head = state.selection.head;
817
+ let before = sel.head.nodeBefore, after = sel.head.nodeAfter;
818
+ if (!before || !before.is(Leaf.Text) || !after || !after.is(Leaf.Text))
819
+ return false;
820
+ let lenBefore = before.param.length - findClusterBreak(before.param, before.param.length, false);
821
+ let lenAfter = findClusterBreak(after.param, 0);
822
+ return {
823
+ changes: [{ from: head - lenBefore, to: head },
824
+ { from: head + lenAfter, insert: [Leaf.text(before.param.slice(before.param.length - lenBefore))] }],
825
+ selection: GardSelection.cursor(head + lenAfter, -1),
826
+ scrollIntoView: true,
827
+ userEvent: "transpose"
828
+ };
829
+ };
830
+ const setTextblockType = ({ state }, tag) => {
831
+ if (state.readOnly)
832
+ return false;
833
+ let changes = [], { schema } = state.doc;
834
+ for (let block of selectedTextblocks(state)) {
835
+ if (!block.node.tag.eq(tag) && block.parent && schema.canContain(block.parent.node.type, tag.type)) {
836
+ changes.push({ from: block.before, to: block.before + 1, insert: [schema.withMarksFrom(block.node.tag, tag)] });
837
+ changes.push(clearNonFitting(schema, block, tag.type));
838
+ }
839
+ }
840
+ if (!changes.length)
841
+ return false;
842
+ return autoJoinBlocks(state, { changes, scrollIntoView: true, userEvent: "settype" });
843
+ };
844
+ const unwrapBlock = ({ state }, query) => {
845
+ if (state.readOnly)
846
+ return false;
847
+ let targets = [], changes = [];
848
+ for (let { from, to } of state.selection.ranges) {
849
+ if (!targets.some(t => t.after > from && t.before < to)) {
850
+ let result = findUnwrappable(state.schema, state.doc.resolve(from), state.doc.resolve(to), query ?? undefined);
851
+ if (result)
852
+ for (let node of result) {
853
+ targets.push(node);
854
+ changes.push(doUnwrapBlock(node, from, to));
855
+ }
856
+ }
857
+ }
858
+ if (!targets.length)
859
+ return false;
860
+ return autoJoinBlocks(state, {
861
+ changes,
862
+ scrollIntoView: true,
863
+ userEvent: "unwrap"
864
+ });
865
+ };
866
+ const wrapBlock = ({ state }, wrapper) => {
867
+ if (state.readOnly)
868
+ return false;
869
+ let changes = [], lastTo = -1;
870
+ for (let { from, to } of state.selection.ranges) {
871
+ let range = findWrappable(state.doc.resolve(from), state.doc.resolve(to), wrapper);
872
+ if (!range || range.from.pos < lastTo)
873
+ continue;
874
+ changes.push(wrapBlockRange(range, wrapper));
875
+ lastTo = range.to.pos;
876
+ }
877
+ if (!changes.length)
878
+ return false;
879
+ return autoJoinBlocks(state, { changes, scrollIntoView: true, userEvent: "wrap" });
880
+ };
881
+ const toggleBlock = (target, tag) => {
882
+ return unwrapBlock(target, tag) || wrapBlock(target, tag);
883
+ };
884
+ const toggleMark = ({ state }, mark) => {
885
+ if (state.readOnly)
886
+ return false;
887
+ let { selection, doc } = state;
888
+ if (selection instanceof GardSelection.Text && selection.empty) {
889
+ let selMarks = selection.marks || state.sel.head.marks(), add = !mark.isInSet(selMarks);
890
+ let newMarks = add ? mark.addToSet(selMarks) : mark.removeFromSet(selMarks);
891
+ return {
892
+ selection: GardSelection.Text.create({ anchor: selection.anchor, headSide: selection.headSide,
893
+ goalColumn: selection.goalColumn, marks: newMarks }),
894
+ userEvent: add ? "mark.add" : "mark.remove"
895
+ };
896
+ }
897
+ else if (selection.ranges.some(r => canAddMarkInRange(doc, r.from, r.to, mark))) {
898
+ return {
899
+ changes: selection.ranges.map(r => ({ from: r.from, to: r.to, add: mark })),
900
+ userEvent: "mark.add"
901
+ };
902
+ }
903
+ else {
904
+ return {
905
+ changes: selection.ranges.map(r => ({ from: r.from, to: r.to, remove: mark })),
906
+ userEvent: "mark.remove"
907
+ };
908
+ }
909
+ };
910
+ const toggleEmphasis = target => toggleMark(target, Emphasis);
911
+ const toggleStrong = target => toggleMark(target, Strong);
912
+ const toggleUnderline = target => toggleMark(target, Underline);
913
+ const setAlignment = ({ state }, align) => {
914
+ let { schema } = state.doc;
915
+ if (state.readOnly || !schema.has(Alignment))
916
+ return false;
917
+ if (align == "start")
918
+ align = null;
919
+ if (align == "left" || align == "right")
920
+ align = ltrAtCursor(state) == (align == "left") ? null : "end";
921
+ let changes = [];
922
+ for (let block of selectedTextblocks(state)) {
923
+ let cur = block.node.tag.mark(Alignment);
924
+ if (cur != align && schema.markAllowed(Alignment, block.node.type))
925
+ changes.push(align ? { from: block.before, add: Alignment.of(align) } : { from: block.before, remove: Alignment.of(cur) });
926
+ }
927
+ if (!changes.length)
928
+ return false;
929
+ return {
930
+ changes,
931
+ userEvent: "mark.set.alignment",
932
+ };
933
+ };
934
+ const setDirection = ({ state }, dir) => {
935
+ let { schema } = state.doc;
936
+ if (state.readOnly || !schema.has(Direction))
937
+ return false;
938
+ let changes = [];
939
+ for (let block of selectedTextblocks(state)) {
940
+ let cur = block.node.tag.mark(Direction);
941
+ if (cur != dir && schema.markAllowed(Direction, block.node.type))
942
+ changes.push(dir ? { from: block.before, add: Direction.of(dir) } : { from: block.before, remove: Direction.of(cur) });
943
+ }
944
+ if (!changes.length)
945
+ return false;
946
+ return {
947
+ changes,
948
+ userEvent: "mark.set.direction",
949
+ };
950
+ };
951
+ const toggleList = ({ state }, listTag) => {
952
+ if (state.readOnly)
953
+ return false;
954
+ let blocks = selectedTextblocks(state);
955
+ if (!blocks.length)
956
+ return false;
957
+ return addList(state, blocks, listTag) || removeList(state, blocks, listTag);
958
+ };
959
+ const listIsActive = (listTag) => state => {
960
+ return selectedTextblocks(state).every(b => {
961
+ let item = isListItem(b);
962
+ return item && item.parent.node.type == listTag.type;
963
+ });
964
+ };
965
+ function isListItem(node) {
966
+ for (let first = true;;) {
967
+ let { parent } = node;
968
+ if (!parent)
969
+ return null;
970
+ if (parent.node.tag.type.hasRole(Node.Role.List))
971
+ return first ? node : null;
972
+ first = node.isFirst;
973
+ node = parent;
974
+ }
975
+ }
976
+ function autoJoin(a, b) {
977
+ let { autoJoin } = a.type.spec;
978
+ return typeof autoJoin == "function" ? autoJoin(a, b) : typeof autoJoin == "boolean" ? autoJoin : a.eq(b);
979
+ }
980
+ function addList(state, blocks, listTag) {
981
+ let plan = [];
982
+ let chBefore = new Set, chAfter = new Set;
983
+ let lastItem = -1, { schema } = state.doc;
984
+ for (let block of blocks) {
985
+ let item = isListItem(block), wrap;
986
+ if (!item && block.parent && schema.canContain(block.parent.node.type, listTag.type) &&
987
+ ((wrap = schema.findWrapping(listTag.type, block.node.type)) && wrap.length == 1 ||
988
+ (wrap = schema.findWrapping(listTag.type, Leaf.Text)) && wrap.length == 1)) {
989
+ chAfter.add(block.before);
990
+ chBefore.add(block.after);
991
+ plan.push({ wrap: block, item: wrap[0] });
992
+ lastItem = block.before;
993
+ }
994
+ else if (item?.parent && item.parent.node.tag.type != listTag.type &&
995
+ schema.canContain(listTag.type, item.node.type) &&
996
+ item.parent.parent && schema.canContain(item.parent.parent.node.type, listTag.type) &&
997
+ item.before != lastItem) {
998
+ chAfter.add(item.before);
999
+ chBefore.add(item.after);
1000
+ if (item.isFirst)
1001
+ chAfter.add(item.parent.before);
1002
+ if (item.isLast)
1003
+ chBefore.add(item.parent.after);
1004
+ plan.push({ change: block, item });
1005
+ lastItem = item.before;
1006
+ }
1007
+ }
1008
+ if (!plan.length)
1009
+ return false;
1010
+ let changes = [];
1011
+ for (let step of plan) {
1012
+ if ("wrap" in step) { let { wrap, item } = step, prev, next;
1013
+ let openTo = item.isTextblock ? wrap.start : wrap.before, openFrom = wrap.before, open = [item];
1014
+ if (chBefore.has(wrap.before)) ; else if ((prev = wrap.previousSibling) && prev.tag.eq(listTag))
1015
+ openFrom--; else
1016
+ open.unshift(listTag); changes.push({ from: openFrom, to: openTo, insert: open });
1017
+ let closeFrom = item.isTextblock ? wrap.end : wrap.after, closeTo = wrap.after, close = [Plot.End];
1018
+ if (chAfter.has(wrap.after)) ; else if ((next = wrap.nextSibling) && next.isPlot && next.type == listTag.type && autoJoin(next.tag, listTag))
1019
+ closeTo++; else
1020
+ close.push(Plot.End); changes.push({ from: closeFrom, to: closeTo, insert: close });
1021
+ }
1022
+ else { let { item } = step, prev, next;
1023
+ if (item.isFirst) {
1024
+ if (chBefore.has(item.before - 1)) changes.push({ from: item.before - 1, to: item.before });
1025
+ else if ((prev = item.parent.previousSibling) && prev.tag.type == listTag.type) changes.push({ from: item.before - 2, to: item.before });
1026
+ else changes.push({ from: item.before - 1, to: item.before, insert: [listTag] });
1027
+ }
1028
+ else if (!chBefore.has(item.before)) { changes.push({ from: item.before, insert: [Plot.End, listTag] });
1029
+ }
1030
+ if (item.isLast) {
1031
+ if (chAfter.has(item.after + 1)) { changes.push({ from: item.after, to: item.after + 1 });
1032
+ }
1033
+ else if ((next = item.parent.nextSibling) && next.isPlot && autoJoin(next.tag, listTag)) { changes.push({ from: item.after, to: item.after + 2 });
1034
+ }
1035
+ }
1036
+ }
1037
+ }
1038
+ return { changes, userEvent: "wrap.list" };
1039
+ }
1040
+ function removeList(state, blocks, listTag) {
1041
+ let plan = [], lastItem = -1;
1042
+ let chBefore = new Set, chAfter = new Set;
1043
+ let { schema } = state.doc;
1044
+ for (let block of blocks) {
1045
+ let item = isListItem(block);
1046
+ if (!item)
1047
+ continue;
1048
+ let list = item.parent, parent = list.parent, rewrap = null;
1049
+ if (parent && list.node.isPlot && list.node.type == listTag.type && item.before != lastItem &&
1050
+ (item.node.isTextblock
1051
+ ? (rewrap = schema.defaultContentPlot(parent.node.type)) && rewrap.isTextblock
1052
+ : schema.canContain(parent.node.type, block.node.type))) {
1053
+ lastItem = item.before;
1054
+ plan.push({ item, rewrap: rewrap });
1055
+ chAfter.add(item.before);
1056
+ chBefore.add(item.after);
1057
+ }
1058
+ }
1059
+ if (!plan.length)
1060
+ return false;
1061
+ let changes = [];
1062
+ for (let { item, rewrap } of plan) {
1063
+ let openFrom = item.before, openTo = item.start, open = rewrap ? [rewrap] : [];
1064
+ if (item.isFirst)
1065
+ openFrom--;
1066
+ else if (!chBefore.has(item.before))
1067
+ open.unshift(Plot.End);
1068
+ changes.push({ from: openFrom, to: openTo, insert: open });
1069
+ let closeFrom = rewrap ? item.after : item.end, closeTo = item.after, close = [];
1070
+ if (item.isLast)
1071
+ closeTo++;
1072
+ else if (!chAfter.has(item.after))
1073
+ close.push(listTag);
1074
+ changes.push({ from: closeFrom, to: closeTo, insert: close });
1075
+ }
1076
+ return { changes, userEvent: "unwrap.list" };
1077
+ }
1078
+ function setSelection(selection) {
1079
+ return {
1080
+ selection: selection,
1081
+ scrollIntoView: true,
1082
+ userEvent: "select"
1083
+ };
1084
+ }
1085
+ function ltrAtCursor(state) {
1086
+ let block = state.sel.head.textblockParent;
1087
+ return block ? state.textblockLTR(block.node) : state.textLTR;
1088
+ }
1089
+ function isForward(dir, state) {
1090
+ return dir == "forward" ? true : dir == "backward" ? false : (dir == "right") == ltrAtCursor(state);
1091
+ }
1092
+ function asTextSel(sel, forward) {
1093
+ if (sel instanceof GardSelection.Text)
1094
+ return sel;
1095
+ let { from, to } = sel.replacementRange;
1096
+ return forward ? GardSelection.range(from, to) : GardSelection.range(to, from);
1097
+ }
1098
+ function extendSel(base, head) {
1099
+ return GardSelection.range(base.anchor, head.head, head.headSide, head.goalColumn);
1100
+ }
1101
+ const moveByUnit = ({ state }, { dir, extend }) => {
1102
+ let forward = isForward(dir, state), selection = asTextSel(state.selection, forward);
1103
+ if (!selection.empty && !extend) {
1104
+ let next = selection.normalCursorAtBound(state, forward);
1105
+ return next ? setSelection(next) : false;
1106
+ }
1107
+ else {
1108
+ let next = selection.nextNormalCursor(state, forward);
1109
+ if (!next)
1110
+ return false;
1111
+ if (!extend)
1112
+ state.doc.iterate(Math.min(selection.head, next.head), Math.max(selection.head, next.head), (node, pos) => {
1113
+ if (node.type.isSelectable && state.isAtom(node.type))
1114
+ next = GardSelection.node(pos, node);
1115
+ if (node.isPlot)
1116
+ return !node.type.isolating;
1117
+ });
1118
+ return setSelection(extend ? extendSel(selection, next) : next);
1119
+ }
1120
+ };
1121
+ const moveByWord = ({ state }, { dir, extend }) => {
1122
+ let forward = (dir == "right") == ltrAtCursor(state);
1123
+ let selection = asTextSel(state.selection, forward);
1124
+ let moved = selection.skipWord(state, forward);
1125
+ return moved ? setSelection(extend ? extendSel(selection, moved) : moved) : false;
1126
+ };
1127
+ function nextVertical(wg, sel, forward, distance, allowNode) {
1128
+ let next = wg.moveVertically(sel, forward, distance, allowNode);
1129
+ if (next)
1130
+ return next;
1131
+ let end = (forward ? GardSelection.atEnd : GardSelection.atStart)(wg.state);
1132
+ return end.head == wg.state.selection.head ? null : end;
1133
+ }
1134
+ const moveByLine = (wg, { dir, extend }) => {
1135
+ let { state } = wg, { selection } = state, forward = dir == "down";
1136
+ if (state.selection instanceof GardSelection.Node) {
1137
+ let next = !extend && state.selection.normalCursorAtBound(state, forward);
1138
+ if (next && !state.doc.resolve(next.head).parent.node.inlineContent)
1139
+ return setSelection(GardSelection.cursor(next.head, next.headSide, state.selection.goalColumn));
1140
+ selection = GardSelection.cursor(forward ? selection.to : selection.from, undefined, selection.goalColumn);
1141
+ }
1142
+ else {
1143
+ selection = asTextSel(state.selection, forward);
1144
+ }
1145
+ let moved = nextVertical(wg, selection, forward, undefined, !extend);
1146
+ return moved ? setSelection(extend ? extendSel(selection, moved) : moved) : false;
1147
+ };
1148
+ function pageHeight(wg) {
1149
+ let marginTop = 0, marginBottom = 0;
1150
+ for (let source of wg.state.facet(wg.constructor.coveredMargins)) {
1151
+ let margins = source(wg);
1152
+ if (margins?.top)
1153
+ marginTop = Math.max(margins?.top, marginTop);
1154
+ if (margins?.bottom)
1155
+ marginBottom = Math.max(margins?.bottom, marginBottom);
1156
+ }
1157
+ return Math.max(10, Math.min(wg.scrollDOM.clientHeight - marginTop - marginBottom, (wg.dom.ownerDocument.defaultView || window).innerHeight) - 10);
1158
+ }
1159
+ const moveByPage = (wg, { dir, extend }) => {
1160
+ let { state } = wg, { selection } = state, forward = dir == "down";
1161
+ let moved = selection.empty || extend ? nextVertical(wg, selection, forward, pageHeight(wg), !extend)
1162
+ : forward ? GardSelection.cursor(selection.to, -1) : GardSelection.cursor(selection.from, 1);
1163
+ return moved ? setSelection(extend ? extendSel(selection, moved) : moved) : false;
1164
+ };
1165
+ const moveToLineSide = (wg, { dir, extend }) => {
1166
+ let pos = wg.moveToLineBoundary(wg.state.selection, isForward(dir, wg.state));
1167
+ return pos ? setSelection(extend ? extendSel(wg.state.selection, pos) : pos) : false;
1168
+ };
1169
+ const moveToTextblockSide = (wg, { dir, extend }) => {
1170
+ let { state } = wg, block = state.sel.head.textblockParent;
1171
+ if (!block)
1172
+ return false;
1173
+ let pos = isForward(dir, wg.state) ? GardSelection.atEnd(state, block) : GardSelection.atStart(state, block);
1174
+ return setSelection(extend ? extendSel(wg.state.selection, pos) : pos);
1175
+ };
1176
+ const moveToDocSide = (target, { side, extend }) => {
1177
+ let { state } = target;
1178
+ let pos = side == "start" ? GardSelection.atStart(state) : GardSelection.atEnd(state);
1179
+ if (state.selection.empty && pos.head == state.selection.head)
1180
+ return false;
1181
+ return setSelection(extend ? extendSel(state.selection, pos) : pos);
1182
+ };
1183
+ const selectAll = ({ state }) => {
1184
+ return {
1185
+ selection: GardSelection.range(0, state.doc.length),
1186
+ userEvent: "select.all"
1187
+ };
1188
+ };
1189
+ const undo = () => false;
1190
+ const redo = () => false;
1191
+
1192
+ const Menu = /*@__PURE__*/(function (Menu) {
1193
+ let Item;
1194
+ (function (Item) {
1195
+ class Base {
1196
+ select;
1197
+ enable;
1198
+ updateFor;
1199
+ parent;
1200
+ rank;
1201
+ description;
1202
+ extension;
1203
+ constructor(spec) {
1204
+ this.select = spec.select;
1205
+ this.enable = spec.enable;
1206
+ this.updateFor = spec.updateFor;
1207
+ this.parent = spec.parent;
1208
+ this.rank = spec.rank == null ? 100 : Math.max(0, Math.min(100, spec.rank));
1209
+ this.description = spec.description;
1210
+ let src = Menu.Item.source.of(this);
1211
+ this.extension = this.parent ? [src, this.parent] : src;
1212
+ }
1213
+ }
1214
+ Item.Base = Base;
1215
+ Item.source = GardState.Facet.define();
1216
+ })(Item = Menu.Item || (Menu.Item = {}));
1217
+ class Button extends Item.Base {
1218
+ spec;
1219
+ label;
1220
+ run;
1221
+ active;
1222
+ constructor(
1223
+ spec) {
1224
+ super(spec);
1225
+ this.spec = spec;
1226
+ this.run = spec.run;
1227
+ this.active = spec.active;
1228
+ this.label = spec.label;
1229
+ }
1230
+ static define(spec) { return new Button(spec); }
1231
+ }
1232
+ Menu.Button = Button;
1233
+ (function (Button) {
1234
+ function toggleMark$1(config) {
1235
+ let { mark, parent, rank, description, label } = config;
1236
+ return Menu.Button.define({
1237
+ run: Command.bind(toggleMark, mark),
1238
+ active(state) {
1239
+ let { selection } = state;
1240
+ if (selection.isCursor)
1241
+ return !!mark.isInSet(state.sel.activeMarks);
1242
+ else
1243
+ return !selection.ranges.some(r => canAddMarkInRange(state.doc, r.from, r.to, mark));
1244
+ },
1245
+ enable: s => !s.readOnly,
1246
+ parent,
1247
+ rank,
1248
+ description,
1249
+ label
1250
+ });
1251
+ }
1252
+ Button.toggleMark = toggleMark$1;
1253
+ })(Button = Menu.Button || (Menu.Button = {}));
1254
+ class CustomControl extends Item.Base {
1255
+ spec;
1256
+ render;
1257
+ setEnabled;
1258
+ constructor(
1259
+ spec) {
1260
+ super(spec);
1261
+ this.spec = spec;
1262
+ this.render = spec.render;
1263
+ this.setEnabled = spec.setEnabled;
1264
+ }
1265
+ static define(spec) { return new CustomControl(spec); }
1266
+ }
1267
+ Menu.CustomControl = CustomControl;
1268
+ class Group {
1269
+ spec;
1270
+ margin;
1271
+ parent;
1272
+ rank;
1273
+ content;
1274
+ overflow;
1275
+ extension;
1276
+ constructor(
1277
+ spec) {
1278
+ this.spec = spec;
1279
+ this.margin = !!spec.margin;
1280
+ this.extension = Item.source.of(this);
1281
+ this.parent = spec.parent;
1282
+ this.rank = spec.rank == null ? 100 : Math.max(0, Math.min(100, spec.rank));
1283
+ this.content = spec.content;
1284
+ this.overflow = spec.overflow;
1285
+ }
1286
+ static define(spec = {}) { return new Group(spec); }
1287
+ template(...content) {
1288
+ return Template.new(this, content.length ? content : ["..."]);
1289
+ }
1290
+ }
1291
+ Menu.Group = Group;
1292
+ (function (Group) {
1293
+ Group.top = Group.define();
1294
+ Group.commands = Group.define({ parent: Group.top, rank: 30 });
1295
+ Group.inline = Group.define({ parent: Group.top, rank: 50, margin: true, overflow: { at: 5 } });
1296
+ Group.block = Group.define({ parent: Group.top, rank: 70, margin: true });
1297
+ Group.insert = Group.define({ parent: Group.top, rank: 90, margin: true });
1298
+ })(Group = Menu.Group || (Menu.Group = {}));
1299
+ class Submenu extends Item.Base {
1300
+ spec;
1301
+ label;
1302
+ defaultLabel;
1303
+ arrow;
1304
+ width;
1305
+ content;
1306
+ constructor(
1307
+ spec) {
1308
+ super(spec);
1309
+ this.spec = spec;
1310
+ this.label = spec.label;
1311
+ this.defaultLabel = spec.defaultLabel;
1312
+ this.arrow = spec.arrow !== false;
1313
+ this.width = spec.width;
1314
+ this.content = spec.content;
1315
+ }
1316
+ static define(spec) { return new Submenu(spec); }
1317
+ template(...content) {
1318
+ return Template.new(this, content.length ? content : ["..."]);
1319
+ }
1320
+ }
1321
+ Menu.Submenu = Submenu;
1322
+ (function (Submenu) {
1323
+ class Resolved {
1324
+ item;
1325
+ content;
1326
+ constructor(
1327
+ item,
1328
+ content) {
1329
+ this.item = item;
1330
+ this.content = content;
1331
+ }
1332
+ static new(item, content) { return new Resolved(item, content); }
1333
+ }
1334
+ Submenu.Resolved = Resolved;
1335
+ Submenu.textblockStyle = Menu.Submenu.define({
1336
+ defaultLabel: phrases.ref("block_style"),
1337
+ description: phrases.ref("block_style"),
1338
+ parent: Group.top,
1339
+ rank: 10,
1340
+ width: 10,
1341
+ });
1342
+ })(Submenu = Menu.Submenu || (Menu.Submenu = {}));
1343
+ class Template {
1344
+ item;
1345
+ content;
1346
+ parent;
1347
+ rank;
1348
+ constructor(
1349
+ item,
1350
+ content) {
1351
+ this.item = item;
1352
+ this.content = content;
1353
+ this.parent = item.parent ?? null;
1354
+ this.rank = item.rank ?? 100;
1355
+ }
1356
+ static new(item, content = []) {
1357
+ return new Template(item, content);
1358
+ }
1359
+ }
1360
+ Menu.Template = Template;
1361
+ const defaultOverflow = Submenu.define({
1362
+ label: {
1363
+ icon: "M57 77a8 8 0 1 1-16 0 8 8 0 0 1 16 0m0-26a8 8 0 1 1-16 0 8 8 0 0 1 16 0m0-26a8 8 0 1 1-16 0 8 8 0 0 1 16 0"
1364
+ },
1365
+ description: phrases.ref("overflow_more"),
1366
+ arrow: false
1367
+ });
1368
+ function resolve(items, template = Group.top.template(), suppress) {
1369
+ let used = new Map();
1370
+ if (suppress)
1371
+ for (let item of suppress)
1372
+ used.set(item, 2);
1373
+ function scan(template) {
1374
+ used.set(template.item, 1);
1375
+ for (let child of template.content) {
1376
+ if (child instanceof Template)
1377
+ scan(child);
1378
+ else if (typeof child != "string")
1379
+ used.set(child, 1);
1380
+ }
1381
+ }
1382
+ function margin(target) {
1383
+ if (target.length && target[target.length - 1] !== "|")
1384
+ target.push("|");
1385
+ }
1386
+ function resolve(template, content, target, fromTemplate) {
1387
+ if (template instanceof Template) {
1388
+ resolve(template.item, template.content, target, true);
1389
+ }
1390
+ else {
1391
+ let wasUsed = used.get(template);
1392
+ if (fromTemplate ? wasUsed == 2 : wasUsed != null)
1393
+ return;
1394
+ used.set(template, 2);
1395
+ if (template instanceof Submenu || template instanceof Group) {
1396
+ if (template instanceof Group && template.margin)
1397
+ margin(target);
1398
+ let inner = [];
1399
+ for (let elt of content || template.content || ["..."]) {
1400
+ if (elt === "...") {
1401
+ let found = items.filter(i => i.parent == template);
1402
+ for (let item of found.sort((a, b) => (a.rank ?? 100) - (b.rank ?? 100)))
1403
+ resolve(item, null, inner, false);
1404
+ }
1405
+ else {
1406
+ resolve(elt, null, inner, fromTemplate);
1407
+ }
1408
+ }
1409
+ if (inner.length) {
1410
+ if (template instanceof Submenu) {
1411
+ if (inner[inner.length - 1] === "|")
1412
+ inner.pop();
1413
+ if (inner.length)
1414
+ target.push(Submenu.Resolved.new(template, inner));
1415
+ }
1416
+ else { if (template.overflow && inner.length > template.overflow.at) {
1417
+ let overflow = Submenu.Resolved.new(template.overflow.wrap || defaultOverflow, inner.slice(template.overflow.at - 1).filter(e => e != "|"));
1418
+ inner.length = template.overflow.at - 1;
1419
+ inner.push(overflow);
1420
+ }
1421
+ for (let elt of inner)
1422
+ target.push(elt);
1423
+ }
1424
+ }
1425
+ if (template instanceof Group && template.margin)
1426
+ margin(target);
1427
+ }
1428
+ else {
1429
+ target.push(template);
1430
+ }
1431
+ }
1432
+ }
1433
+ let top = [];
1434
+ if (Array.isArray(template)) {
1435
+ for (let elt of template)
1436
+ scan(elt);
1437
+ for (let elt of template)
1438
+ resolve(elt, null, top, true);
1439
+ }
1440
+ else {
1441
+ scan(template);
1442
+ resolve(template, null, top, true);
1443
+ }
1444
+ if (top.length && top[top.length - 1] === "|")
1445
+ top.pop();
1446
+ return top;
1447
+ }
1448
+ Menu.resolve = resolve;
1449
+ ;return Menu})({});
1450
+
1451
+ export { Command, Menu, autoJoinBlocks, canAddMarkInRange, clearNonFitting, deleteBackward, deleteEmptyTextblock, deleteForward, deleteLine, deleteSelection, deleteToLineEnd, deleteUnit, deleteWord, doUnwrapBlock, enter, findUnwrappable, findWrappable, insertLineBreak, insertText, joinBackward, joinBlocks, joinForward, joinListItems, liftEmptyBlock, listIsActive, moveByLine, moveByPage, moveByUnit, moveByWord, moveToDocSide, moveToLineSide, moveToTextblockSide, redo, selectAll, selectedTextblocks, setAlignment, setDirection, setTextblockType, splitTextblock, toggleBlock, toggleEmphasis, toggleList, toggleMark, toggleStrong, toggleUnderline, transposeChars, undo, unwrapBlock, wrapBlock, wrapBlockRange };