@lofcz/platejs-core 52.3.6 → 53.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{index-CLvWpTKx.d.ts → index-C-LKDYK-.d.ts} +504 -54
- package/dist/index.d.ts +2 -2
- package/dist/index.js +105 -2
- package/dist/react/index.d.ts +100 -84
- package/dist/react/index.js +49 -6
- package/dist/static/index.d.ts +1 -1
- package/dist/static/index.js +2 -2
- package/dist/{static-DIYyt_jS.js → static-HrbPXDQ1.js} +2 -2
- package/dist/{withSlate-BfRR5wTZ.js → withSlate-DsAgt7dN.js} +846 -26
- package/package.json +4 -4
|
@@ -112,6 +112,7 @@ function createSlatePlugin(config = {}) {
|
|
|
112
112
|
render: {},
|
|
113
113
|
rules: {},
|
|
114
114
|
shortcuts: {},
|
|
115
|
+
inputRules: [],
|
|
115
116
|
transforms: {}
|
|
116
117
|
}, config);
|
|
117
118
|
if (plugin.node.isLeaf && !isDefined(plugin.node.isDecoration)) plugin.node.isDecoration = true;
|
|
@@ -283,6 +284,11 @@ function getEditorPlugin(editor, p) {
|
|
|
283
284
|
|
|
284
285
|
//#endregion
|
|
285
286
|
//#region src/internal/plugin/resolvePlugin.ts
|
|
287
|
+
const normalizeConfiguredInputRules = (config) => {
|
|
288
|
+
if (config === void 0) return [];
|
|
289
|
+
if (Array.isArray(config)) return [...config];
|
|
290
|
+
throw new Error("inputRules config must be an array of explicit rule instances.");
|
|
291
|
+
};
|
|
286
292
|
/**
|
|
287
293
|
* Resolves and finalizes a plugin configuration for use in a Plate editor.
|
|
288
294
|
*
|
|
@@ -303,6 +309,11 @@ const resolvePlugin = (editor, _plugin) => {
|
|
|
303
309
|
plugin.__resolved = true;
|
|
304
310
|
if (plugin.__configuration) {
|
|
305
311
|
const configResult = plugin.__configuration(getEditorPlugin(editor, plugin));
|
|
312
|
+
if (configResult.inputRules !== void 0) {
|
|
313
|
+
const normalizedInputRules = normalizeConfiguredInputRules(configResult.inputRules);
|
|
314
|
+
plugin.__configuredInputRules = [...normalizeConfiguredInputRules(plugin.__configuredInputRules), ...normalizedInputRules];
|
|
315
|
+
configResult.inputRules = void 0;
|
|
316
|
+
}
|
|
306
317
|
plugin = mergePlugins(plugin, configResult);
|
|
307
318
|
plugin.__configuration = void 0;
|
|
308
319
|
}
|
|
@@ -361,11 +372,390 @@ const getPluginByType = (editor, type) => {
|
|
|
361
372
|
};
|
|
362
373
|
const getContainerTypes = (editor) => getPluginTypes(editor, editor.meta.pluginCache.node.isContainer);
|
|
363
374
|
|
|
375
|
+
//#endregion
|
|
376
|
+
//#region src/lib/plugins/input-rules/defineInputRule.ts
|
|
377
|
+
function defineInputRule(rule) {
|
|
378
|
+
return rule;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
//#endregion
|
|
382
|
+
//#region src/lib/plugins/input-rules/createInputRules.ts
|
|
383
|
+
const noWhiteSpaceRegex = /\S+/;
|
|
384
|
+
const isPreviousCharacterEmpty = (editor, at) => {
|
|
385
|
+
const range = editor.api.range("before", at);
|
|
386
|
+
if (!range) return true;
|
|
387
|
+
const text = editor.api.string(range);
|
|
388
|
+
return text ? !noWhiteSpaceRegex.exec(text) : true;
|
|
389
|
+
};
|
|
390
|
+
const getMarkMatch = (editor, { end = "", start }) => {
|
|
391
|
+
const { selection } = editor;
|
|
392
|
+
if (!selection) return;
|
|
393
|
+
let beforeEndMatchPoint = selection.anchor;
|
|
394
|
+
if (end) {
|
|
395
|
+
beforeEndMatchPoint = editor.api.before(selection, { matchString: end });
|
|
396
|
+
if (!beforeEndMatchPoint) return;
|
|
397
|
+
}
|
|
398
|
+
const afterStartMatchPoint = editor.api.before(beforeEndMatchPoint, {
|
|
399
|
+
afterMatch: true,
|
|
400
|
+
matchString: start,
|
|
401
|
+
skipInvalid: true
|
|
402
|
+
});
|
|
403
|
+
if (!afterStartMatchPoint) return;
|
|
404
|
+
const beforeStartMatchPoint = editor.api.before(beforeEndMatchPoint, {
|
|
405
|
+
matchString: start,
|
|
406
|
+
skipInvalid: true
|
|
407
|
+
});
|
|
408
|
+
if (!beforeStartMatchPoint) return;
|
|
409
|
+
if (!isPreviousCharacterEmpty(editor, beforeStartMatchPoint)) return;
|
|
410
|
+
return {
|
|
411
|
+
afterStartMatchPoint,
|
|
412
|
+
beforeEndMatchPoint,
|
|
413
|
+
beforeStartMatchPoint
|
|
414
|
+
};
|
|
415
|
+
};
|
|
416
|
+
const createMarkInputRule = (config) => defineInputRule({
|
|
417
|
+
enabled: config.enabled,
|
|
418
|
+
priority: config.priority,
|
|
419
|
+
target: "insertText",
|
|
420
|
+
trigger: config.trigger,
|
|
421
|
+
resolve: ({ editor, text }) => {
|
|
422
|
+
if (text !== config.trigger || !editor.selection || !editor.api.isCollapsed()) return;
|
|
423
|
+
const match = getMarkMatch(editor, {
|
|
424
|
+
end: config.end,
|
|
425
|
+
start: config.start
|
|
426
|
+
});
|
|
427
|
+
if (!match) return;
|
|
428
|
+
const range = {
|
|
429
|
+
anchor: match.afterStartMatchPoint,
|
|
430
|
+
focus: match.beforeEndMatchPoint
|
|
431
|
+
};
|
|
432
|
+
const matchText = editor.api.string(range);
|
|
433
|
+
if (config.trim !== "allow" && matchText.trim() !== matchText) return;
|
|
434
|
+
return {
|
|
435
|
+
...match,
|
|
436
|
+
end: config.end
|
|
437
|
+
};
|
|
438
|
+
},
|
|
439
|
+
apply: ({ editor, pluginKey }, match) => {
|
|
440
|
+
const marks = config.marks ? [...config.marks] : [config.mark ?? pluginKey];
|
|
441
|
+
if (match.beforeEndMatchPoint !== editor.selection?.anchor) editor.tf.delete({ at: {
|
|
442
|
+
anchor: match.beforeEndMatchPoint,
|
|
443
|
+
focus: editor.selection.anchor
|
|
444
|
+
} });
|
|
445
|
+
editor.tf.select({
|
|
446
|
+
anchor: match.afterStartMatchPoint,
|
|
447
|
+
focus: match.beforeEndMatchPoint
|
|
448
|
+
});
|
|
449
|
+
marks.forEach((mark) => {
|
|
450
|
+
editor.tf.addMark(editor.getType(mark), true);
|
|
451
|
+
});
|
|
452
|
+
editor.tf.collapse({ edge: "end" });
|
|
453
|
+
editor.tf.removeMarks(marks.map((mark) => editor.getType(mark)), { shouldChange: false });
|
|
454
|
+
editor.tf.delete({ at: {
|
|
455
|
+
anchor: match.beforeStartMatchPoint,
|
|
456
|
+
focus: match.afterStartMatchPoint
|
|
457
|
+
} });
|
|
458
|
+
return true;
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
const matchBlockStart = (context, config) => {
|
|
462
|
+
if (!context.isCollapsed) return;
|
|
463
|
+
const pattern = typeof config.match === "function" ? config.match(context) : config.match;
|
|
464
|
+
if (!pattern) return;
|
|
465
|
+
const range = context.getBlockStartRange();
|
|
466
|
+
const blockText = context.getBlockStartText();
|
|
467
|
+
if (!range || blockText === void 0) return;
|
|
468
|
+
const baseMatch = {
|
|
469
|
+
range,
|
|
470
|
+
text: blockText
|
|
471
|
+
};
|
|
472
|
+
if (typeof pattern === "string") {
|
|
473
|
+
if (blockText !== pattern) return;
|
|
474
|
+
if (config.resolveMatch) {
|
|
475
|
+
const resolved = config.resolveMatch({
|
|
476
|
+
match: pattern,
|
|
477
|
+
range,
|
|
478
|
+
text: blockText
|
|
479
|
+
});
|
|
480
|
+
if (resolved === void 0) return;
|
|
481
|
+
return {
|
|
482
|
+
...baseMatch,
|
|
483
|
+
...resolved
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
return baseMatch;
|
|
487
|
+
}
|
|
488
|
+
const regexMatch = blockText.match(pattern);
|
|
489
|
+
if (!regexMatch) return;
|
|
490
|
+
if (config.resolveMatch) {
|
|
491
|
+
const resolved = config.resolveMatch({
|
|
492
|
+
match: regexMatch,
|
|
493
|
+
range,
|
|
494
|
+
text: blockText
|
|
495
|
+
});
|
|
496
|
+
if (resolved === void 0) return;
|
|
497
|
+
return {
|
|
498
|
+
...baseMatch,
|
|
499
|
+
...resolved
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
return baseMatch;
|
|
503
|
+
};
|
|
504
|
+
const createBlockStartInputRule = (config) => defineInputRule({
|
|
505
|
+
enabled: config.enabled,
|
|
506
|
+
priority: config.priority,
|
|
507
|
+
target: "insertText",
|
|
508
|
+
trigger: config.trigger,
|
|
509
|
+
resolve: (context) => matchBlockStart(context, config),
|
|
510
|
+
apply: (context, match) => {
|
|
511
|
+
if (config.apply) return config.apply(context, match);
|
|
512
|
+
const { editor, pluginKey } = context;
|
|
513
|
+
const defaultMatch = match;
|
|
514
|
+
if (config.removeMatchedText !== false) editor.tf.delete({ at: defaultMatch.range });
|
|
515
|
+
const node = editor.getType(config.node ?? pluginKey);
|
|
516
|
+
if (config.mode === "wrap") {
|
|
517
|
+
editor.tf.toggleBlock(node, { wrap: true });
|
|
518
|
+
return true;
|
|
519
|
+
}
|
|
520
|
+
if (config.mode === "toggle") {
|
|
521
|
+
editor.tf.toggleBlock(node);
|
|
522
|
+
return true;
|
|
523
|
+
}
|
|
524
|
+
editor.tf.setNodes({ type: node }, { match: (entryNode) => editor.api.isBlock(entryNode) });
|
|
525
|
+
return true;
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
const matchBlockFence = (context, config) => {
|
|
529
|
+
const { editor } = context;
|
|
530
|
+
const { selection } = editor;
|
|
531
|
+
if (!context.isCollapsed || !selection) return;
|
|
532
|
+
const blockEntry = context.getBlockEntry();
|
|
533
|
+
if (!blockEntry) return;
|
|
534
|
+
const [blockNode, path] = blockEntry;
|
|
535
|
+
const endPoint = editor.api.end(path);
|
|
536
|
+
if (config.block && blockNode.type !== editor.getType(config.block)) return;
|
|
537
|
+
if (!endPoint || !editor.api.isEnd(selection.focus, path)) return;
|
|
538
|
+
const range = context.getBlockStartRange();
|
|
539
|
+
const blockText = context.getBlockStartText();
|
|
540
|
+
if (!range || blockText === void 0 || blockText !== config.fence) return;
|
|
541
|
+
return config.resolveMatch ? config.resolveMatch({
|
|
542
|
+
fence: config.fence,
|
|
543
|
+
path,
|
|
544
|
+
range,
|
|
545
|
+
text: blockText
|
|
546
|
+
}) : {
|
|
547
|
+
path,
|
|
548
|
+
range,
|
|
549
|
+
text: blockText
|
|
550
|
+
};
|
|
551
|
+
};
|
|
552
|
+
function createBlockFenceInputRule(config) {
|
|
553
|
+
if (config.on === "break") return defineInputRule({
|
|
554
|
+
priority: config.priority,
|
|
555
|
+
target: "insertBreak",
|
|
556
|
+
enabled: config.enabled,
|
|
557
|
+
resolve: (context) => matchBlockFence(context, {
|
|
558
|
+
block: config.block,
|
|
559
|
+
fence: config.fence,
|
|
560
|
+
resolveMatch: config.resolveMatch
|
|
561
|
+
}),
|
|
562
|
+
apply: config.apply
|
|
563
|
+
});
|
|
564
|
+
const trigger = config.fence.at(-1);
|
|
565
|
+
if (!trigger) throw new Error("createBlockFenceInputRule requires a non-empty fence.");
|
|
566
|
+
return defineInputRule({
|
|
567
|
+
priority: config.priority,
|
|
568
|
+
target: "insertText",
|
|
569
|
+
enabled: config.enabled,
|
|
570
|
+
trigger,
|
|
571
|
+
resolve: (context) => {
|
|
572
|
+
if (context.text !== trigger) return;
|
|
573
|
+
return matchBlockFence(context, {
|
|
574
|
+
block: config.block,
|
|
575
|
+
fence: config.fence.slice(0, -trigger.length),
|
|
576
|
+
resolveMatch: config.resolveMatch
|
|
577
|
+
});
|
|
578
|
+
},
|
|
579
|
+
apply: config.apply
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
const matchDelimitedInline = (context, { boundaryRe, close, followRe, open, requireClosingDelimiter = true, rejectRepeatedOpen = true, trim = "reject" }) => {
|
|
583
|
+
const { editor } = context;
|
|
584
|
+
const { selection } = editor;
|
|
585
|
+
if (!selection || !context.isCollapsed) return;
|
|
586
|
+
const blockRange = context.getBlockStartRange();
|
|
587
|
+
if (!blockRange) return;
|
|
588
|
+
const openingDelimiter = open;
|
|
589
|
+
const closingDelimiter = close ?? open;
|
|
590
|
+
const textBefore = editor.api.string(blockRange);
|
|
591
|
+
const beforeClose = requireClosingDelimiter ? (() => {
|
|
592
|
+
const closeLength = closingDelimiter.length;
|
|
593
|
+
if (textBefore.length < closeLength) return;
|
|
594
|
+
if (!textBefore.endsWith(closingDelimiter)) return;
|
|
595
|
+
return textBefore.slice(0, -closeLength);
|
|
596
|
+
})() : textBefore;
|
|
597
|
+
if (!beforeClose) return;
|
|
598
|
+
const openIndex = beforeClose.lastIndexOf(openingDelimiter);
|
|
599
|
+
if (openIndex < 0) return;
|
|
600
|
+
const prefix = beforeClose.slice(0, openIndex);
|
|
601
|
+
const content = beforeClose.slice(openIndex + openingDelimiter.length);
|
|
602
|
+
if (!content) return;
|
|
603
|
+
if (trim === "reject" && content.trim() !== content) return;
|
|
604
|
+
if (rejectRepeatedOpen && openingDelimiter === closingDelimiter && prefix.endsWith(openingDelimiter)) return;
|
|
605
|
+
const previousChar = prefix.at(-1);
|
|
606
|
+
if (previousChar && boundaryRe && !boundaryRe.test(previousChar)) return;
|
|
607
|
+
const nextPoint = editor.api.after(selection, {
|
|
608
|
+
distance: 1,
|
|
609
|
+
unit: "character"
|
|
610
|
+
});
|
|
611
|
+
if (nextPoint && followRe) {
|
|
612
|
+
const nextChar = editor.api.string({
|
|
613
|
+
anchor: selection.anchor,
|
|
614
|
+
focus: nextPoint
|
|
615
|
+
});
|
|
616
|
+
if (nextChar && !followRe.test(nextChar)) return;
|
|
617
|
+
}
|
|
618
|
+
const startPoint = editor.api.before(selection, {
|
|
619
|
+
distance: content.length + openingDelimiter.length,
|
|
620
|
+
unit: "character"
|
|
621
|
+
});
|
|
622
|
+
if (!startPoint) return;
|
|
623
|
+
return {
|
|
624
|
+
content,
|
|
625
|
+
deleteRange: {
|
|
626
|
+
anchor: startPoint,
|
|
627
|
+
focus: selection.anchor
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
};
|
|
631
|
+
const getTextSubstitutionMatchRange = ({ match, trigger }) => {
|
|
632
|
+
const start = match;
|
|
633
|
+
const reversed = start.split("").reverse().join("");
|
|
634
|
+
const triggers = trigger ? Array.isArray(trigger) ? [...trigger] : [trigger] : [reversed.slice(-1)];
|
|
635
|
+
return {
|
|
636
|
+
end: trigger ? reversed : reversed.slice(0, -1),
|
|
637
|
+
start,
|
|
638
|
+
triggers
|
|
639
|
+
};
|
|
640
|
+
};
|
|
641
|
+
const getTextSubstitutionMatchPoints = (editor, { end, start }) => {
|
|
642
|
+
const { selection } = editor;
|
|
643
|
+
if (!selection) return;
|
|
644
|
+
let beforeEndMatchPoint = selection.anchor;
|
|
645
|
+
if (end) {
|
|
646
|
+
beforeEndMatchPoint = editor.api.before(selection, { matchString: end });
|
|
647
|
+
if (!beforeEndMatchPoint) return;
|
|
648
|
+
}
|
|
649
|
+
let afterStartMatchPoint;
|
|
650
|
+
let beforeStartMatchPoint;
|
|
651
|
+
if (start) {
|
|
652
|
+
afterStartMatchPoint = editor.api.before(beforeEndMatchPoint, {
|
|
653
|
+
afterMatch: true,
|
|
654
|
+
matchString: start,
|
|
655
|
+
skipInvalid: true
|
|
656
|
+
});
|
|
657
|
+
if (!afterStartMatchPoint) return;
|
|
658
|
+
beforeStartMatchPoint = editor.api.before(beforeEndMatchPoint, {
|
|
659
|
+
matchString: start,
|
|
660
|
+
skipInvalid: true
|
|
661
|
+
});
|
|
662
|
+
if (!beforeStartMatchPoint) return;
|
|
663
|
+
if (!isPreviousCharacterEmpty(editor, beforeStartMatchPoint)) return;
|
|
664
|
+
}
|
|
665
|
+
return {
|
|
666
|
+
afterStartMatchPoint,
|
|
667
|
+
beforeEndMatchPoint,
|
|
668
|
+
beforeStartMatchPoint
|
|
669
|
+
};
|
|
670
|
+
};
|
|
671
|
+
const getTextSubstitutionTriggers = (patterns) => Array.from(new Set(patterns.flatMap((pattern) => {
|
|
672
|
+
return (Array.isArray(pattern.match) ? [...pattern.match] : [pattern.match]).flatMap((match) => getTextSubstitutionMatchRange({
|
|
673
|
+
match,
|
|
674
|
+
trigger: pattern.trigger
|
|
675
|
+
}).triggers);
|
|
676
|
+
})));
|
|
677
|
+
const resolveTextSubstitution = ({ editor, patterns, text }) => {
|
|
678
|
+
for (const pattern of patterns) {
|
|
679
|
+
const matches = Array.isArray(pattern.match) ? [...pattern.match] : [pattern.match];
|
|
680
|
+
for (const match of matches) {
|
|
681
|
+
const { end, start, triggers } = getTextSubstitutionMatchRange({
|
|
682
|
+
match,
|
|
683
|
+
trigger: pattern.trigger
|
|
684
|
+
});
|
|
685
|
+
if (!triggers.includes(text)) continue;
|
|
686
|
+
const points = getTextSubstitutionMatchPoints(editor, {
|
|
687
|
+
end: Array.isArray(pattern.format) ? "" : end,
|
|
688
|
+
start
|
|
689
|
+
});
|
|
690
|
+
if (!points) continue;
|
|
691
|
+
return {
|
|
692
|
+
end: Array.isArray(pattern.format) ? "" : end,
|
|
693
|
+
pattern,
|
|
694
|
+
points
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
};
|
|
699
|
+
const applyTextSubstitution = (editor, match) => {
|
|
700
|
+
const selection = editor.selection;
|
|
701
|
+
if (!selection || !match) return false;
|
|
702
|
+
if (match.end) editor.tf.delete({ at: {
|
|
703
|
+
anchor: match.points.beforeEndMatchPoint,
|
|
704
|
+
focus: selection.anchor
|
|
705
|
+
} });
|
|
706
|
+
const formatEnd = Array.isArray(match.pattern.format) ? match.pattern.format[1] : match.pattern.format;
|
|
707
|
+
editor.tf.insertText(formatEnd);
|
|
708
|
+
if (match.points.beforeStartMatchPoint && match.points.afterStartMatchPoint) {
|
|
709
|
+
const formatStart = Array.isArray(match.pattern.format) ? match.pattern.format[0] : match.pattern.format;
|
|
710
|
+
editor.tf.delete({ at: {
|
|
711
|
+
anchor: match.points.beforeStartMatchPoint,
|
|
712
|
+
focus: match.points.afterStartMatchPoint
|
|
713
|
+
} });
|
|
714
|
+
editor.tf.insertText(formatStart, { at: match.points.beforeStartMatchPoint });
|
|
715
|
+
}
|
|
716
|
+
return true;
|
|
717
|
+
};
|
|
718
|
+
const createTextSubstitutionInputRule = ({ enabled, patterns, priority }) => defineInputRule({
|
|
719
|
+
enabled,
|
|
720
|
+
priority,
|
|
721
|
+
target: "insertText",
|
|
722
|
+
trigger: getTextSubstitutionTriggers(patterns),
|
|
723
|
+
resolve: ({ editor, text }) => {
|
|
724
|
+
if (!editor.selection || !editor.api.isCollapsed()) return;
|
|
725
|
+
return resolveTextSubstitution({
|
|
726
|
+
editor,
|
|
727
|
+
patterns,
|
|
728
|
+
text
|
|
729
|
+
});
|
|
730
|
+
},
|
|
731
|
+
apply: ({ editor }, match) => applyTextSubstitution(editor, match)
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
//#endregion
|
|
735
|
+
//#region src/lib/plugins/input-rules/internal/createInputRuleBuilder.ts
|
|
736
|
+
const createInputRuleBuilder = () => ({
|
|
737
|
+
blockFence: (config) => createBlockFenceInputRule(config),
|
|
738
|
+
blockStart: (config) => createBlockStartInputRule(config),
|
|
739
|
+
insertBreak: (rule) => defineInputRule(rule),
|
|
740
|
+
insertData: (rule) => defineInputRule(rule),
|
|
741
|
+
insertText: (rule) => defineInputRule(rule),
|
|
742
|
+
mark: (config) => createMarkInputRule(config)
|
|
743
|
+
});
|
|
744
|
+
|
|
364
745
|
//#endregion
|
|
365
746
|
//#region src/internal/plugin/resolvePlugins.ts
|
|
366
747
|
const resolvePlugins = (editor, plugins = [], createStore = createVanillaStore) => {
|
|
367
748
|
editor.plugins = {};
|
|
368
749
|
editor.meta.pluginList = [];
|
|
750
|
+
editor.meta.inputRules = {
|
|
751
|
+
insertBreak: [],
|
|
752
|
+
insertData: [],
|
|
753
|
+
insertText: {
|
|
754
|
+
all: [],
|
|
755
|
+
byTrigger: {}
|
|
756
|
+
},
|
|
757
|
+
plugins: {}
|
|
758
|
+
};
|
|
369
759
|
editor.meta.shortcuts = {};
|
|
370
760
|
editor.meta.components = {};
|
|
371
761
|
editor.meta.pluginCache = {
|
|
@@ -435,6 +825,8 @@ const resolvePlugins = (editor, plugins = [], createStore = createVanillaStore)
|
|
|
435
825
|
if (plugin.handlers?.onTextChange) editor.meta.pluginCache.handlers.onTextChange.push(plugin.key);
|
|
436
826
|
});
|
|
437
827
|
resolvePluginShortcuts(editor);
|
|
828
|
+
resolvePluginInputRules(editor);
|
|
829
|
+
validateRemovedRuntimePlugins(editor);
|
|
438
830
|
return editor;
|
|
439
831
|
};
|
|
440
832
|
const resolvePluginStores = (editor, createStore) => {
|
|
@@ -512,6 +904,68 @@ const resolvePluginShortcuts = (editor) => {
|
|
|
512
904
|
});
|
|
513
905
|
});
|
|
514
906
|
};
|
|
907
|
+
const resolvePluginInputRules = (editor) => {
|
|
908
|
+
const resolvedMeta = {
|
|
909
|
+
insertBreak: [],
|
|
910
|
+
insertData: [],
|
|
911
|
+
insertText: {
|
|
912
|
+
all: [],
|
|
913
|
+
byTrigger: {}
|
|
914
|
+
},
|
|
915
|
+
plugins: {}
|
|
916
|
+
};
|
|
917
|
+
editor.meta.pluginList.forEach((plugin, pluginIndex) => {
|
|
918
|
+
const pluginKey = plugin.key;
|
|
919
|
+
const inputRulesDefinition = plugin.inputRules;
|
|
920
|
+
const definitionRules = typeof inputRulesDefinition === "function" ? inputRulesDefinition({ rule: createInputRuleBuilder() }) : inputRulesDefinition ?? [];
|
|
921
|
+
const configuredRules = plugin.__configuredInputRules ?? [];
|
|
922
|
+
const ruleDefinitions = [...definitionRules, ...configuredRules];
|
|
923
|
+
resolvedMeta.plugins[pluginKey] = { rules: [] };
|
|
924
|
+
ruleDefinitions.forEach((definition, ruleIndex) => {
|
|
925
|
+
if (!definition) return;
|
|
926
|
+
const mergedRule = mergePlugins({}, definition);
|
|
927
|
+
const resolvedRule = {
|
|
928
|
+
...mergedRule,
|
|
929
|
+
id: `${pluginKey}.${ruleIndex}`,
|
|
930
|
+
pluginIndex,
|
|
931
|
+
pluginKey,
|
|
932
|
+
priority: mergedRule.priority ?? plugin.priority,
|
|
933
|
+
ruleIndex
|
|
934
|
+
};
|
|
935
|
+
resolvedMeta.plugins[pluginKey].rules.push(resolvedRule);
|
|
936
|
+
if (resolvedRule.target === "insertText") {
|
|
937
|
+
const triggers = Array.isArray(resolvedRule.trigger) ? [...resolvedRule.trigger] : [resolvedRule.trigger];
|
|
938
|
+
resolvedMeta.insertText.all.push(resolvedRule);
|
|
939
|
+
triggers.forEach((trigger) => {
|
|
940
|
+
if (!resolvedMeta.insertText.byTrigger[trigger]) resolvedMeta.insertText.byTrigger[trigger] = [];
|
|
941
|
+
resolvedMeta.insertText.byTrigger[trigger].push(resolvedRule);
|
|
942
|
+
});
|
|
943
|
+
} else if (resolvedRule.target === "insertBreak") resolvedMeta.insertBreak.push(resolvedRule);
|
|
944
|
+
else if (resolvedRule.target === "insertData") resolvedMeta.insertData.push(resolvedRule);
|
|
945
|
+
});
|
|
946
|
+
});
|
|
947
|
+
const sortRules = (a, b) => {
|
|
948
|
+
if (b.priority !== a.priority) return b.priority - a.priority;
|
|
949
|
+
if (a.pluginIndex !== b.pluginIndex) return a.pluginIndex - b.pluginIndex;
|
|
950
|
+
return a.ruleIndex - b.ruleIndex;
|
|
951
|
+
};
|
|
952
|
+
resolvedMeta.insertBreak.sort(sortRules);
|
|
953
|
+
resolvedMeta.insertData.sort(sortRules);
|
|
954
|
+
resolvedMeta.insertText.all.sort(sortRules);
|
|
955
|
+
Object.values(resolvedMeta.insertText.byTrigger).forEach((rules) => {
|
|
956
|
+
rules.sort(sortRules);
|
|
957
|
+
});
|
|
958
|
+
editor.meta.inputRules = resolvedMeta;
|
|
959
|
+
};
|
|
960
|
+
const validateRemovedRuntimePlugins = (editor) => {
|
|
961
|
+
const hasAutoformatPlugin = !!editor.plugins.autoformat;
|
|
962
|
+
const hasResolvedInputRules = editor.meta.inputRules.insertBreak.length > 0 || editor.meta.inputRules.insertData.length > 0 || editor.meta.inputRules.insertText.all.length > 0;
|
|
963
|
+
if (hasAutoformatPlugin && hasResolvedInputRules) throw new Error([
|
|
964
|
+
"AutoformatPlugin cannot be used with plugin-owned input rules.",
|
|
965
|
+
"Remove AutoformatPlugin from your editor plugins.",
|
|
966
|
+
"Enable inputRules on the feature plugins you use instead."
|
|
967
|
+
].join(" "));
|
|
968
|
+
};
|
|
515
969
|
const flattenAndResolvePlugins = (editor, plugins) => {
|
|
516
970
|
const pluginMap = /* @__PURE__ */ new Map();
|
|
517
971
|
const processPlugin = (plugin) => {
|
|
@@ -643,11 +1097,11 @@ const withBreakRules = (ctx) => {
|
|
|
643
1097
|
node: blockNode,
|
|
644
1098
|
path: blockPath,
|
|
645
1099
|
rule
|
|
646
|
-
})) return overridePlugin
|
|
1100
|
+
})) return overridePlugin;
|
|
647
1101
|
}
|
|
648
1102
|
return null;
|
|
649
1103
|
};
|
|
650
|
-
const executeBreakAction = (action, blockPath) => {
|
|
1104
|
+
const executeBreakAction = (action, blockPath, type) => {
|
|
651
1105
|
if (action === "reset") {
|
|
652
1106
|
editor.tf.resetBlock({ at: blockPath });
|
|
653
1107
|
return true;
|
|
@@ -665,30 +1119,40 @@ const withBreakRules = (ctx) => {
|
|
|
665
1119
|
editor.tf.insertSoftBreak();
|
|
666
1120
|
return true;
|
|
667
1121
|
}
|
|
1122
|
+
if (action === "lift" && type) return !!editor.tf.liftBlock({
|
|
1123
|
+
at: blockPath,
|
|
1124
|
+
match: { type }
|
|
1125
|
+
});
|
|
668
1126
|
return false;
|
|
669
1127
|
};
|
|
670
1128
|
return { transforms: { insertBreak() {
|
|
671
|
-
if (editor.selection
|
|
1129
|
+
if (editor.selection) {
|
|
672
1130
|
const block = editor.api.block();
|
|
673
1131
|
if (block) {
|
|
674
1132
|
const [blockNode, blockPath] = block;
|
|
675
1133
|
const breakRules = getPluginByType(editor, blockNode.type)?.rules.break;
|
|
676
|
-
if (editor.api.isEmpty(editor.selection, { block: true })) {
|
|
677
|
-
const
|
|
678
|
-
|
|
1134
|
+
if (editor.api.isCollapsed() && editor.api.isEmpty(editor.selection, { block: true })) {
|
|
1135
|
+
const overridePlugin = checkMatchRulesOverride("break.empty", blockNode, blockPath);
|
|
1136
|
+
const emptyAction = (overridePlugin?.rules.break ?? breakRules)?.empty;
|
|
1137
|
+
const actionType = overridePlugin?.node.type;
|
|
1138
|
+
if (executeBreakAction(emptyAction, blockPath, actionType)) return;
|
|
679
1139
|
}
|
|
680
|
-
if (!editor.api.isEmpty(editor.selection, { block: true }) && editor.api.isAt({ end: true })) {
|
|
1140
|
+
if (editor.api.isCollapsed() && !editor.api.isEmpty(editor.selection, { block: true }) && editor.api.isAt({ end: true })) {
|
|
681
1141
|
const range = editor.api.range("before", editor.selection);
|
|
682
1142
|
if (range) {
|
|
683
1143
|
if (editor.api.string(range) === "\n") {
|
|
684
|
-
const
|
|
685
|
-
|
|
1144
|
+
const overridePlugin = checkMatchRulesOverride("break.emptyLineEnd", blockNode, blockPath);
|
|
1145
|
+
const emptyLineEndAction = (overridePlugin?.rules.break ?? breakRules)?.emptyLineEnd;
|
|
1146
|
+
const actionType = overridePlugin?.node.type;
|
|
1147
|
+
if (executeBreakAction(emptyLineEndAction, blockPath, actionType)) return;
|
|
686
1148
|
}
|
|
687
1149
|
}
|
|
688
1150
|
}
|
|
689
|
-
const
|
|
690
|
-
|
|
691
|
-
|
|
1151
|
+
const overrideDefaultPlugin = checkMatchRulesOverride("break.default", blockNode, blockPath);
|
|
1152
|
+
const defaultAction = (overrideDefaultPlugin?.rules.break ?? breakRules)?.default;
|
|
1153
|
+
const defaultActionType = overrideDefaultPlugin?.node.type;
|
|
1154
|
+
if (executeBreakAction(defaultAction, blockPath, defaultActionType)) return;
|
|
1155
|
+
if ((checkMatchRulesOverride("break.splitReset", blockNode, blockPath)?.rules.break?.splitReset ?? breakRules?.splitReset) && !editor.api.isAt({ blocks: true })) {
|
|
692
1156
|
const isAtStart = editor.api.isAt({ start: true });
|
|
693
1157
|
insertBreak();
|
|
694
1158
|
editor.tf.resetBlock({ at: isAtStart ? blockPath : PathApi.next(blockPath) });
|
|
@@ -716,15 +1180,19 @@ const withDeleteRules = (ctx) => {
|
|
|
716
1180
|
node: blockNode,
|
|
717
1181
|
path: blockPath,
|
|
718
1182
|
rule
|
|
719
|
-
})) return overridePlugin
|
|
1183
|
+
})) return overridePlugin;
|
|
720
1184
|
}
|
|
721
1185
|
return null;
|
|
722
1186
|
};
|
|
723
|
-
const executeDeleteAction = (action, blockPath) => {
|
|
1187
|
+
const executeDeleteAction = (action, blockPath, type) => {
|
|
724
1188
|
if (action === "reset") {
|
|
725
1189
|
editor.tf.resetBlock({ at: blockPath });
|
|
726
1190
|
return true;
|
|
727
1191
|
}
|
|
1192
|
+
if (action === "lift" && type) return !!editor.tf.liftBlock({
|
|
1193
|
+
at: blockPath,
|
|
1194
|
+
match: { type }
|
|
1195
|
+
});
|
|
728
1196
|
return false;
|
|
729
1197
|
};
|
|
730
1198
|
return { transforms: {
|
|
@@ -735,12 +1203,16 @@ const withDeleteRules = (ctx) => {
|
|
|
735
1203
|
const [blockNode, blockPath] = block;
|
|
736
1204
|
const deleteRules = getPluginByType(editor, blockNode.type)?.rules.delete;
|
|
737
1205
|
if (editor.api.isAt({ start: true })) {
|
|
738
|
-
const
|
|
739
|
-
|
|
1206
|
+
const overridePlugin = checkMatchRulesOverride("delete.start", blockNode, blockPath);
|
|
1207
|
+
const startAction = (overridePlugin?.rules.delete ?? deleteRules)?.start;
|
|
1208
|
+
const actionType = overridePlugin?.node.type;
|
|
1209
|
+
if (executeDeleteAction(startAction, blockPath, actionType)) return;
|
|
740
1210
|
}
|
|
741
1211
|
if (editor.api.isEmpty(editor.selection, { block: true })) {
|
|
742
|
-
const
|
|
743
|
-
|
|
1212
|
+
const overridePlugin = checkMatchRulesOverride("delete.empty", blockNode, blockPath);
|
|
1213
|
+
const emptyAction = (overridePlugin?.rules.delete ?? deleteRules)?.empty;
|
|
1214
|
+
const actionType = overridePlugin?.node.type;
|
|
1215
|
+
if (executeDeleteAction(emptyAction, blockPath, actionType)) return;
|
|
744
1216
|
}
|
|
745
1217
|
}
|
|
746
1218
|
if (PointApi.equals(editor.selection.anchor, editor.api.start([]))) {
|
|
@@ -994,6 +1466,7 @@ const getInjectMatch = (editor, plugin) => {
|
|
|
994
1466
|
if (targetPlugins && !targetPlugins.includes(getPluginKey(editor, element.type))) return false;
|
|
995
1467
|
}
|
|
996
1468
|
if (excludeBelowPlugins || maxLevel) {
|
|
1469
|
+
if (!path) return false;
|
|
997
1470
|
if (maxLevel && path.length > maxLevel) return false;
|
|
998
1471
|
if (excludeBelowPlugins) {
|
|
999
1472
|
const excludeTypes = getPluginKeys(editor, excludeBelowPlugins);
|
|
@@ -2067,6 +2540,182 @@ const LengthPlugin = createTSlatePlugin({ key: "length" }).overrideEditor(({ edi
|
|
|
2067
2540
|
});
|
|
2068
2541
|
} } }));
|
|
2069
2542
|
|
|
2543
|
+
//#endregion
|
|
2544
|
+
//#region src/lib/plugins/navigation-feedback/types.ts
|
|
2545
|
+
const NAVIGATION_FEEDBACK_KEY = "navigationFeedback";
|
|
2546
|
+
const NavigationFeedbackPluginKey = { key: NAVIGATION_FEEDBACK_KEY };
|
|
2547
|
+
|
|
2548
|
+
//#endregion
|
|
2549
|
+
//#region src/lib/plugins/navigation-feedback/transforms/flashTarget.ts
|
|
2550
|
+
const NAVIGATION_FEEDBACK_TIMEOUT = /* @__PURE__ */ new WeakMap();
|
|
2551
|
+
const NAVIGATION_FEEDBACK_PULSE = /* @__PURE__ */ new WeakMap();
|
|
2552
|
+
const NAVIGATION_FEEDBACK_ATTRIBUTES = [
|
|
2553
|
+
"data-nav-cycle",
|
|
2554
|
+
"data-nav-highlight",
|
|
2555
|
+
"data-nav-pulse",
|
|
2556
|
+
"data-nav-target"
|
|
2557
|
+
];
|
|
2558
|
+
const clearNavigationPathRef = (target) => {
|
|
2559
|
+
target?.pathRef.unref();
|
|
2560
|
+
};
|
|
2561
|
+
const resolveNavigationFeedbackTarget = (target) => {
|
|
2562
|
+
const path = target?.pathRef.current;
|
|
2563
|
+
if (!target || !path) return null;
|
|
2564
|
+
const { pathRef: _pathRef, ...rest } = target;
|
|
2565
|
+
return {
|
|
2566
|
+
...rest,
|
|
2567
|
+
path
|
|
2568
|
+
};
|
|
2569
|
+
};
|
|
2570
|
+
const getNavigationElement = (editor, target) => {
|
|
2571
|
+
const node = NodeApi.get(editor, target.path);
|
|
2572
|
+
if (!node) return;
|
|
2573
|
+
try {
|
|
2574
|
+
return editor.api.toDOMNode(node);
|
|
2575
|
+
} catch {
|
|
2576
|
+
return;
|
|
2577
|
+
}
|
|
2578
|
+
};
|
|
2579
|
+
const clearNavigationElement = (editor, target) => {
|
|
2580
|
+
if (!target) return;
|
|
2581
|
+
const element = getNavigationElement(editor, target);
|
|
2582
|
+
if (!element) return;
|
|
2583
|
+
for (const attribute of NAVIGATION_FEEDBACK_ATTRIBUTES) element.removeAttribute(attribute);
|
|
2584
|
+
element.style.removeProperty("--plate-nav-feedback-duration");
|
|
2585
|
+
};
|
|
2586
|
+
const setNavigationElement = (editor, target) => {
|
|
2587
|
+
const element = getNavigationElement(editor, target);
|
|
2588
|
+
if (!element) return;
|
|
2589
|
+
element.setAttribute("data-nav-cycle", String(target.cycle));
|
|
2590
|
+
element.setAttribute("data-nav-highlight", target.variant);
|
|
2591
|
+
element.setAttribute("data-nav-pulse", String(target.pulse));
|
|
2592
|
+
element.setAttribute("data-nav-target", "true");
|
|
2593
|
+
element.style.setProperty("--plate-nav-feedback-duration", `${target.duration}ms`);
|
|
2594
|
+
};
|
|
2595
|
+
const clearNavigationTimeout = (editor) => {
|
|
2596
|
+
const timeoutId = NAVIGATION_FEEDBACK_TIMEOUT.get(editor);
|
|
2597
|
+
if (timeoutId) {
|
|
2598
|
+
clearTimeout(timeoutId);
|
|
2599
|
+
NAVIGATION_FEEDBACK_TIMEOUT.delete(editor);
|
|
2600
|
+
}
|
|
2601
|
+
};
|
|
2602
|
+
const nextPulse = (editor) => {
|
|
2603
|
+
const pulse = (NAVIGATION_FEEDBACK_PULSE.get(editor) ?? 0) + 1;
|
|
2604
|
+
NAVIGATION_FEEDBACK_PULSE.set(editor, pulse);
|
|
2605
|
+
return pulse;
|
|
2606
|
+
};
|
|
2607
|
+
const clearNavigationFeedbackTarget = (editor, pulse) => {
|
|
2608
|
+
const storedTarget = editor.getOption(NavigationFeedbackPluginKey, "activeTarget");
|
|
2609
|
+
const activeTarget = resolveNavigationFeedbackTarget(storedTarget);
|
|
2610
|
+
if (!storedTarget) return false;
|
|
2611
|
+
if (pulse !== void 0 && storedTarget.pulse !== pulse) return false;
|
|
2612
|
+
clearNavigationTimeout(editor);
|
|
2613
|
+
clearNavigationElement(editor, activeTarget);
|
|
2614
|
+
clearNavigationPathRef(storedTarget);
|
|
2615
|
+
editor.setOption(NavigationFeedbackPluginKey, "activeTarget", null);
|
|
2616
|
+
return true;
|
|
2617
|
+
};
|
|
2618
|
+
const flashTarget = (editor, { duration, target, variant = "navigated" }) => {
|
|
2619
|
+
if (!editor.api.node(target.path)) return false;
|
|
2620
|
+
const pulse = nextPulse(editor);
|
|
2621
|
+
const timeoutMs = duration ?? editor.getOption(NavigationFeedbackPluginKey, "duration") ?? 800;
|
|
2622
|
+
const previousTarget = editor.getOption(NavigationFeedbackPluginKey, "activeTarget");
|
|
2623
|
+
clearNavigationTimeout(editor);
|
|
2624
|
+
clearNavigationElement(editor, resolveNavigationFeedbackTarget(previousTarget));
|
|
2625
|
+
clearNavigationPathRef(previousTarget);
|
|
2626
|
+
const pathRef = editor.api.pathRef(target.path);
|
|
2627
|
+
const activeTarget = {
|
|
2628
|
+
cycle: pulse % 2,
|
|
2629
|
+
duration: timeoutMs,
|
|
2630
|
+
pathRef,
|
|
2631
|
+
pulse,
|
|
2632
|
+
type: target.type,
|
|
2633
|
+
variant
|
|
2634
|
+
};
|
|
2635
|
+
editor.setOption(NavigationFeedbackPluginKey, "activeTarget", activeTarget);
|
|
2636
|
+
setNavigationElement(editor, resolveNavigationFeedbackTarget(activeTarget) ?? {
|
|
2637
|
+
cycle: activeTarget.cycle,
|
|
2638
|
+
duration: activeTarget.duration,
|
|
2639
|
+
path: target.path,
|
|
2640
|
+
pulse: activeTarget.pulse,
|
|
2641
|
+
type: activeTarget.type,
|
|
2642
|
+
variant: activeTarget.variant
|
|
2643
|
+
});
|
|
2644
|
+
const timeoutId = setTimeout(() => {
|
|
2645
|
+
clearNavigationFeedbackTarget(editor, pulse);
|
|
2646
|
+
}, timeoutMs);
|
|
2647
|
+
NAVIGATION_FEEDBACK_TIMEOUT.set(editor, timeoutId);
|
|
2648
|
+
return true;
|
|
2649
|
+
};
|
|
2650
|
+
|
|
2651
|
+
//#endregion
|
|
2652
|
+
//#region src/lib/plugins/navigation-feedback/transforms/navigate.ts
|
|
2653
|
+
const getScrollTarget = (editor, { scrollTarget, select, target }) => {
|
|
2654
|
+
if (scrollTarget) return scrollTarget;
|
|
2655
|
+
if (select && "focus" in select && select.focus) return select.focus;
|
|
2656
|
+
if (select && "anchor" in select && select.anchor) return select.anchor;
|
|
2657
|
+
if (select && "path" in select) return select;
|
|
2658
|
+
return editor.api.start(target.path);
|
|
2659
|
+
};
|
|
2660
|
+
const navigate = (editor, { flash, focus = true, scroll = true, scrollTarget, select, target }) => {
|
|
2661
|
+
if (!editor.api.node(target.path)) return false;
|
|
2662
|
+
if (select) if ("focus" in select) editor.tf.select(select);
|
|
2663
|
+
else editor.tf.select({
|
|
2664
|
+
anchor: select,
|
|
2665
|
+
focus: select
|
|
2666
|
+
});
|
|
2667
|
+
if (focus) editor.tf.focus();
|
|
2668
|
+
if (scroll) {
|
|
2669
|
+
const point = getScrollTarget(editor, {
|
|
2670
|
+
flash,
|
|
2671
|
+
focus,
|
|
2672
|
+
scroll,
|
|
2673
|
+
scrollTarget,
|
|
2674
|
+
select,
|
|
2675
|
+
target
|
|
2676
|
+
});
|
|
2677
|
+
if (point) editor.api.scrollIntoView(point);
|
|
2678
|
+
}
|
|
2679
|
+
if (flash !== false) flashTarget(editor, {
|
|
2680
|
+
duration: flash?.duration,
|
|
2681
|
+
target,
|
|
2682
|
+
variant: flash?.variant
|
|
2683
|
+
});
|
|
2684
|
+
return true;
|
|
2685
|
+
};
|
|
2686
|
+
|
|
2687
|
+
//#endregion
|
|
2688
|
+
//#region src/lib/plugins/navigation-feedback/NavigationFeedbackPlugin.ts
|
|
2689
|
+
const NavigationFeedbackPlugin = createTSlatePlugin({
|
|
2690
|
+
key: NAVIGATION_FEEDBACK_KEY,
|
|
2691
|
+
options: {
|
|
2692
|
+
activeTarget: null,
|
|
2693
|
+
duration: 1600
|
|
2694
|
+
}
|
|
2695
|
+
}).extendEditorApi(({ editor }) => {
|
|
2696
|
+
const getActiveTarget = () => {
|
|
2697
|
+
const storedTarget = editor.getOption(NavigationFeedbackPluginKey, "activeTarget");
|
|
2698
|
+
const activeTarget = resolveNavigationFeedbackTarget(storedTarget);
|
|
2699
|
+
if (!activeTarget && storedTarget) {
|
|
2700
|
+
clearNavigationFeedbackTarget(editor);
|
|
2701
|
+
return null;
|
|
2702
|
+
}
|
|
2703
|
+
return activeTarget;
|
|
2704
|
+
};
|
|
2705
|
+
return { navigation: {
|
|
2706
|
+
activeTarget: getActiveTarget,
|
|
2707
|
+
clear: () => clearNavigationFeedbackTarget(editor),
|
|
2708
|
+
isTarget: (path) => {
|
|
2709
|
+
const activeTarget = getActiveTarget();
|
|
2710
|
+
return !!activeTarget && PathApi.equals(activeTarget.path, path);
|
|
2711
|
+
}
|
|
2712
|
+
} };
|
|
2713
|
+
}).extendEditorTransforms(({ editor }) => ({ navigation: {
|
|
2714
|
+
clear: () => clearNavigationFeedbackTarget(editor),
|
|
2715
|
+
flashTarget: (options) => flashTarget(editor, options),
|
|
2716
|
+
navigate: (options) => navigate(editor, options)
|
|
2717
|
+
} }));
|
|
2718
|
+
|
|
2070
2719
|
//#endregion
|
|
2071
2720
|
//#region src/lib/plugins/node-id/withNodeId.ts
|
|
2072
2721
|
/** Enables support for inserting nodes with an id key. */
|
|
@@ -2402,6 +3051,31 @@ const insertExitBreak = (editor, { match, reverse } = {}) => {
|
|
|
2402
3051
|
return true;
|
|
2403
3052
|
};
|
|
2404
3053
|
|
|
3054
|
+
//#endregion
|
|
3055
|
+
//#region src/lib/plugins/slate-extension/transforms/liftBlock.ts
|
|
3056
|
+
/**
|
|
3057
|
+
* Lift the current block out of the nearest matching ancestor container.
|
|
3058
|
+
*
|
|
3059
|
+
* This unwraps only the current block and splits the ancestor around it when
|
|
3060
|
+
* needed, so one keypress changes one structural level instead of exploding the
|
|
3061
|
+
* whole container.
|
|
3062
|
+
*/
|
|
3063
|
+
const liftBlock = (editor, { at, match } = {}) => {
|
|
3064
|
+
const block = editor.api.block({ at });
|
|
3065
|
+
if (!block || !match) return;
|
|
3066
|
+
const [, blockPath] = block;
|
|
3067
|
+
if (!editor.api.above({
|
|
3068
|
+
at: blockPath,
|
|
3069
|
+
match: combineMatchOptions(editor, (_node, path) => path.length < blockPath.length, { match })
|
|
3070
|
+
})) return;
|
|
3071
|
+
editor.tf.unwrapNodes({
|
|
3072
|
+
at: blockPath,
|
|
3073
|
+
match,
|
|
3074
|
+
split: true
|
|
3075
|
+
});
|
|
3076
|
+
return true;
|
|
3077
|
+
};
|
|
3078
|
+
|
|
2405
3079
|
//#endregion
|
|
2406
3080
|
//#region src/lib/plugins/slate-extension/transforms/resetBlock.ts
|
|
2407
3081
|
/**
|
|
@@ -2439,25 +3113,27 @@ const setValue = (editor, value) => {
|
|
|
2439
3113
|
|
|
2440
3114
|
//#endregion
|
|
2441
3115
|
//#region src/lib/plugins/slate-extension/SlateExtensionPlugin.ts
|
|
3116
|
+
const NOOP_ON_NODE_CHANGE = () => {};
|
|
3117
|
+
const NOOP_ON_TEXT_CHANGE = () => {};
|
|
2442
3118
|
/** Opinionated extension of slate default behavior. */
|
|
2443
3119
|
const SlateExtensionPlugin = createTSlatePlugin({
|
|
2444
3120
|
api: { redecorate: () => {} },
|
|
2445
3121
|
key: "slateExtension",
|
|
2446
3122
|
options: {
|
|
2447
|
-
onNodeChange:
|
|
2448
|
-
onTextChange:
|
|
3123
|
+
onNodeChange: NOOP_ON_NODE_CHANGE,
|
|
3124
|
+
onTextChange: NOOP_ON_TEXT_CHANGE
|
|
2449
3125
|
}
|
|
2450
3126
|
}).extendEditorTransforms(({ editor, getOption, tf }) => {
|
|
2451
3127
|
const apply = tf?.apply ?? editor.tf.apply;
|
|
2452
3128
|
return {
|
|
2453
3129
|
init: bindFirst(init, editor),
|
|
2454
3130
|
insertExitBreak: bindFirst(insertExitBreak, editor),
|
|
3131
|
+
liftBlock: bindFirst(liftBlock, editor),
|
|
2455
3132
|
resetBlock: bindFirst(resetBlock, editor),
|
|
2456
3133
|
setValue: bindFirst(setValue, editor),
|
|
2457
3134
|
apply(operation) {
|
|
2458
|
-
const
|
|
2459
|
-
const
|
|
2460
|
-
const hasTextHandlers = editor.meta.pluginCache.handlers.onTextChange.length > 0 || getOption("onTextChange") !== noop;
|
|
3135
|
+
const hasNodeHandlers = editor.meta.pluginCache.handlers.onNodeChange.length > 0 || getOption("onNodeChange") !== NOOP_ON_NODE_CHANGE;
|
|
3136
|
+
const hasTextHandlers = editor.meta.pluginCache.handlers.onTextChange.length > 0 || getOption("onTextChange") !== NOOP_ON_TEXT_CHANGE;
|
|
2461
3137
|
if (!hasNodeHandlers && !hasTextHandlers) {
|
|
2462
3138
|
apply(operation);
|
|
2463
3139
|
return;
|
|
@@ -2655,16 +3331,159 @@ const ParserPlugin = createSlatePlugin({ key: "parser" }).overrideEditor(({ edit
|
|
|
2655
3331
|
insertData(dataTransfer);
|
|
2656
3332
|
} } }));
|
|
2657
3333
|
|
|
3334
|
+
//#endregion
|
|
3335
|
+
//#region src/lib/plugins/input-rules/internal/InputRulesPlugin.ts
|
|
3336
|
+
const createCachedGetter = (compute) => {
|
|
3337
|
+
let hasValue = false;
|
|
3338
|
+
let value;
|
|
3339
|
+
return () => {
|
|
3340
|
+
if (!hasValue) {
|
|
3341
|
+
value = compute();
|
|
3342
|
+
hasValue = true;
|
|
3343
|
+
}
|
|
3344
|
+
return value;
|
|
3345
|
+
};
|
|
3346
|
+
};
|
|
3347
|
+
const createSelectionContext = ({ editor }) => {
|
|
3348
|
+
const { selection } = editor;
|
|
3349
|
+
const isCollapsed = !!selection && editor.api.isCollapsed();
|
|
3350
|
+
const getBlockStartRange = createCachedGetter(() => {
|
|
3351
|
+
if (!selection) return;
|
|
3352
|
+
return editor.api.range("start", selection);
|
|
3353
|
+
});
|
|
3354
|
+
const getBlockStartText = createCachedGetter(() => {
|
|
3355
|
+
const range = getBlockStartRange();
|
|
3356
|
+
return range ? editor.api.string(range) : void 0;
|
|
3357
|
+
});
|
|
3358
|
+
return {
|
|
3359
|
+
editor,
|
|
3360
|
+
getBlockEntry: createCachedGetter(() => {
|
|
3361
|
+
if (!selection) return;
|
|
3362
|
+
return editor.api.block({ at: selection });
|
|
3363
|
+
}),
|
|
3364
|
+
getBlockStartRange,
|
|
3365
|
+
getBlockStartText,
|
|
3366
|
+
getBlockTextBeforeSelection: createCachedGetter(() => getBlockStartText() ?? ""),
|
|
3367
|
+
getCharAfter: createCachedGetter(() => {
|
|
3368
|
+
if (!selection || !isCollapsed) return;
|
|
3369
|
+
const afterPoint = editor.api.after(selection, {
|
|
3370
|
+
distance: 1,
|
|
3371
|
+
unit: "character"
|
|
3372
|
+
});
|
|
3373
|
+
if (!afterPoint) return;
|
|
3374
|
+
return editor.api.string({
|
|
3375
|
+
anchor: selection.anchor,
|
|
3376
|
+
focus: afterPoint
|
|
3377
|
+
}) || void 0;
|
|
3378
|
+
}),
|
|
3379
|
+
getCharBefore: createCachedGetter(() => {
|
|
3380
|
+
if (!selection || !isCollapsed) return;
|
|
3381
|
+
const beforePoint = editor.api.before(selection, {
|
|
3382
|
+
distance: 1,
|
|
3383
|
+
unit: "character"
|
|
3384
|
+
});
|
|
3385
|
+
if (!beforePoint) return;
|
|
3386
|
+
return editor.api.string({
|
|
3387
|
+
anchor: beforePoint,
|
|
3388
|
+
focus: selection.anchor
|
|
3389
|
+
}) || void 0;
|
|
3390
|
+
}),
|
|
3391
|
+
isCollapsed
|
|
3392
|
+
};
|
|
3393
|
+
};
|
|
3394
|
+
const isTriggerMatch = (trigger, text) => Array.isArray(trigger) ? trigger.includes(text) : trigger === text;
|
|
3395
|
+
const InputRulesPlugin = createTSlatePlugin({
|
|
3396
|
+
editOnly: true,
|
|
3397
|
+
key: "inputRules"
|
|
3398
|
+
}).overrideEditor(({ editor, tf: { insertBreak, insertData, insertText } }) => ({ transforms: {
|
|
3399
|
+
insertBreak() {
|
|
3400
|
+
const selectionContext = createSelectionContext({ editor });
|
|
3401
|
+
let handled = false;
|
|
3402
|
+
for (const rule of editor.meta.inputRules.insertBreak) {
|
|
3403
|
+
const context = {
|
|
3404
|
+
cause: "insertBreak",
|
|
3405
|
+
insertBreak,
|
|
3406
|
+
pluginKey: rule.pluginKey,
|
|
3407
|
+
...selectionContext
|
|
3408
|
+
};
|
|
3409
|
+
if (rule.enabled?.(context) === false) continue;
|
|
3410
|
+
const match = rule.resolve ? rule.resolve(context) : true;
|
|
3411
|
+
if (match === void 0) continue;
|
|
3412
|
+
if (rule.apply(context, match) !== false) {
|
|
3413
|
+
handled = true;
|
|
3414
|
+
break;
|
|
3415
|
+
}
|
|
3416
|
+
}
|
|
3417
|
+
if (handled) return;
|
|
3418
|
+
insertBreak();
|
|
3419
|
+
},
|
|
3420
|
+
insertData(data) {
|
|
3421
|
+
const text = data.getData("text/plain") || null;
|
|
3422
|
+
const selectionContext = createSelectionContext({ editor });
|
|
3423
|
+
let handled = false;
|
|
3424
|
+
for (const rule of editor.meta.inputRules.insertData) {
|
|
3425
|
+
const context = {
|
|
3426
|
+
cause: "insertData",
|
|
3427
|
+
data,
|
|
3428
|
+
insertData,
|
|
3429
|
+
pluginKey: rule.pluginKey,
|
|
3430
|
+
text,
|
|
3431
|
+
...selectionContext
|
|
3432
|
+
};
|
|
3433
|
+
if (rule.enabled?.(context) === false) continue;
|
|
3434
|
+
if (rule.mimeTypes && rule.mimeTypes.length > 0 && !rule.mimeTypes.some((type) => !!context.data.getData(type))) continue;
|
|
3435
|
+
const match = rule.resolve ? rule.resolve(context) : true;
|
|
3436
|
+
if (match === void 0) continue;
|
|
3437
|
+
if (rule.apply(context, match) !== false) {
|
|
3438
|
+
handled = true;
|
|
3439
|
+
break;
|
|
3440
|
+
}
|
|
3441
|
+
}
|
|
3442
|
+
if (handled) return;
|
|
3443
|
+
insertData(data);
|
|
3444
|
+
},
|
|
3445
|
+
insertText(text, options) {
|
|
3446
|
+
const rules = editor.meta.inputRules.insertText.byTrigger[text] ?? [];
|
|
3447
|
+
const selectionContext = createSelectionContext({ editor });
|
|
3448
|
+
let handled = false;
|
|
3449
|
+
for (const rule of rules) {
|
|
3450
|
+
const context = {
|
|
3451
|
+
cause: "insertText",
|
|
3452
|
+
insertText,
|
|
3453
|
+
options,
|
|
3454
|
+
pluginKey: rule.pluginKey,
|
|
3455
|
+
text,
|
|
3456
|
+
...selectionContext
|
|
3457
|
+
};
|
|
3458
|
+
if (!isTriggerMatch(rule.trigger, context.text)) continue;
|
|
3459
|
+
if (rule.enabled?.(context) === false) continue;
|
|
3460
|
+
const match = rule.resolve ? rule.resolve(context) : true;
|
|
3461
|
+
if (match === void 0) continue;
|
|
3462
|
+
if (rule.apply(context, match) !== false) {
|
|
3463
|
+
handled = true;
|
|
3464
|
+
break;
|
|
3465
|
+
}
|
|
3466
|
+
}
|
|
3467
|
+
if (handled) return;
|
|
3468
|
+
insertText(text, options);
|
|
3469
|
+
}
|
|
3470
|
+
} }));
|
|
3471
|
+
|
|
2658
3472
|
//#endregion
|
|
2659
3473
|
//#region src/lib/plugins/getCorePlugins.ts
|
|
2660
|
-
const getCorePlugins = ({ affinity, chunking, maxLength, nodeId, plugins = [] }) => {
|
|
3474
|
+
const getCorePlugins = ({ affinity, chunking, maxLength, navigationFeedback, nodeId, plugins = [] }) => {
|
|
2661
3475
|
let resolvedNodeId = nodeId;
|
|
2662
3476
|
if (process.env.NODE_ENV === "test" && nodeId === void 0) resolvedNodeId = false;
|
|
2663
3477
|
let corePlugins = [
|
|
2664
3478
|
DebugPlugin,
|
|
2665
3479
|
SlateExtensionPlugin,
|
|
2666
3480
|
DOMPlugin,
|
|
3481
|
+
NavigationFeedbackPlugin.configure({
|
|
3482
|
+
enabled: navigationFeedback !== false,
|
|
3483
|
+
options: typeof navigationFeedback === "boolean" ? void 0 : navigationFeedback
|
|
3484
|
+
}),
|
|
2667
3485
|
HistoryPlugin,
|
|
3486
|
+
InputRulesPlugin,
|
|
2668
3487
|
OverridePlugin,
|
|
2669
3488
|
ParserPlugin,
|
|
2670
3489
|
maxLength ? LengthPlugin.configure({ options: { maxLength } }) : LengthPlugin,
|
|
@@ -2707,7 +3526,7 @@ const getCorePlugins = ({ affinity, chunking, maxLength, nodeId, plugins = [] })
|
|
|
2707
3526
|
* @see {@link usePlateEditor} for a memoized React version.
|
|
2708
3527
|
* @see {@link withPlate} for the React-specific enhancement function.
|
|
2709
3528
|
*/
|
|
2710
|
-
const withSlate = (e, { id, affinity = true, autoSelect, chunking = true, maxLength, nodeId, optionsStoreFactory, plugins = [], readOnly = false, rootPlugin, selection, shouldNormalizeEditor, skipInitialization, userId, value, onReady, ...pluginConfig } = {}) => {
|
|
3529
|
+
const withSlate = (e, { id, affinity = true, autoSelect, chunking = true, maxLength, navigationFeedback, nodeId, optionsStoreFactory, plugins = [], readOnly = false, rootPlugin, selection, shouldNormalizeEditor, skipInitialization, userId, value, onReady, ...pluginConfig } = {}) => {
|
|
2711
3530
|
const editor = e;
|
|
2712
3531
|
editor.id = id ?? editor.id ?? nanoid();
|
|
2713
3532
|
editor.meta.key = editor.meta.key ?? nanoid();
|
|
@@ -2765,6 +3584,7 @@ const withSlate = (e, { id, affinity = true, autoSelect, chunking = true, maxLen
|
|
|
2765
3584
|
affinity,
|
|
2766
3585
|
chunking,
|
|
2767
3586
|
maxLength,
|
|
3587
|
+
navigationFeedback,
|
|
2768
3588
|
nodeId,
|
|
2769
3589
|
plugins
|
|
2770
3590
|
});
|
|
@@ -2837,4 +3657,4 @@ const withSlate = (e, { id, affinity = true, autoSelect, chunking = true, maxLen
|
|
|
2837
3657
|
const createSlateEditor = ({ editor = createEditor(), ...options } = {}) => withSlate(editor, options);
|
|
2838
3658
|
|
|
2839
3659
|
//#endregion
|
|
2840
|
-
export {
|
|
3660
|
+
export { isHtmlBlockElement as $, defineInputRule as $t, htmlStringToDOMNode as A, isSlatePluginElement as At, htmlBrToNewLine as B, withDeleteRules as Bt, resolveNavigationFeedbackTarget as C, getInjectMatch as Ct, HtmlPlugin as D, isSlateElement as Dt, LengthPlugin as E, isSlateEditor as Et, pipeDeserializeHtmlLeaf as F, applyDeepToNodes as Ft, inferWhiteSpaceRule as G, AstPlugin as Gt, deserializeHtmlNodeChildren as H, BaseParagraphPlugin as Ht, htmlElementToElement as I, OverridePlugin as It, collapseWhiteSpaceText as J, createMarkInputRule as Jt, collapseWhiteSpaceChildren as K, createBlockFenceInputRule as Kt, pipeDeserializeHtmlElement as L, withOverrides as Lt, deserializeHtmlNode as M, isSlateString as Mt, htmlTextNodeToString as N, isSlateText as Nt, parseHtmlDocument as O, isSlateLeaf as Ot, htmlElementToLeaf as P, isSlateVoid as Pt, collapseString as Q, matchDelimitedInline as Qt, pluginDeserializeHtml as R, withNormalizeRules as Rt, flashTarget as S, getInjectedPlugins as St, NavigationFeedbackPluginKey as T, getSlateElements as Tt, collapseWhiteSpace as U, HistoryPlugin as Ut, htmlBodyToFragment as V, withBreakRules as Vt, collapseWhiteSpaceElement as W, withPlateHistory as Wt, upsertInlineFormattingContext as X, matchBlockFence as Xt, endInlineFormattingContext as Y, createTextSubstitutionInputRule as Yt, isLastNonEmptyTextOfInlineFormattingContext as Z, matchBlockStart as Zt, normalizeNodeId as _, mergeDeepToNodes as _t, pipeInsertDataQuery as a, getPluginTypes as an, DOMPlugin as at, navigate as b, getNodeDataAttributeKeys as bt, setValue as c, createSlatePlugin as cn, PlateError as ct, insertExitBreak as d, AffinityPlugin as dt, getContainerTypes as en, isHtmlInlineElement as et, init as f, setAffinitySelection as ft, NodeIdPlugin as g, getEdgeNodes as gt, pipeOnNodeChange as h, getMarkBoundaryAffinity as ht, ParserPlugin as i, getPluginType as in, AUTO_SCROLL as it, deserializeHtmlElement as j, isSlatePluginNode as jt, deserializeHtml as k, isSlateNode as kt, resetBlock as l, createTSlatePlugin as ln, ChunkingPlugin as lt, pipeOnTextChange as m, isNodesAffinity as mt, withSlate as n, getPluginKey as nn, isHtmlText as nt, normalizeDescendantsToDocumentFragment as o, getSlatePlugin as on, withScrolling as ot, isEditOnly as p, isNodeAffinity as pt, collapseWhiteSpaceNode as q, createBlockStartInputRule as qt, getCorePlugins as r, getPluginKeys as rn, isHtmlElement as rt, SlateExtensionPlugin as s, getEditorPlugin as sn, DebugPlugin as st, createSlateEditor as t, getPluginByType as tn, inlineTagNames as tt, liftBlock as u, withChunking as ut, withNodeId as v, getSlateClass as vt, NAVIGATION_FEEDBACK_KEY as w, defaultsDeepToNodes as wt, clearNavigationFeedbackTarget as x, keyToDataAttribute as xt, NavigationFeedbackPlugin as y, getPluginNodeProps as yt, getDataNodeProps as z, withMergeRules as zt };
|