slate-angular 20.2.18 → 20.2.20
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/fesm2022/slate-angular.mjs +851 -822
- package/fesm2022/slate-angular.mjs.map +1 -1
- package/index.d.ts +8 -3
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Editor, Range, Element, Transforms, Text as Text$1, Node, Path } from 'slate';
|
|
2
|
-
import { EDITOR_TO_ELEMENT, NODE_TO_ELEMENT, DOMEditor, normalizeDOMPoint, isDOMSelection, IS_CHROME as IS_CHROME$1, hasShadowRoot, isDOMElement, NODE_TO_PARENT, NODE_TO_INDEX,
|
|
2
|
+
import { EDITOR_TO_ELEMENT, NODE_TO_ELEMENT, DOMEditor, normalizeDOMPoint, isDOMSelection, IS_CHROME as IS_CHROME$1, hasShadowRoot, isDOMElement, NODE_TO_PARENT, NODE_TO_INDEX, isDOMNode, IS_FOCUSED, withDOM, NODE_TO_KEY, ELEMENT_TO_NODE, getDefaultView, EDITOR_TO_WINDOW, IS_READ_ONLY, EDITOR_TO_ON_CHANGE, TRIPLE_CLICK, isPlainTextOnlyPaste } from 'slate-dom';
|
|
3
3
|
import { isKeyHotkey } from 'is-hotkey';
|
|
4
4
|
import * as i0 from '@angular/core';
|
|
5
5
|
import { TemplateRef, ComponentRef, IterableDiffers, inject, ViewContainerRef, forwardRef, HostBinding, Input, ChangeDetectionStrategy, Component, NgModule, ElementRef, ChangeDetectorRef, Directive, ViewChild } from '@angular/core';
|
|
@@ -353,95 +353,6 @@ const CustomDOMEditor = {
|
|
|
353
353
|
}
|
|
354
354
|
};
|
|
355
355
|
|
|
356
|
-
/**
|
|
357
|
-
* Symbols.
|
|
358
|
-
*/
|
|
359
|
-
const PLACEHOLDER_SYMBOL = Symbol('placeholder');
|
|
360
|
-
/**
|
|
361
|
-
* Weak map for associating the html element with the component.
|
|
362
|
-
*/
|
|
363
|
-
const ELEMENT_TO_COMPONENT = new WeakMap();
|
|
364
|
-
const IS_ENABLED_VIRTUAL_SCROLL = new WeakMap();
|
|
365
|
-
const EDITOR_TO_VIRTUAL_SCROLL_SELECTION = new WeakMap();
|
|
366
|
-
const EDITOR_TO_AFTER_VIEW_INIT_QUEUE = new WeakMap();
|
|
367
|
-
|
|
368
|
-
const AngularEditor = {
|
|
369
|
-
...CustomDOMEditor,
|
|
370
|
-
/**
|
|
371
|
-
* handle editor error.
|
|
372
|
-
*/
|
|
373
|
-
onError(errorData) {
|
|
374
|
-
if (errorData.nativeError) {
|
|
375
|
-
throw errorData.nativeError;
|
|
376
|
-
}
|
|
377
|
-
},
|
|
378
|
-
/**
|
|
379
|
-
* onKeydown hook.
|
|
380
|
-
*/
|
|
381
|
-
onKeydown(editor, data) {
|
|
382
|
-
editor.onKeydown(data);
|
|
383
|
-
},
|
|
384
|
-
/**
|
|
385
|
-
* onClick hook.
|
|
386
|
-
*/
|
|
387
|
-
onClick(editor, data) {
|
|
388
|
-
editor.onClick(data);
|
|
389
|
-
},
|
|
390
|
-
deleteCutData(editor) {
|
|
391
|
-
editor.deleteCutData();
|
|
392
|
-
},
|
|
393
|
-
isLeafBlock(editor, node) {
|
|
394
|
-
return Element.isElement(node) && !editor.isInline(node) && Editor.hasInlines(editor, node);
|
|
395
|
-
},
|
|
396
|
-
/**
|
|
397
|
-
* move native selection to card-left or card-right
|
|
398
|
-
* @param editor
|
|
399
|
-
* @param blockCardNode
|
|
400
|
-
* @param options
|
|
401
|
-
*/
|
|
402
|
-
moveBlockCard(editor, blockCardNode, options) {
|
|
403
|
-
const cursorNode = AngularEditor.getCardCursorNode(editor, blockCardNode, options);
|
|
404
|
-
const window = AngularEditor.getWindow(editor);
|
|
405
|
-
const domSelection = window.getSelection();
|
|
406
|
-
domSelection.setBaseAndExtent(cursorNode, 1, cursorNode, 1);
|
|
407
|
-
},
|
|
408
|
-
/**
|
|
409
|
-
* move slate selection to card-left or card-right
|
|
410
|
-
* @param editor
|
|
411
|
-
* @param path
|
|
412
|
-
* @param options
|
|
413
|
-
*/
|
|
414
|
-
moveBlockCardCursor(editor, path, options) {
|
|
415
|
-
const cursor = {
|
|
416
|
-
path,
|
|
417
|
-
offset: options.direction === 'left' ? FAKE_LEFT_BLOCK_CARD_OFFSET : FAKE_RIGHT_BLOCK_CARD_OFFSET
|
|
418
|
-
};
|
|
419
|
-
Transforms.select(editor, { anchor: cursor, focus: cursor });
|
|
420
|
-
},
|
|
421
|
-
focus: (editor, options = { retries: 5 }) => {
|
|
422
|
-
// Return if already focused
|
|
423
|
-
if (IS_FOCUSED.get(editor)) {
|
|
424
|
-
return;
|
|
425
|
-
}
|
|
426
|
-
// Return if no dom node is associated with the editor, which means the editor is not yet mounted
|
|
427
|
-
// or has been unmounted. This can happen especially, while retrying to focus the editor.
|
|
428
|
-
if (!EDITOR_TO_ELEMENT.get(editor)) {
|
|
429
|
-
return;
|
|
430
|
-
}
|
|
431
|
-
IS_FOCUSED.set(editor, true);
|
|
432
|
-
const el = DOMEditor.toDOMNode(editor, editor);
|
|
433
|
-
const root = DOMEditor.findDocumentOrShadowRoot(editor);
|
|
434
|
-
if (root.activeElement !== el) {
|
|
435
|
-
// IS_FOCUSED should be set before calling el.focus() to ensure that
|
|
436
|
-
// FocusedContext is updated to the correct value
|
|
437
|
-
el.focus({ preventScroll: true });
|
|
438
|
-
}
|
|
439
|
-
},
|
|
440
|
-
isEnabledVirtualScroll(editor) {
|
|
441
|
-
return IS_ENABLED_VIRTUAL_SCROLL.get(editor);
|
|
442
|
-
}
|
|
443
|
-
};
|
|
444
|
-
|
|
445
356
|
const IS_IOS = typeof navigator !== 'undefined' &&
|
|
446
357
|
typeof window !== 'undefined' &&
|
|
447
358
|
/iPad|iPhone|iPod/.test(navigator.userAgent) &&
|
|
@@ -480,493 +391,28 @@ const SLATE_DEBUG_KEY = '__SLATE_DEBUG__';
|
|
|
480
391
|
const SLATE_DEBUG_KEY_SCROLL_TOP = '__SLATE_DEBUG_SCROLL_TOP__';
|
|
481
392
|
|
|
482
393
|
/**
|
|
483
|
-
*
|
|
484
|
-
*/
|
|
485
|
-
const HOTKEYS = {
|
|
486
|
-
bold: 'mod+b',
|
|
487
|
-
compose: ['down', 'left', 'right', 'up', 'backspace', 'enter'],
|
|
488
|
-
moveBackward: 'left',
|
|
489
|
-
moveForward: 'right',
|
|
490
|
-
moveUp: 'up',
|
|
491
|
-
moveDown: 'down',
|
|
492
|
-
moveWordBackward: 'ctrl+left',
|
|
493
|
-
moveWordForward: 'ctrl+right',
|
|
494
|
-
deleteBackward: 'shift?+backspace',
|
|
495
|
-
deleteForward: 'shift?+delete',
|
|
496
|
-
extendBackward: 'shift+left',
|
|
497
|
-
extendForward: 'shift+right',
|
|
498
|
-
italic: 'mod+i',
|
|
499
|
-
splitBlock: 'shift?+enter',
|
|
500
|
-
undo: 'mod+z'
|
|
501
|
-
};
|
|
502
|
-
const APPLE_HOTKEYS = {
|
|
503
|
-
moveLineBackward: 'opt+up',
|
|
504
|
-
moveLineForward: 'opt+down',
|
|
505
|
-
moveWordBackward: 'opt+left',
|
|
506
|
-
moveWordForward: 'opt+right',
|
|
507
|
-
deleteBackward: ['ctrl+backspace', 'ctrl+h'],
|
|
508
|
-
deleteForward: ['ctrl+delete', 'ctrl+d'],
|
|
509
|
-
deleteLineBackward: 'cmd+shift?+backspace',
|
|
510
|
-
deleteLineForward: ['cmd+shift?+delete', 'ctrl+k'],
|
|
511
|
-
deleteWordBackward: 'opt+shift?+backspace',
|
|
512
|
-
deleteWordForward: 'opt+shift?+delete',
|
|
513
|
-
extendLineBackward: 'opt+shift+up',
|
|
514
|
-
extendLineForward: 'opt+shift+down',
|
|
515
|
-
redo: 'cmd+shift+z',
|
|
516
|
-
transposeCharacter: 'ctrl+t'
|
|
517
|
-
};
|
|
518
|
-
const WINDOWS_HOTKEYS = {
|
|
519
|
-
deleteWordBackward: 'ctrl+shift?+backspace',
|
|
520
|
-
deleteWordForward: 'ctrl+shift?+delete',
|
|
521
|
-
redo: ['ctrl+y', 'ctrl+shift+z']
|
|
522
|
-
};
|
|
523
|
-
/**
|
|
524
|
-
* Create a platform-aware hotkey checker.
|
|
394
|
+
* Symbols.
|
|
525
395
|
*/
|
|
526
|
-
const
|
|
527
|
-
const generic = HOTKEYS[key];
|
|
528
|
-
const apple = APPLE_HOTKEYS[key];
|
|
529
|
-
const windows = WINDOWS_HOTKEYS[key];
|
|
530
|
-
const isGeneric = generic && isKeyHotkey(generic);
|
|
531
|
-
const isApple = apple && isKeyHotkey(apple);
|
|
532
|
-
const isWindows = windows && isKeyHotkey(windows);
|
|
533
|
-
return (event) => {
|
|
534
|
-
if (isGeneric && isGeneric(event)) {
|
|
535
|
-
return true;
|
|
536
|
-
}
|
|
537
|
-
if (IS_APPLE && isApple && isApple(event)) {
|
|
538
|
-
return true;
|
|
539
|
-
}
|
|
540
|
-
if (!IS_APPLE && isWindows && isWindows(event)) {
|
|
541
|
-
return true;
|
|
542
|
-
}
|
|
543
|
-
return false;
|
|
544
|
-
};
|
|
545
|
-
};
|
|
396
|
+
const PLACEHOLDER_SYMBOL = Symbol('placeholder');
|
|
546
397
|
/**
|
|
547
|
-
*
|
|
398
|
+
* Weak map for associating the html element with the component.
|
|
548
399
|
*/
|
|
549
|
-
const
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
isMoveBackward: create('moveBackward'),
|
|
553
|
-
isMoveForward: create('moveForward'),
|
|
554
|
-
isMoveUp: create('moveUp'),
|
|
555
|
-
isMoveDown: create('moveDown'),
|
|
556
|
-
isDeleteBackward: create('deleteBackward'),
|
|
557
|
-
isDeleteForward: create('deleteForward'),
|
|
558
|
-
isDeleteLineBackward: create('deleteLineBackward'),
|
|
559
|
-
isDeleteLineForward: create('deleteLineForward'),
|
|
560
|
-
isDeleteWordBackward: create('deleteWordBackward'),
|
|
561
|
-
isDeleteWordForward: create('deleteWordForward'),
|
|
562
|
-
isExtendBackward: create('extendBackward'),
|
|
563
|
-
isExtendForward: create('extendForward'),
|
|
564
|
-
isExtendLineBackward: create('extendLineBackward'),
|
|
565
|
-
isExtendLineForward: create('extendLineForward'),
|
|
566
|
-
isItalic: create('italic'),
|
|
567
|
-
isMoveLineBackward: create('moveLineBackward'),
|
|
568
|
-
isMoveLineForward: create('moveLineForward'),
|
|
569
|
-
isMoveWordBackward: create('moveWordBackward'),
|
|
570
|
-
isMoveWordForward: create('moveWordForward'),
|
|
571
|
-
isRedo: create('redo'),
|
|
572
|
-
isSplitBlock: create('splitBlock'),
|
|
573
|
-
isTransposeCharacter: create('transposeCharacter'),
|
|
574
|
-
isUndo: create('undo')
|
|
575
|
-
};
|
|
576
|
-
|
|
577
|
-
function isTemplateRef(value) {
|
|
578
|
-
return value && value instanceof TemplateRef;
|
|
579
|
-
}
|
|
580
|
-
function isComponentType(value) {
|
|
581
|
-
return !isTemplateRef(value);
|
|
582
|
-
}
|
|
583
|
-
function isFlavourType(value) {
|
|
584
|
-
return value && value.isFlavour === true;
|
|
585
|
-
}
|
|
400
|
+
const ELEMENT_TO_COMPONENT = new WeakMap();
|
|
401
|
+
const EDITOR_TO_VIRTUAL_SCROLL_SELECTION = new WeakMap();
|
|
402
|
+
const EDITOR_TO_AFTER_VIEW_INIT_QUEUE = new WeakMap();
|
|
586
403
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
const isDecoratorRangeListEqual = (list, another) => {
|
|
597
|
-
if (list.length !== another.length) {
|
|
598
|
-
return false;
|
|
599
|
-
}
|
|
600
|
-
for (let i = 0; i < list.length; i++) {
|
|
601
|
-
const range = list[i];
|
|
602
|
-
const other = another[i];
|
|
603
|
-
const { anchor: rangeAnchor, focus: rangeFocus, ...rangeOwnProps } = range;
|
|
604
|
-
const { anchor: otherAnchor, focus: otherFocus, ...otherOwnProps } = other;
|
|
605
|
-
if (!Range.equals(range, other) ||
|
|
606
|
-
range[PLACEHOLDER_SYMBOL] !== other[PLACEHOLDER_SYMBOL] ||
|
|
607
|
-
!shallowCompare(rangeOwnProps, otherOwnProps)) {
|
|
608
|
-
return false;
|
|
404
|
+
class VirtualScrollDebugOverlay {
|
|
405
|
+
static { this.storageKey = 'slate_virtual_scroll_debug_overlay_state'; }
|
|
406
|
+
static { this.minWidth = 320; }
|
|
407
|
+
static { this.minHeight = 240; }
|
|
408
|
+
static { this.defaultWidth = 410; }
|
|
409
|
+
static { this.defaultHeight = 480; }
|
|
410
|
+
static getInstance(doc) {
|
|
411
|
+
if (!this.instance) {
|
|
412
|
+
this.instance = new VirtualScrollDebugOverlay(doc);
|
|
609
413
|
}
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
};
|
|
613
|
-
|
|
614
|
-
const isValid = (value) => (Element.isElement(value) && value.children.length > 0 && value.children.every(child => isValid(child))) ||
|
|
615
|
-
Text$1.isText(value);
|
|
616
|
-
const check = (document) => {
|
|
617
|
-
return document.every(value => Element.isElement(value) && isValid(value));
|
|
618
|
-
};
|
|
619
|
-
function normalize(document) {
|
|
620
|
-
return document.filter(value => Element.isElement(value) && isValid(value));
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
const createThrottleRAF = () => {
|
|
624
|
-
let timerId = null;
|
|
625
|
-
const throttleRAF = (fn) => {
|
|
626
|
-
const scheduleFunc = () => {
|
|
627
|
-
timerId = requestAnimationFrame(() => {
|
|
628
|
-
timerId = null;
|
|
629
|
-
fn();
|
|
630
|
-
});
|
|
631
|
-
};
|
|
632
|
-
if (timerId !== null) {
|
|
633
|
-
cancelAnimationFrame(timerId);
|
|
634
|
-
timerId = null;
|
|
635
|
-
}
|
|
636
|
-
scheduleFunc();
|
|
637
|
-
};
|
|
638
|
-
return throttleRAF;
|
|
639
|
-
};
|
|
640
|
-
|
|
641
|
-
const isClipboardReadSupported = () => {
|
|
642
|
-
return 'clipboard' in navigator && 'read' in navigator.clipboard;
|
|
643
|
-
};
|
|
644
|
-
const isClipboardWriteSupported = () => {
|
|
645
|
-
return 'clipboard' in navigator && 'write' in navigator.clipboard;
|
|
646
|
-
};
|
|
647
|
-
const isClipboardWriteTextSupported = () => {
|
|
648
|
-
return 'clipboard' in navigator && 'writeText' in navigator.clipboard;
|
|
649
|
-
};
|
|
650
|
-
const isClipboardFile = (item) => {
|
|
651
|
-
return item.types.find(i => i.match(/^image\//));
|
|
652
|
-
};
|
|
653
|
-
const isInvalidTable = (nodes = []) => {
|
|
654
|
-
return nodes.some(node => node.tagName.toLowerCase() === 'tr');
|
|
655
|
-
};
|
|
656
|
-
const stripHtml = (html) => {
|
|
657
|
-
// See <https://github.com/developit/preact-markup/blob/4788b8d61b4e24f83688710746ee36e7464f7bbc/src/parse-markup.js#L60-L69>
|
|
658
|
-
const doc = document.implementation.createHTMLDocument('');
|
|
659
|
-
doc.documentElement.innerHTML = html.trim();
|
|
660
|
-
return doc.body.textContent || doc.body.innerText || '';
|
|
661
|
-
};
|
|
662
|
-
const blobAsString = (blob) => {
|
|
663
|
-
return new Promise((resolve, reject) => {
|
|
664
|
-
const reader = new FileReader();
|
|
665
|
-
reader.addEventListener('loadend', () => {
|
|
666
|
-
const text = reader.result;
|
|
667
|
-
resolve(text);
|
|
668
|
-
});
|
|
669
|
-
reader.addEventListener('error', () => {
|
|
670
|
-
reject(reader.error);
|
|
671
|
-
});
|
|
672
|
-
reader.readAsText(blob);
|
|
673
|
-
});
|
|
674
|
-
};
|
|
675
|
-
const completeTable = (fragment) => {
|
|
676
|
-
const result = document.createDocumentFragment();
|
|
677
|
-
const table = document.createElement('table');
|
|
678
|
-
result.appendChild(table);
|
|
679
|
-
table.appendChild(fragment);
|
|
680
|
-
return result;
|
|
681
|
-
};
|
|
682
|
-
|
|
683
|
-
const setDataTransferClipboard = (dataTransfer, htmlText) => {
|
|
684
|
-
dataTransfer?.setData(`text/html`, htmlText);
|
|
685
|
-
};
|
|
686
|
-
const setDataTransferClipboardText = (data, text) => {
|
|
687
|
-
data?.setData(`text/plain`, text);
|
|
688
|
-
};
|
|
689
|
-
const getDataTransferClipboard = (data) => {
|
|
690
|
-
const html = data?.getData(`text/html`);
|
|
691
|
-
if (html) {
|
|
692
|
-
const htmlClipboardData = getClipboardFromHTMLText(html);
|
|
693
|
-
if (htmlClipboardData) {
|
|
694
|
-
return htmlClipboardData;
|
|
695
|
-
}
|
|
696
|
-
const textData = getDataTransferClipboardText(data);
|
|
697
|
-
if (textData) {
|
|
698
|
-
return {
|
|
699
|
-
html,
|
|
700
|
-
...textData
|
|
701
|
-
};
|
|
702
|
-
}
|
|
703
|
-
else {
|
|
704
|
-
return { html };
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
else {
|
|
708
|
-
const textData = getDataTransferClipboardText(data);
|
|
709
|
-
return textData;
|
|
710
|
-
}
|
|
711
|
-
};
|
|
712
|
-
const getDataTransferClipboardText = (data) => {
|
|
713
|
-
if (!data) {
|
|
714
|
-
return null;
|
|
715
|
-
}
|
|
716
|
-
const text = data?.getData(`text/plain`);
|
|
717
|
-
if (text) {
|
|
718
|
-
const htmlClipboardData = getClipboardFromHTMLText(text);
|
|
719
|
-
if (htmlClipboardData) {
|
|
720
|
-
return htmlClipboardData;
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
return { text };
|
|
724
|
-
};
|
|
725
|
-
|
|
726
|
-
const setNavigatorClipboard = async (htmlText, data, text = '') => {
|
|
727
|
-
let textClipboard = text;
|
|
728
|
-
if (isClipboardWriteSupported()) {
|
|
729
|
-
await navigator.clipboard.write([
|
|
730
|
-
new ClipboardItem({
|
|
731
|
-
'text/html': new Blob([htmlText], {
|
|
732
|
-
type: 'text/html'
|
|
733
|
-
}),
|
|
734
|
-
'text/plain': new Blob([textClipboard ?? JSON.stringify(data)], { type: 'text/plain' })
|
|
735
|
-
})
|
|
736
|
-
]);
|
|
737
|
-
}
|
|
738
|
-
};
|
|
739
|
-
const getNavigatorClipboard = async () => {
|
|
740
|
-
if (!isClipboardReadSupported()) {
|
|
741
|
-
return null;
|
|
742
|
-
}
|
|
743
|
-
const clipboardItems = await navigator.clipboard.read();
|
|
744
|
-
let clipboardData = {};
|
|
745
|
-
if (Array.isArray(clipboardItems) && clipboardItems[0] instanceof ClipboardItem) {
|
|
746
|
-
for (const item of clipboardItems) {
|
|
747
|
-
if (isClipboardFile(item)) {
|
|
748
|
-
const clipboardFiles = item.types.filter(type => type.match(/^image\//));
|
|
749
|
-
const fileBlobs = await Promise.all(clipboardFiles.map(type => item.getType(type)));
|
|
750
|
-
const urls = fileBlobs.filter(Boolean).map(blob => URL.createObjectURL(blob));
|
|
751
|
-
const files = await Promise.all(urls.map(async (url) => {
|
|
752
|
-
const blob = await (await fetch(url)).blob();
|
|
753
|
-
return new File([blob], 'file', { type: blob.type });
|
|
754
|
-
}));
|
|
755
|
-
clipboardData = {
|
|
756
|
-
...clipboardData,
|
|
757
|
-
files
|
|
758
|
-
};
|
|
759
|
-
}
|
|
760
|
-
if (item.types.includes('text/html')) {
|
|
761
|
-
const htmlContent = await blobAsString(await item.getType('text/html'));
|
|
762
|
-
const htmlClipboardData = getClipboardFromHTMLText(htmlContent);
|
|
763
|
-
if (htmlClipboardData) {
|
|
764
|
-
clipboardData = { ...clipboardData, ...htmlClipboardData };
|
|
765
|
-
return clipboardData;
|
|
766
|
-
}
|
|
767
|
-
if (htmlContent && htmlContent.trim()) {
|
|
768
|
-
clipboardData = { ...clipboardData, html: htmlContent };
|
|
769
|
-
}
|
|
770
|
-
}
|
|
771
|
-
if (item.types.includes('text/plain')) {
|
|
772
|
-
const textContent = await blobAsString(await item.getType('text/plain'));
|
|
773
|
-
clipboardData = {
|
|
774
|
-
...clipboardData,
|
|
775
|
-
text: stripHtml(textContent)
|
|
776
|
-
};
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
return clipboardData;
|
|
781
|
-
};
|
|
782
|
-
|
|
783
|
-
const SlateFragmentAttributeKey = 'data-slate-angular-fragment';
|
|
784
|
-
/**
|
|
785
|
-
* Get x-slate-fragment attribute from data-slate-angular-fragment
|
|
786
|
-
*/
|
|
787
|
-
const catchSlateFragment = /data-slate-angular-fragment="(.+?)"/m;
|
|
788
|
-
const getSlateFragmentAttribute = (htmlData) => {
|
|
789
|
-
const [, fragment] = htmlData.match(catchSlateFragment) || [];
|
|
790
|
-
return fragment;
|
|
791
|
-
};
|
|
792
|
-
/**
|
|
793
|
-
* Check if a DOM node is an element node.
|
|
794
|
-
*/
|
|
795
|
-
const isDOMText = (value) => {
|
|
796
|
-
return isDOMNode(value) && value.nodeType === 3;
|
|
797
|
-
};
|
|
798
|
-
/**
|
|
799
|
-
* Get a plaintext representation of the content of a node, accounting for block
|
|
800
|
-
* elements which get a newline appended.
|
|
801
|
-
*
|
|
802
|
-
* The domNode must be attached to the DOM.
|
|
803
|
-
*/
|
|
804
|
-
const getPlainText = (domNode) => {
|
|
805
|
-
let text = '';
|
|
806
|
-
if (isDOMText(domNode) && domNode.nodeValue) {
|
|
807
|
-
return domNode.nodeValue;
|
|
808
|
-
}
|
|
809
|
-
if (isDOMElement(domNode)) {
|
|
810
|
-
for (const childNode of Array.from(domNode.childNodes)) {
|
|
811
|
-
text += getPlainText(childNode);
|
|
812
|
-
}
|
|
813
|
-
const display = getComputedStyle(domNode).getPropertyValue('display');
|
|
814
|
-
if (display === 'block' || display === 'list' || domNode.tagName === 'BR') {
|
|
815
|
-
text += '\n';
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
return text;
|
|
819
|
-
};
|
|
820
|
-
/**
|
|
821
|
-
* Get the dom selection from Shadow Root if possible, otherwise from the document
|
|
822
|
-
*/
|
|
823
|
-
const getSelection = (root) => {
|
|
824
|
-
if (root.getSelection != null) {
|
|
825
|
-
return root.getSelection();
|
|
826
|
-
}
|
|
827
|
-
return document.getSelection();
|
|
828
|
-
};
|
|
829
|
-
const getContentHeight = (element) => {
|
|
830
|
-
if (!element)
|
|
831
|
-
return 0;
|
|
832
|
-
const style = window.getComputedStyle(element);
|
|
833
|
-
const boxSizing = style.boxSizing;
|
|
834
|
-
const height = parseFloat(style.height) || 0;
|
|
835
|
-
const paddingTop = parseFloat(style.paddingTop) || 0;
|
|
836
|
-
const paddingBottom = parseFloat(style.paddingBottom) || 0;
|
|
837
|
-
const totalPadding = paddingTop + paddingBottom;
|
|
838
|
-
const borderTop = parseFloat(style.borderTopWidth) || 0;
|
|
839
|
-
const borderBottom = parseFloat(style.borderBottomWidth) || 0;
|
|
840
|
-
const totalBorder = borderTop + borderBottom;
|
|
841
|
-
let contentHeight;
|
|
842
|
-
if (boxSizing === 'border-box') {
|
|
843
|
-
contentHeight = height - totalPadding - totalBorder;
|
|
844
|
-
}
|
|
845
|
-
else {
|
|
846
|
-
contentHeight = height;
|
|
847
|
-
}
|
|
848
|
-
return Math.max(contentHeight, 0);
|
|
849
|
-
};
|
|
850
|
-
const getZeroTextNode = () => {
|
|
851
|
-
return document.createTextNode('\uFEFF');
|
|
852
|
-
};
|
|
853
|
-
|
|
854
|
-
const buildHTMLText = (wrapper, attach, data) => {
|
|
855
|
-
const stringObj = JSON.stringify(data);
|
|
856
|
-
const encoded = window.btoa(encodeURIComponent(stringObj));
|
|
857
|
-
attach.setAttribute(SlateFragmentAttributeKey, encoded);
|
|
858
|
-
return wrapper.innerHTML;
|
|
859
|
-
};
|
|
860
|
-
const getClipboardFromHTMLText = (html) => {
|
|
861
|
-
const fragmentAttribute = getSlateFragmentAttribute(html);
|
|
862
|
-
if (fragmentAttribute) {
|
|
863
|
-
try {
|
|
864
|
-
const decoded = decodeURIComponent(window.atob(fragmentAttribute));
|
|
865
|
-
const result = JSON.parse(decoded);
|
|
866
|
-
if (result && Array.isArray(result) && result.length > 0) {
|
|
867
|
-
return {
|
|
868
|
-
elements: result
|
|
869
|
-
};
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
catch (error) {
|
|
873
|
-
console.error(error);
|
|
874
|
-
return null;
|
|
875
|
-
}
|
|
876
|
-
}
|
|
877
|
-
return null;
|
|
878
|
-
};
|
|
879
|
-
const createClipboardData = (html, elements, text, files) => {
|
|
880
|
-
const data = { elements, text, html, files };
|
|
881
|
-
return data;
|
|
882
|
-
};
|
|
883
|
-
const getClipboardData = async (dataTransfer) => {
|
|
884
|
-
let clipboardData = null;
|
|
885
|
-
if (dataTransfer) {
|
|
886
|
-
let filesData = {};
|
|
887
|
-
if (dataTransfer.files.length) {
|
|
888
|
-
filesData = { ...filesData, files: Array.from(dataTransfer.files) };
|
|
889
|
-
}
|
|
890
|
-
clipboardData = getDataTransferClipboard(dataTransfer);
|
|
891
|
-
return { ...clipboardData, ...filesData };
|
|
892
|
-
}
|
|
893
|
-
if (isClipboardReadSupported()) {
|
|
894
|
-
return await getNavigatorClipboard();
|
|
895
|
-
}
|
|
896
|
-
return clipboardData;
|
|
897
|
-
};
|
|
898
|
-
/**
|
|
899
|
-
* @param wrapper get wrapper.innerHTML string which will be written in clipboard
|
|
900
|
-
* @param attach attach must be child element of wrapper which will be attached json data
|
|
901
|
-
* @returns void
|
|
902
|
-
*/
|
|
903
|
-
const setClipboardData = async (clipboardData, wrapper, attach, dataTransfer) => {
|
|
904
|
-
if (!clipboardData) {
|
|
905
|
-
return;
|
|
906
|
-
}
|
|
907
|
-
const { elements, text } = clipboardData;
|
|
908
|
-
if (isClipboardWriteSupported()) {
|
|
909
|
-
const htmlText = buildHTMLText(wrapper, attach, elements);
|
|
910
|
-
// TODO
|
|
911
|
-
// maybe fail to write when copy some cell in table
|
|
912
|
-
return await setNavigatorClipboard(htmlText, elements, text);
|
|
913
|
-
}
|
|
914
|
-
if (dataTransfer) {
|
|
915
|
-
const htmlText = buildHTMLText(wrapper, attach, elements);
|
|
916
|
-
setDataTransferClipboard(dataTransfer, htmlText);
|
|
917
|
-
setDataTransferClipboardText(dataTransfer, text);
|
|
918
|
-
return;
|
|
919
|
-
}
|
|
920
|
-
const htmlText = buildHTMLText(wrapper, attach, elements);
|
|
921
|
-
// Compatible with situations where navigator.clipboard.write is not supported and dataTransfer is empty
|
|
922
|
-
// Such as contextmenu copy in Firefox.
|
|
923
|
-
if (isClipboardWriteTextSupported()) {
|
|
924
|
-
return await navigator.clipboard.writeText(htmlText);
|
|
925
|
-
}
|
|
926
|
-
else {
|
|
927
|
-
return await fallbackCopyText(htmlText);
|
|
928
|
-
}
|
|
929
|
-
};
|
|
930
|
-
const fallbackCopyText = async (text) => {
|
|
931
|
-
return new Promise((resolve, reject) => {
|
|
932
|
-
const textArea = document.createElement('textarea');
|
|
933
|
-
textArea.value = text;
|
|
934
|
-
textArea.style.position = 'fixed';
|
|
935
|
-
textArea.style.left = '-999999px';
|
|
936
|
-
textArea.style.top = '-999999px';
|
|
937
|
-
textArea.style.opacity = '0';
|
|
938
|
-
document.body.appendChild(textArea);
|
|
939
|
-
textArea.focus();
|
|
940
|
-
textArea.select();
|
|
941
|
-
try {
|
|
942
|
-
const successful = document.execCommand('copy');
|
|
943
|
-
document.body.removeChild(textArea);
|
|
944
|
-
if (successful) {
|
|
945
|
-
resolve();
|
|
946
|
-
}
|
|
947
|
-
else {
|
|
948
|
-
reject(new Error('execCommand error'));
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
catch (err) {
|
|
952
|
-
document.body.removeChild(textArea);
|
|
953
|
-
reject(err);
|
|
954
|
-
}
|
|
955
|
-
});
|
|
956
|
-
};
|
|
957
|
-
|
|
958
|
-
class VirtualScrollDebugOverlay {
|
|
959
|
-
static { this.storageKey = 'slate_virtual_scroll_debug_overlay_state'; }
|
|
960
|
-
static { this.minWidth = 320; }
|
|
961
|
-
static { this.minHeight = 240; }
|
|
962
|
-
static { this.defaultWidth = 410; }
|
|
963
|
-
static { this.defaultHeight = 480; }
|
|
964
|
-
static getInstance(doc) {
|
|
965
|
-
if (!this.instance) {
|
|
966
|
-
this.instance = new VirtualScrollDebugOverlay(doc);
|
|
967
|
-
}
|
|
968
|
-
this.instance.init();
|
|
969
|
-
return this.instance;
|
|
414
|
+
this.instance.init();
|
|
415
|
+
return this.instance;
|
|
970
416
|
}
|
|
971
417
|
static log(doc, type, ...args) {
|
|
972
418
|
this.getInstance(doc).log(type, ...args);
|
|
@@ -1367,285 +813,867 @@ class VirtualScrollDebugOverlay {
|
|
|
1367
813
|
this.resizeHandle.style.display = 'block';
|
|
1368
814
|
}
|
|
1369
815
|
}
|
|
1370
|
-
setCollapsed(collapsed) {
|
|
1371
|
-
this.state.collapsed = collapsed;
|
|
1372
|
-
this.applyCollapsedState();
|
|
1373
|
-
this.persistState();
|
|
816
|
+
setCollapsed(collapsed) {
|
|
817
|
+
this.state.collapsed = collapsed;
|
|
818
|
+
this.applyCollapsedState();
|
|
819
|
+
this.persistState();
|
|
820
|
+
}
|
|
821
|
+
applyState() {
|
|
822
|
+
this.applyPosition();
|
|
823
|
+
this.applyCollapsedState();
|
|
824
|
+
}
|
|
825
|
+
loadState() {
|
|
826
|
+
try {
|
|
827
|
+
const raw = this.doc.defaultView?.localStorage?.getItem(VirtualScrollDebugOverlay.storageKey);
|
|
828
|
+
if (raw) {
|
|
829
|
+
const parsed = JSON.parse(raw);
|
|
830
|
+
if (typeof parsed.left === 'number') {
|
|
831
|
+
this.state.left = parsed.left;
|
|
832
|
+
}
|
|
833
|
+
if (typeof parsed.top === 'number') {
|
|
834
|
+
this.state.top = parsed.top;
|
|
835
|
+
}
|
|
836
|
+
if (typeof parsed.collapsed === 'boolean') {
|
|
837
|
+
this.state.collapsed = parsed.collapsed;
|
|
838
|
+
}
|
|
839
|
+
if (typeof parsed.width === 'number') {
|
|
840
|
+
this.state.width = parsed.width;
|
|
841
|
+
}
|
|
842
|
+
if (typeof parsed.height === 'number') {
|
|
843
|
+
this.state.height = parsed.height;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
catch {
|
|
848
|
+
// ignore storage errors
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
persistState() {
|
|
852
|
+
try {
|
|
853
|
+
this.doc.defaultView?.localStorage?.setItem(VirtualScrollDebugOverlay.storageKey, JSON.stringify(this.state));
|
|
854
|
+
}
|
|
855
|
+
catch {
|
|
856
|
+
// ignore storage errors
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
appendLog(type, ...args) {
|
|
860
|
+
if (!this.logList) {
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
const item = this.doc.createElement('div');
|
|
864
|
+
item.style.display = 'flex';
|
|
865
|
+
item.style.gap = '6px';
|
|
866
|
+
item.style.alignItems = 'flex-start';
|
|
867
|
+
item.style.wordBreak = 'break-all';
|
|
868
|
+
item.style.color = type === 'warn' ? '#fbbf24' : '#9ca3af';
|
|
869
|
+
const time = this.doc.createElement('span');
|
|
870
|
+
time.textContent = new Date().toLocaleTimeString();
|
|
871
|
+
time.style.color = '#6b7280';
|
|
872
|
+
time.style.flexShrink = '0';
|
|
873
|
+
time.style.width = '72px';
|
|
874
|
+
const text = this.doc.createElement('span');
|
|
875
|
+
text.textContent = `[${type}] ${args.map(arg => this.formatValue(arg)).join(' ')}`;
|
|
876
|
+
item.appendChild(time);
|
|
877
|
+
item.appendChild(text);
|
|
878
|
+
this.logList.appendChild(item);
|
|
879
|
+
}
|
|
880
|
+
formatValue(value) {
|
|
881
|
+
if (typeof value === 'string') {
|
|
882
|
+
return value;
|
|
883
|
+
}
|
|
884
|
+
try {
|
|
885
|
+
return JSON.stringify(value);
|
|
886
|
+
}
|
|
887
|
+
catch (error) {
|
|
888
|
+
return String(value);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
setScrollTopValue(value) {
|
|
892
|
+
if (this.distanceInput) {
|
|
893
|
+
this.distanceInput.value = String(value ?? 0);
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
const SlateFragmentAttributeKey = 'data-slate-angular-fragment';
|
|
899
|
+
/**
|
|
900
|
+
* Get x-slate-fragment attribute from data-slate-angular-fragment
|
|
901
|
+
*/
|
|
902
|
+
const catchSlateFragment = /data-slate-angular-fragment="(.+?)"/m;
|
|
903
|
+
const getSlateFragmentAttribute = (htmlData) => {
|
|
904
|
+
const [, fragment] = htmlData.match(catchSlateFragment) || [];
|
|
905
|
+
return fragment;
|
|
906
|
+
};
|
|
907
|
+
/**
|
|
908
|
+
* Check if a DOM node is an element node.
|
|
909
|
+
*/
|
|
910
|
+
const isDOMText = (value) => {
|
|
911
|
+
return isDOMNode(value) && value.nodeType === 3;
|
|
912
|
+
};
|
|
913
|
+
/**
|
|
914
|
+
* Get a plaintext representation of the content of a node, accounting for block
|
|
915
|
+
* elements which get a newline appended.
|
|
916
|
+
*
|
|
917
|
+
* The domNode must be attached to the DOM.
|
|
918
|
+
*/
|
|
919
|
+
const getPlainText = (domNode) => {
|
|
920
|
+
let text = '';
|
|
921
|
+
if (isDOMText(domNode) && domNode.nodeValue) {
|
|
922
|
+
return domNode.nodeValue;
|
|
923
|
+
}
|
|
924
|
+
if (isDOMElement(domNode)) {
|
|
925
|
+
for (const childNode of Array.from(domNode.childNodes)) {
|
|
926
|
+
text += getPlainText(childNode);
|
|
927
|
+
}
|
|
928
|
+
const display = getComputedStyle(domNode).getPropertyValue('display');
|
|
929
|
+
if (display === 'block' || display === 'list' || domNode.tagName === 'BR') {
|
|
930
|
+
text += '\n';
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
return text;
|
|
934
|
+
};
|
|
935
|
+
/**
|
|
936
|
+
* Get the dom selection from Shadow Root if possible, otherwise from the document
|
|
937
|
+
*/
|
|
938
|
+
const getSelection = (root) => {
|
|
939
|
+
if (root.getSelection != null) {
|
|
940
|
+
return root.getSelection();
|
|
941
|
+
}
|
|
942
|
+
return document.getSelection();
|
|
943
|
+
};
|
|
944
|
+
const getContentHeight = (element) => {
|
|
945
|
+
if (!element)
|
|
946
|
+
return 0;
|
|
947
|
+
const style = window.getComputedStyle(element);
|
|
948
|
+
const boxSizing = style.boxSizing;
|
|
949
|
+
const height = parseFloat(style.height) || 0;
|
|
950
|
+
const paddingTop = parseFloat(style.paddingTop) || 0;
|
|
951
|
+
const paddingBottom = parseFloat(style.paddingBottom) || 0;
|
|
952
|
+
const totalPadding = paddingTop + paddingBottom;
|
|
953
|
+
const borderTop = parseFloat(style.borderTopWidth) || 0;
|
|
954
|
+
const borderBottom = parseFloat(style.borderBottomWidth) || 0;
|
|
955
|
+
const totalBorder = borderTop + borderBottom;
|
|
956
|
+
let contentHeight;
|
|
957
|
+
if (boxSizing === 'border-box') {
|
|
958
|
+
contentHeight = height - totalPadding - totalBorder;
|
|
959
|
+
}
|
|
960
|
+
else {
|
|
961
|
+
contentHeight = height;
|
|
962
|
+
}
|
|
963
|
+
return Math.max(contentHeight, 0);
|
|
964
|
+
};
|
|
965
|
+
const getZeroTextNode = () => {
|
|
966
|
+
return document.createTextNode('\uFEFF');
|
|
967
|
+
};
|
|
968
|
+
|
|
969
|
+
const SLATE_BLOCK_CARD_CLASS_NAME = 'slate-block-card';
|
|
970
|
+
class SlateBlockCard {
|
|
971
|
+
onInit() {
|
|
972
|
+
const nativeElement = document.createElement('div');
|
|
973
|
+
nativeElement.classList.add(SLATE_BLOCK_CARD_CLASS_NAME);
|
|
974
|
+
this.nativeElement = nativeElement;
|
|
975
|
+
this.createContent();
|
|
976
|
+
}
|
|
977
|
+
createContent() {
|
|
978
|
+
const leftCaret = document.createElement('span');
|
|
979
|
+
leftCaret.setAttribute(`card-target`, 'card-left');
|
|
980
|
+
leftCaret.classList.add('card-left');
|
|
981
|
+
leftCaret.appendChild(getZeroTextNode());
|
|
982
|
+
const rightCaret = document.createElement('span');
|
|
983
|
+
rightCaret.setAttribute(`card-target`, 'card-right');
|
|
984
|
+
rightCaret.classList.add('card-right');
|
|
985
|
+
rightCaret.appendChild(getZeroTextNode());
|
|
986
|
+
const center = document.createElement('div');
|
|
987
|
+
center.setAttribute(`card-target`, 'card-center');
|
|
988
|
+
this.nativeElement.appendChild(leftCaret);
|
|
989
|
+
this.nativeElement.appendChild(center);
|
|
990
|
+
this.nativeElement.appendChild(rightCaret);
|
|
991
|
+
this.centerContainer = center;
|
|
992
|
+
}
|
|
993
|
+
append() {
|
|
994
|
+
this.centerRootNodes.forEach(rootNode => !this.centerContainer.contains(rootNode) && this.centerContainer.appendChild(rootNode));
|
|
995
|
+
}
|
|
996
|
+
initializeCenter(rootNodes) {
|
|
997
|
+
this.centerRootNodes = rootNodes;
|
|
998
|
+
this.append();
|
|
999
|
+
}
|
|
1000
|
+
onDestroy() {
|
|
1001
|
+
this.nativeElement.remove();
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
const getBlockCardByNativeElement = (nativeElement) => {
|
|
1005
|
+
const blockCardElement = nativeElement?.parentElement?.parentElement;
|
|
1006
|
+
if (blockCardElement && blockCardElement.classList.contains(SLATE_BLOCK_CARD_CLASS_NAME)) {
|
|
1007
|
+
return blockCardElement;
|
|
1008
|
+
}
|
|
1009
|
+
return null;
|
|
1010
|
+
};
|
|
1011
|
+
|
|
1012
|
+
const roundTo = (value, precision = 2) => {
|
|
1013
|
+
const factor = 10 ** precision;
|
|
1014
|
+
const n = Math.round(value * factor) / factor;
|
|
1015
|
+
return Object.is(n, -0) ? 0 : n;
|
|
1016
|
+
};
|
|
1017
|
+
|
|
1018
|
+
const VIRTUAL_TOP_HEIGHT_CLASS_NAME = 'virtual-top-height';
|
|
1019
|
+
const VIRTUAL_BOTTOM_HEIGHT_CLASS_NAME = 'virtual-bottom-height';
|
|
1020
|
+
const VIRTUAL_CENTER_OUTLET_CLASS_NAME = 'virtual-center-outlet';
|
|
1021
|
+
const isDebug = localStorage.getItem(SLATE_DEBUG_KEY) === 'true';
|
|
1022
|
+
const isDebugScrollTop = localStorage.getItem(SLATE_DEBUG_KEY_SCROLL_TOP) === 'true';
|
|
1023
|
+
const ELEMENT_KEY_TO_HEIGHTS = new WeakMap();
|
|
1024
|
+
const EDITOR_TO_BUSINESS_TOP = new WeakMap();
|
|
1025
|
+
const EDITOR_TO_VIRTUAL_SCROLL_CONFIG = new WeakMap();
|
|
1026
|
+
const EDITOR_TO_VIEWPORT_HEIGHT = new WeakMap();
|
|
1027
|
+
const EDITOR_TO_ROOT_NODE_WIDTH = new WeakMap();
|
|
1028
|
+
const EDITOR_TO_IS_FROM_SCROLL_TO = new WeakMap();
|
|
1029
|
+
const isValidNumber = (value) => {
|
|
1030
|
+
return typeof value === 'number' && !Number.isNaN(value);
|
|
1031
|
+
};
|
|
1032
|
+
const debugLog = (type, ...args) => {
|
|
1033
|
+
const doc = document;
|
|
1034
|
+
VirtualScrollDebugOverlay.log(doc, type, ...args);
|
|
1035
|
+
};
|
|
1036
|
+
const cacheHeightByElement = (editor, element, height) => {
|
|
1037
|
+
if (!AngularEditor.isEnabledVirtualScroll(editor)) {
|
|
1038
|
+
return;
|
|
1039
|
+
}
|
|
1040
|
+
if (!isValidNumber(height)) {
|
|
1041
|
+
console.error('cacheHeightByElement: height must be number', height);
|
|
1042
|
+
return;
|
|
1043
|
+
}
|
|
1044
|
+
const key = AngularEditor.findKey(editor, element);
|
|
1045
|
+
const heights = ELEMENT_KEY_TO_HEIGHTS.get(editor);
|
|
1046
|
+
heights.set(key.id, height);
|
|
1047
|
+
};
|
|
1048
|
+
const setMinHeightByElement = (editor, element, rootElementMarginBottom) => {
|
|
1049
|
+
if (!AngularEditor.isEnabledVirtualScroll(editor)) {
|
|
1050
|
+
return;
|
|
1051
|
+
}
|
|
1052
|
+
const realHeight = getCachedHeightByElement(editor, element);
|
|
1053
|
+
if (realHeight) {
|
|
1054
|
+
const nativeElement = AngularEditor.toDOMNode(editor, element);
|
|
1055
|
+
const blockCard = getBlockCardByNativeElement(nativeElement);
|
|
1056
|
+
if (blockCard) {
|
|
1057
|
+
const minHeight = realHeight - rootElementMarginBottom;
|
|
1058
|
+
blockCard.style.minHeight = minHeight + 'px';
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
};
|
|
1062
|
+
const clearMinHeightByElement = (editor, element) => {
|
|
1063
|
+
if (!AngularEditor.isEnabledVirtualScroll(editor)) {
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
1066
|
+
const nativeElement = AngularEditor.toDOMNode(editor, element);
|
|
1067
|
+
const blockCard = getBlockCardByNativeElement(nativeElement);
|
|
1068
|
+
if (blockCard && blockCard.style.minHeight) {
|
|
1069
|
+
blockCard.style.minHeight = '';
|
|
1070
|
+
return true;
|
|
1071
|
+
}
|
|
1072
|
+
else {
|
|
1073
|
+
return false;
|
|
1374
1074
|
}
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1075
|
+
};
|
|
1076
|
+
const calcHeightByElement = (editor, element) => {
|
|
1077
|
+
const view = ELEMENT_TO_COMPONENT.get(element);
|
|
1078
|
+
if (!view) {
|
|
1079
|
+
return;
|
|
1378
1080
|
}
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
if (typeof parsed.width === 'number') {
|
|
1394
|
-
this.state.width = parsed.width;
|
|
1395
|
-
}
|
|
1396
|
-
if (typeof parsed.height === 'number') {
|
|
1397
|
-
this.state.height = parsed.height;
|
|
1081
|
+
const height = view.calcHeight();
|
|
1082
|
+
cacheHeightByElement(editor, element, height);
|
|
1083
|
+
return height;
|
|
1084
|
+
};
|
|
1085
|
+
const measureHeightByIndics = (editor, indics, force = false) => {
|
|
1086
|
+
let hasChanged = false;
|
|
1087
|
+
indics.forEach((index, i) => {
|
|
1088
|
+
const element = editor.children[index];
|
|
1089
|
+
const preHeight = getCachedHeightByElement(editor, element);
|
|
1090
|
+
if (preHeight && !force) {
|
|
1091
|
+
if (isDebug) {
|
|
1092
|
+
const height = calcHeightByElement(editor, element);
|
|
1093
|
+
if (height !== preHeight) {
|
|
1094
|
+
debugLog('warn', 'calcHeightByElement: height not equal, index: ', index, 'preHeight: ', preHeight, 'height: ', height);
|
|
1398
1095
|
}
|
|
1399
1096
|
}
|
|
1097
|
+
return;
|
|
1400
1098
|
}
|
|
1401
|
-
|
|
1402
|
-
|
|
1099
|
+
const currentHeight = calcHeightByElement(editor, element);
|
|
1100
|
+
if (isValidNumber(currentHeight) && currentHeight !== preHeight) {
|
|
1101
|
+
hasChanged = true;
|
|
1403
1102
|
}
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
try {
|
|
1407
|
-
this.doc.defaultView?.localStorage?.setItem(VirtualScrollDebugOverlay.storageKey, JSON.stringify(this.state));
|
|
1103
|
+
if (isDebug && isValidNumber(currentHeight)) {
|
|
1104
|
+
debugLog('log', 'measureHeightByIndics: index: ', index, 'preHeight: ', preHeight, 'height: ', currentHeight);
|
|
1408
1105
|
}
|
|
1409
|
-
|
|
1410
|
-
|
|
1106
|
+
});
|
|
1107
|
+
return hasChanged;
|
|
1108
|
+
};
|
|
1109
|
+
const getBusinessTop = (editor) => {
|
|
1110
|
+
return EDITOR_TO_BUSINESS_TOP.get(editor) ?? 0;
|
|
1111
|
+
};
|
|
1112
|
+
const getViewportHeight = (editor) => {
|
|
1113
|
+
return EDITOR_TO_VIEWPORT_HEIGHT.get(editor) ?? window.innerHeight;
|
|
1114
|
+
};
|
|
1115
|
+
const getScrollContainer = (editor) => {
|
|
1116
|
+
const config = EDITOR_TO_VIRTUAL_SCROLL_CONFIG.get(editor);
|
|
1117
|
+
return config?.scrollContainer || document.body;
|
|
1118
|
+
};
|
|
1119
|
+
const getCachedHeightByElement = (editor, element) => {
|
|
1120
|
+
const heights = ELEMENT_KEY_TO_HEIGHTS.get(editor);
|
|
1121
|
+
const key = AngularEditor.findKey(editor, element);
|
|
1122
|
+
const height = heights?.get(key.id);
|
|
1123
|
+
if (typeof height === 'number') {
|
|
1124
|
+
return height;
|
|
1125
|
+
}
|
|
1126
|
+
if (heights?.has(key.id)) {
|
|
1127
|
+
console.error('getBlockHeight: invalid height value', key.id, height);
|
|
1128
|
+
}
|
|
1129
|
+
return null;
|
|
1130
|
+
};
|
|
1131
|
+
const buildHeightsAndAccumulatedHeights = (editor, visibleStates) => {
|
|
1132
|
+
const children = (editor.children || []);
|
|
1133
|
+
const heights = new Array(children.length);
|
|
1134
|
+
const accumulatedHeights = new Array(children.length + 1);
|
|
1135
|
+
accumulatedHeights[0] = 0;
|
|
1136
|
+
for (let i = 0; i < children.length; i++) {
|
|
1137
|
+
const isVisible = visibleStates[i];
|
|
1138
|
+
let height = isVisible ? getCachedHeightByElement(editor, children[i]) : 0;
|
|
1139
|
+
if (height === null) {
|
|
1140
|
+
try {
|
|
1141
|
+
height = editor.getRoughHeight(children[i]);
|
|
1142
|
+
}
|
|
1143
|
+
catch (error) {
|
|
1144
|
+
console.error('buildHeightsAndAccumulatedHeights: getRoughHeight error', error);
|
|
1145
|
+
}
|
|
1411
1146
|
}
|
|
1147
|
+
heights[i] = height;
|
|
1148
|
+
accumulatedHeights[i + 1] = accumulatedHeights[i] + height;
|
|
1412
1149
|
}
|
|
1413
|
-
|
|
1414
|
-
|
|
1150
|
+
return { heights, accumulatedHeights };
|
|
1151
|
+
};
|
|
1152
|
+
const calculateVirtualTopHeight = (editor, startIndex, visibleStates) => {
|
|
1153
|
+
const { accumulatedHeights } = buildHeightsAndAccumulatedHeights(editor, visibleStates);
|
|
1154
|
+
const virtualTopHeight = roundTo(accumulatedHeights[startIndex] ?? 0, 1);
|
|
1155
|
+
return virtualTopHeight;
|
|
1156
|
+
};
|
|
1157
|
+
const calcBusinessTop = (editor) => {
|
|
1158
|
+
const editable = AngularEditor.toDOMNode(editor, editor);
|
|
1159
|
+
const virtualTopElement = editable.querySelector(`.${VIRTUAL_TOP_HEIGHT_CLASS_NAME}`);
|
|
1160
|
+
const virtualTopBoundingTop = virtualTopElement?.getBoundingClientRect()?.top ?? 0;
|
|
1161
|
+
const virtualScrollConfig = EDITOR_TO_VIRTUAL_SCROLL_CONFIG.get(editor);
|
|
1162
|
+
const scrollContainer = virtualScrollConfig?.scrollContainer;
|
|
1163
|
+
const viewportBoundingTop = scrollContainer?.getBoundingClientRect()?.top ?? 0;
|
|
1164
|
+
const businessTop = Math.ceil(virtualTopBoundingTop) + Math.ceil(virtualScrollConfig.scrollTop) - Math.floor(viewportBoundingTop);
|
|
1165
|
+
EDITOR_TO_BUSINESS_TOP.set(editor, businessTop);
|
|
1166
|
+
if (isDebug) {
|
|
1167
|
+
debugLog('log', 'calcBusinessTop: ', businessTop);
|
|
1168
|
+
virtualTopElement.setAttribute('data-business-top', businessTop.toString());
|
|
1169
|
+
}
|
|
1170
|
+
console.log('virtualTopBoundingTop: ', virtualTopBoundingTop, 'virtualScrollConfig.scrollTop: ', virtualScrollConfig.scrollTop, 'viewportBoundingTop: ', viewportBoundingTop);
|
|
1171
|
+
return businessTop;
|
|
1172
|
+
};
|
|
1173
|
+
const scrollToElement = (editor, element, scrollTo) => {
|
|
1174
|
+
const children = editor.children;
|
|
1175
|
+
if (!children.length) {
|
|
1176
|
+
return;
|
|
1177
|
+
}
|
|
1178
|
+
const anchorIndex = children.findIndex(item => item === element);
|
|
1179
|
+
if (anchorIndex < 0) {
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1182
|
+
const visibleStates = editor.getAllVisibleStates();
|
|
1183
|
+
const { accumulatedHeights } = buildHeightsAndAccumulatedHeights(editor, visibleStates);
|
|
1184
|
+
let businessTop = getBusinessTop(editor);
|
|
1185
|
+
if (businessTop === 0) {
|
|
1186
|
+
businessTop = calcBusinessTop(editor);
|
|
1187
|
+
}
|
|
1188
|
+
scrollTo((accumulatedHeights[anchorIndex] ?? 0) + businessTop);
|
|
1189
|
+
EDITOR_TO_IS_FROM_SCROLL_TO.set(editor, true);
|
|
1190
|
+
setTimeout(() => {
|
|
1191
|
+
console.log('scrollToElement: end scroll');
|
|
1192
|
+
EDITOR_TO_IS_FROM_SCROLL_TO.set(editor, false);
|
|
1193
|
+
}, 0);
|
|
1194
|
+
};
|
|
1195
|
+
|
|
1196
|
+
const AngularEditor = {
|
|
1197
|
+
...CustomDOMEditor,
|
|
1198
|
+
/**
|
|
1199
|
+
* handle editor error.
|
|
1200
|
+
*/
|
|
1201
|
+
onError(errorData) {
|
|
1202
|
+
if (errorData.nativeError) {
|
|
1203
|
+
throw errorData.nativeError;
|
|
1204
|
+
}
|
|
1205
|
+
},
|
|
1206
|
+
/**
|
|
1207
|
+
* onKeydown hook.
|
|
1208
|
+
*/
|
|
1209
|
+
onKeydown(editor, data) {
|
|
1210
|
+
editor.onKeydown(data);
|
|
1211
|
+
},
|
|
1212
|
+
/**
|
|
1213
|
+
* onClick hook.
|
|
1214
|
+
*/
|
|
1215
|
+
onClick(editor, data) {
|
|
1216
|
+
editor.onClick(data);
|
|
1217
|
+
},
|
|
1218
|
+
deleteCutData(editor) {
|
|
1219
|
+
editor.deleteCutData();
|
|
1220
|
+
},
|
|
1221
|
+
isLeafBlock(editor, node) {
|
|
1222
|
+
return Element.isElement(node) && !editor.isInline(node) && Editor.hasInlines(editor, node);
|
|
1223
|
+
},
|
|
1224
|
+
/**
|
|
1225
|
+
* move native selection to card-left or card-right
|
|
1226
|
+
* @param editor
|
|
1227
|
+
* @param blockCardNode
|
|
1228
|
+
* @param options
|
|
1229
|
+
*/
|
|
1230
|
+
moveBlockCard(editor, blockCardNode, options) {
|
|
1231
|
+
const cursorNode = AngularEditor.getCardCursorNode(editor, blockCardNode, options);
|
|
1232
|
+
const window = AngularEditor.getWindow(editor);
|
|
1233
|
+
const domSelection = window.getSelection();
|
|
1234
|
+
domSelection.setBaseAndExtent(cursorNode, 1, cursorNode, 1);
|
|
1235
|
+
},
|
|
1236
|
+
/**
|
|
1237
|
+
* move slate selection to card-left or card-right
|
|
1238
|
+
* @param editor
|
|
1239
|
+
* @param path
|
|
1240
|
+
* @param options
|
|
1241
|
+
*/
|
|
1242
|
+
moveBlockCardCursor(editor, path, options) {
|
|
1243
|
+
const cursor = {
|
|
1244
|
+
path,
|
|
1245
|
+
offset: options.direction === 'left' ? FAKE_LEFT_BLOCK_CARD_OFFSET : FAKE_RIGHT_BLOCK_CARD_OFFSET
|
|
1246
|
+
};
|
|
1247
|
+
Transforms.select(editor, { anchor: cursor, focus: cursor });
|
|
1248
|
+
},
|
|
1249
|
+
focus: (editor, options = { retries: 5 }) => {
|
|
1250
|
+
// Return if already focused
|
|
1251
|
+
if (IS_FOCUSED.get(editor)) {
|
|
1415
1252
|
return;
|
|
1416
1253
|
}
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
item.style.wordBreak = 'break-all';
|
|
1422
|
-
item.style.color = type === 'warn' ? '#fbbf24' : '#9ca3af';
|
|
1423
|
-
const time = this.doc.createElement('span');
|
|
1424
|
-
time.textContent = new Date().toLocaleTimeString();
|
|
1425
|
-
time.style.color = '#6b7280';
|
|
1426
|
-
time.style.flexShrink = '0';
|
|
1427
|
-
time.style.width = '72px';
|
|
1428
|
-
const text = this.doc.createElement('span');
|
|
1429
|
-
text.textContent = `[${type}] ${args.map(arg => this.formatValue(arg)).join(' ')}`;
|
|
1430
|
-
item.appendChild(time);
|
|
1431
|
-
item.appendChild(text);
|
|
1432
|
-
this.logList.appendChild(item);
|
|
1433
|
-
}
|
|
1434
|
-
formatValue(value) {
|
|
1435
|
-
if (typeof value === 'string') {
|
|
1436
|
-
return value;
|
|
1254
|
+
// Return if no dom node is associated with the editor, which means the editor is not yet mounted
|
|
1255
|
+
// or has been unmounted. This can happen especially, while retrying to focus the editor.
|
|
1256
|
+
if (!EDITOR_TO_ELEMENT.get(editor)) {
|
|
1257
|
+
return;
|
|
1437
1258
|
}
|
|
1438
|
-
|
|
1439
|
-
|
|
1259
|
+
IS_FOCUSED.set(editor, true);
|
|
1260
|
+
const el = DOMEditor.toDOMNode(editor, editor);
|
|
1261
|
+
const root = DOMEditor.findDocumentOrShadowRoot(editor);
|
|
1262
|
+
if (root.activeElement !== el) {
|
|
1263
|
+
// IS_FOCUSED should be set before calling el.focus() to ensure that
|
|
1264
|
+
// FocusedContext is updated to the correct value
|
|
1265
|
+
el.focus({ preventScroll: true });
|
|
1266
|
+
}
|
|
1267
|
+
},
|
|
1268
|
+
isEnabledVirtualScroll(editor) {
|
|
1269
|
+
const config = EDITOR_TO_VIRTUAL_SCROLL_CONFIG.get(editor);
|
|
1270
|
+
return config?.enabled || false;
|
|
1271
|
+
}
|
|
1272
|
+
};
|
|
1273
|
+
|
|
1274
|
+
/**
|
|
1275
|
+
* Hotkey mappings for each platform.
|
|
1276
|
+
*/
|
|
1277
|
+
const HOTKEYS = {
|
|
1278
|
+
bold: 'mod+b',
|
|
1279
|
+
compose: ['down', 'left', 'right', 'up', 'backspace', 'enter'],
|
|
1280
|
+
moveBackward: 'left',
|
|
1281
|
+
moveForward: 'right',
|
|
1282
|
+
moveUp: 'up',
|
|
1283
|
+
moveDown: 'down',
|
|
1284
|
+
moveWordBackward: 'ctrl+left',
|
|
1285
|
+
moveWordForward: 'ctrl+right',
|
|
1286
|
+
deleteBackward: 'shift?+backspace',
|
|
1287
|
+
deleteForward: 'shift?+delete',
|
|
1288
|
+
extendBackward: 'shift+left',
|
|
1289
|
+
extendForward: 'shift+right',
|
|
1290
|
+
italic: 'mod+i',
|
|
1291
|
+
splitBlock: 'shift?+enter',
|
|
1292
|
+
undo: 'mod+z'
|
|
1293
|
+
};
|
|
1294
|
+
const APPLE_HOTKEYS = {
|
|
1295
|
+
moveLineBackward: 'opt+up',
|
|
1296
|
+
moveLineForward: 'opt+down',
|
|
1297
|
+
moveWordBackward: 'opt+left',
|
|
1298
|
+
moveWordForward: 'opt+right',
|
|
1299
|
+
deleteBackward: ['ctrl+backspace', 'ctrl+h'],
|
|
1300
|
+
deleteForward: ['ctrl+delete', 'ctrl+d'],
|
|
1301
|
+
deleteLineBackward: 'cmd+shift?+backspace',
|
|
1302
|
+
deleteLineForward: ['cmd+shift?+delete', 'ctrl+k'],
|
|
1303
|
+
deleteWordBackward: 'opt+shift?+backspace',
|
|
1304
|
+
deleteWordForward: 'opt+shift?+delete',
|
|
1305
|
+
extendLineBackward: 'opt+shift+up',
|
|
1306
|
+
extendLineForward: 'opt+shift+down',
|
|
1307
|
+
redo: 'cmd+shift+z',
|
|
1308
|
+
transposeCharacter: 'ctrl+t'
|
|
1309
|
+
};
|
|
1310
|
+
const WINDOWS_HOTKEYS = {
|
|
1311
|
+
deleteWordBackward: 'ctrl+shift?+backspace',
|
|
1312
|
+
deleteWordForward: 'ctrl+shift?+delete',
|
|
1313
|
+
redo: ['ctrl+y', 'ctrl+shift+z']
|
|
1314
|
+
};
|
|
1315
|
+
/**
|
|
1316
|
+
* Create a platform-aware hotkey checker.
|
|
1317
|
+
*/
|
|
1318
|
+
const create = (key) => {
|
|
1319
|
+
const generic = HOTKEYS[key];
|
|
1320
|
+
const apple = APPLE_HOTKEYS[key];
|
|
1321
|
+
const windows = WINDOWS_HOTKEYS[key];
|
|
1322
|
+
const isGeneric = generic && isKeyHotkey(generic);
|
|
1323
|
+
const isApple = apple && isKeyHotkey(apple);
|
|
1324
|
+
const isWindows = windows && isKeyHotkey(windows);
|
|
1325
|
+
return (event) => {
|
|
1326
|
+
if (isGeneric && isGeneric(event)) {
|
|
1327
|
+
return true;
|
|
1440
1328
|
}
|
|
1441
|
-
|
|
1442
|
-
return
|
|
1329
|
+
if (IS_APPLE && isApple && isApple(event)) {
|
|
1330
|
+
return true;
|
|
1443
1331
|
}
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
if (this.distanceInput) {
|
|
1447
|
-
this.distanceInput.value = String(value ?? 0);
|
|
1332
|
+
if (!IS_APPLE && isWindows && isWindows(event)) {
|
|
1333
|
+
return true;
|
|
1448
1334
|
}
|
|
1449
|
-
|
|
1335
|
+
return false;
|
|
1336
|
+
};
|
|
1337
|
+
};
|
|
1338
|
+
/**
|
|
1339
|
+
* Hotkeys.
|
|
1340
|
+
*/
|
|
1341
|
+
const hotkeys = {
|
|
1342
|
+
isBold: create('bold'),
|
|
1343
|
+
isCompose: create('compose'),
|
|
1344
|
+
isMoveBackward: create('moveBackward'),
|
|
1345
|
+
isMoveForward: create('moveForward'),
|
|
1346
|
+
isMoveUp: create('moveUp'),
|
|
1347
|
+
isMoveDown: create('moveDown'),
|
|
1348
|
+
isDeleteBackward: create('deleteBackward'),
|
|
1349
|
+
isDeleteForward: create('deleteForward'),
|
|
1350
|
+
isDeleteLineBackward: create('deleteLineBackward'),
|
|
1351
|
+
isDeleteLineForward: create('deleteLineForward'),
|
|
1352
|
+
isDeleteWordBackward: create('deleteWordBackward'),
|
|
1353
|
+
isDeleteWordForward: create('deleteWordForward'),
|
|
1354
|
+
isExtendBackward: create('extendBackward'),
|
|
1355
|
+
isExtendForward: create('extendForward'),
|
|
1356
|
+
isExtendLineBackward: create('extendLineBackward'),
|
|
1357
|
+
isExtendLineForward: create('extendLineForward'),
|
|
1358
|
+
isItalic: create('italic'),
|
|
1359
|
+
isMoveLineBackward: create('moveLineBackward'),
|
|
1360
|
+
isMoveLineForward: create('moveLineForward'),
|
|
1361
|
+
isMoveWordBackward: create('moveWordBackward'),
|
|
1362
|
+
isMoveWordForward: create('moveWordForward'),
|
|
1363
|
+
isRedo: create('redo'),
|
|
1364
|
+
isSplitBlock: create('splitBlock'),
|
|
1365
|
+
isTransposeCharacter: create('transposeCharacter'),
|
|
1366
|
+
isUndo: create('undo')
|
|
1367
|
+
};
|
|
1368
|
+
|
|
1369
|
+
function isTemplateRef(value) {
|
|
1370
|
+
return value && value instanceof TemplateRef;
|
|
1371
|
+
}
|
|
1372
|
+
function isComponentType(value) {
|
|
1373
|
+
return !isTemplateRef(value);
|
|
1374
|
+
}
|
|
1375
|
+
function isFlavourType(value) {
|
|
1376
|
+
return value && value.isFlavour === true;
|
|
1450
1377
|
}
|
|
1451
1378
|
|
|
1452
|
-
const
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
leftCaret.appendChild(getZeroTextNode());
|
|
1465
|
-
const rightCaret = document.createElement('span');
|
|
1466
|
-
rightCaret.setAttribute(`card-target`, 'card-right');
|
|
1467
|
-
rightCaret.classList.add('card-right');
|
|
1468
|
-
rightCaret.appendChild(getZeroTextNode());
|
|
1469
|
-
const center = document.createElement('div');
|
|
1470
|
-
center.setAttribute(`card-target`, 'card-center');
|
|
1471
|
-
this.nativeElement.appendChild(leftCaret);
|
|
1472
|
-
this.nativeElement.appendChild(center);
|
|
1473
|
-
this.nativeElement.appendChild(rightCaret);
|
|
1474
|
-
this.centerContainer = center;
|
|
1475
|
-
}
|
|
1476
|
-
append() {
|
|
1477
|
-
this.centerRootNodes.forEach(rootNode => !this.centerContainer.contains(rootNode) && this.centerContainer.appendChild(rootNode));
|
|
1478
|
-
}
|
|
1479
|
-
initializeCenter(rootNodes) {
|
|
1480
|
-
this.centerRootNodes = rootNodes;
|
|
1481
|
-
this.append();
|
|
1482
|
-
}
|
|
1483
|
-
onDestroy() {
|
|
1484
|
-
this.nativeElement.remove();
|
|
1379
|
+
const shallowCompare = (obj1, obj2) => Object.keys(obj1).length === Object.keys(obj2).length &&
|
|
1380
|
+
Object.keys(obj1).every(key => obj2.hasOwnProperty(key) && obj1[key] === obj2[key]);
|
|
1381
|
+
/**
|
|
1382
|
+
* Check if a list of decorator ranges are equal to another.
|
|
1383
|
+
*
|
|
1384
|
+
* PERF: this requires the two lists to also have the ranges inside them in the
|
|
1385
|
+
* same order, but this is an okay constraint for us since decorations are
|
|
1386
|
+
* kept in order, and the odd case where they aren't is okay to re-render for.
|
|
1387
|
+
*/
|
|
1388
|
+
const isDecoratorRangeListEqual = (list, another) => {
|
|
1389
|
+
if (list.length !== another.length) {
|
|
1390
|
+
return false;
|
|
1485
1391
|
}
|
|
1486
|
-
|
|
1487
|
-
const
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1392
|
+
for (let i = 0; i < list.length; i++) {
|
|
1393
|
+
const range = list[i];
|
|
1394
|
+
const other = another[i];
|
|
1395
|
+
const { anchor: rangeAnchor, focus: rangeFocus, ...rangeOwnProps } = range;
|
|
1396
|
+
const { anchor: otherAnchor, focus: otherFocus, ...otherOwnProps } = other;
|
|
1397
|
+
if (!Range.equals(range, other) ||
|
|
1398
|
+
range[PLACEHOLDER_SYMBOL] !== other[PLACEHOLDER_SYMBOL] ||
|
|
1399
|
+
!shallowCompare(rangeOwnProps, otherOwnProps)) {
|
|
1400
|
+
return false;
|
|
1401
|
+
}
|
|
1491
1402
|
}
|
|
1492
|
-
return
|
|
1403
|
+
return true;
|
|
1493
1404
|
};
|
|
1494
1405
|
|
|
1495
|
-
const
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
return
|
|
1406
|
+
const isValid = (value) => (Element.isElement(value) && value.children.length > 0 && value.children.every(child => isValid(child))) ||
|
|
1407
|
+
Text$1.isText(value);
|
|
1408
|
+
const check = (document) => {
|
|
1409
|
+
return document.every(value => Element.isElement(value) && isValid(value));
|
|
1499
1410
|
};
|
|
1411
|
+
function normalize(document) {
|
|
1412
|
+
return document.filter(value => Element.isElement(value) && isValid(value));
|
|
1413
|
+
}
|
|
1500
1414
|
|
|
1501
|
-
const
|
|
1502
|
-
|
|
1503
|
-
const
|
|
1504
|
-
const
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1415
|
+
const createThrottleRAF = () => {
|
|
1416
|
+
let timerId = null;
|
|
1417
|
+
const throttleRAF = (fn) => {
|
|
1418
|
+
const scheduleFunc = () => {
|
|
1419
|
+
timerId = requestAnimationFrame(() => {
|
|
1420
|
+
timerId = null;
|
|
1421
|
+
fn();
|
|
1422
|
+
});
|
|
1423
|
+
};
|
|
1424
|
+
if (timerId !== null) {
|
|
1425
|
+
cancelAnimationFrame(timerId);
|
|
1426
|
+
timerId = null;
|
|
1427
|
+
}
|
|
1428
|
+
scheduleFunc();
|
|
1429
|
+
};
|
|
1430
|
+
return throttleRAF;
|
|
1510
1431
|
};
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1432
|
+
|
|
1433
|
+
const isClipboardReadSupported = () => {
|
|
1434
|
+
return 'clipboard' in navigator && 'read' in navigator.clipboard;
|
|
1514
1435
|
};
|
|
1515
|
-
const
|
|
1516
|
-
|
|
1517
|
-
return;
|
|
1518
|
-
}
|
|
1519
|
-
if (!isValidNumber(height)) {
|
|
1520
|
-
console.error('cacheHeightByElement: height must be number', height);
|
|
1521
|
-
return;
|
|
1522
|
-
}
|
|
1523
|
-
const key = AngularEditor.findKey(editor, element);
|
|
1524
|
-
const heights = ELEMENT_KEY_TO_HEIGHTS.get(editor);
|
|
1525
|
-
heights.set(key.id, height);
|
|
1436
|
+
const isClipboardWriteSupported = () => {
|
|
1437
|
+
return 'clipboard' in navigator && 'write' in navigator.clipboard;
|
|
1526
1438
|
};
|
|
1527
|
-
const
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1439
|
+
const isClipboardWriteTextSupported = () => {
|
|
1440
|
+
return 'clipboard' in navigator && 'writeText' in navigator.clipboard;
|
|
1441
|
+
};
|
|
1442
|
+
const isClipboardFile = (item) => {
|
|
1443
|
+
return item.types.find(i => i.match(/^image\//));
|
|
1444
|
+
};
|
|
1445
|
+
const isInvalidTable = (nodes = []) => {
|
|
1446
|
+
return nodes.some(node => node.tagName.toLowerCase() === 'tr');
|
|
1447
|
+
};
|
|
1448
|
+
const stripHtml = (html) => {
|
|
1449
|
+
// See <https://github.com/developit/preact-markup/blob/4788b8d61b4e24f83688710746ee36e7464f7bbc/src/parse-markup.js#L60-L69>
|
|
1450
|
+
const doc = document.implementation.createHTMLDocument('');
|
|
1451
|
+
doc.documentElement.innerHTML = html.trim();
|
|
1452
|
+
return doc.body.textContent || doc.body.innerText || '';
|
|
1453
|
+
};
|
|
1454
|
+
const blobAsString = (blob) => {
|
|
1455
|
+
return new Promise((resolve, reject) => {
|
|
1456
|
+
const reader = new FileReader();
|
|
1457
|
+
reader.addEventListener('loadend', () => {
|
|
1458
|
+
const text = reader.result;
|
|
1459
|
+
resolve(text);
|
|
1460
|
+
});
|
|
1461
|
+
reader.addEventListener('error', () => {
|
|
1462
|
+
reject(reader.error);
|
|
1463
|
+
});
|
|
1464
|
+
reader.readAsText(blob);
|
|
1465
|
+
});
|
|
1466
|
+
};
|
|
1467
|
+
const completeTable = (fragment) => {
|
|
1468
|
+
const result = document.createDocumentFragment();
|
|
1469
|
+
const table = document.createElement('table');
|
|
1470
|
+
result.appendChild(table);
|
|
1471
|
+
table.appendChild(fragment);
|
|
1472
|
+
return result;
|
|
1473
|
+
};
|
|
1474
|
+
|
|
1475
|
+
const setDataTransferClipboard = (dataTransfer, htmlText) => {
|
|
1476
|
+
dataTransfer?.setData(`text/html`, htmlText);
|
|
1477
|
+
};
|
|
1478
|
+
const setDataTransferClipboardText = (data, text) => {
|
|
1479
|
+
data?.setData(`text/plain`, text);
|
|
1480
|
+
};
|
|
1481
|
+
const getDataTransferClipboard = (data) => {
|
|
1482
|
+
const html = data?.getData(`text/html`);
|
|
1483
|
+
if (html) {
|
|
1484
|
+
const htmlClipboardData = getClipboardFromHTMLText(html);
|
|
1485
|
+
if (htmlClipboardData) {
|
|
1486
|
+
return htmlClipboardData;
|
|
1487
|
+
}
|
|
1488
|
+
const textData = getDataTransferClipboardText(data);
|
|
1489
|
+
if (textData) {
|
|
1490
|
+
return {
|
|
1491
|
+
html,
|
|
1492
|
+
...textData
|
|
1493
|
+
};
|
|
1494
|
+
}
|
|
1495
|
+
else {
|
|
1496
|
+
return { html };
|
|
1538
1497
|
}
|
|
1539
1498
|
}
|
|
1540
|
-
|
|
1541
|
-
const
|
|
1542
|
-
|
|
1543
|
-
return;
|
|
1499
|
+
else {
|
|
1500
|
+
const textData = getDataTransferClipboardText(data);
|
|
1501
|
+
return textData;
|
|
1544
1502
|
}
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
if (
|
|
1548
|
-
|
|
1549
|
-
return true;
|
|
1503
|
+
};
|
|
1504
|
+
const getDataTransferClipboardText = (data) => {
|
|
1505
|
+
if (!data) {
|
|
1506
|
+
return null;
|
|
1550
1507
|
}
|
|
1551
|
-
|
|
1552
|
-
|
|
1508
|
+
const text = data?.getData(`text/plain`);
|
|
1509
|
+
if (text) {
|
|
1510
|
+
const htmlClipboardData = getClipboardFromHTMLText(text);
|
|
1511
|
+
if (htmlClipboardData) {
|
|
1512
|
+
return htmlClipboardData;
|
|
1513
|
+
}
|
|
1553
1514
|
}
|
|
1515
|
+
return { text };
|
|
1554
1516
|
};
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1517
|
+
|
|
1518
|
+
const setNavigatorClipboard = async (htmlText, data, text = '') => {
|
|
1519
|
+
let textClipboard = text;
|
|
1520
|
+
if (isClipboardWriteSupported()) {
|
|
1521
|
+
await navigator.clipboard.write([
|
|
1522
|
+
new ClipboardItem({
|
|
1523
|
+
'text/html': new Blob([htmlText], {
|
|
1524
|
+
type: 'text/html'
|
|
1525
|
+
}),
|
|
1526
|
+
'text/plain': new Blob([textClipboard ?? JSON.stringify(data)], { type: 'text/plain' })
|
|
1527
|
+
})
|
|
1528
|
+
]);
|
|
1559
1529
|
}
|
|
1560
|
-
const height = view.calcHeight();
|
|
1561
|
-
cacheHeightByElement(editor, element, height);
|
|
1562
|
-
return height;
|
|
1563
1530
|
};
|
|
1564
|
-
const
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1531
|
+
const getNavigatorClipboard = async () => {
|
|
1532
|
+
if (!isClipboardReadSupported()) {
|
|
1533
|
+
return null;
|
|
1534
|
+
}
|
|
1535
|
+
const clipboardItems = await navigator.clipboard.read();
|
|
1536
|
+
let clipboardData = {};
|
|
1537
|
+
if (Array.isArray(clipboardItems) && clipboardItems[0] instanceof ClipboardItem) {
|
|
1538
|
+
for (const item of clipboardItems) {
|
|
1539
|
+
if (isClipboardFile(item)) {
|
|
1540
|
+
const clipboardFiles = item.types.filter(type => type.match(/^image\//));
|
|
1541
|
+
const fileBlobs = await Promise.all(clipboardFiles.map(type => item.getType(type)));
|
|
1542
|
+
const urls = fileBlobs.filter(Boolean).map(blob => URL.createObjectURL(blob));
|
|
1543
|
+
const files = await Promise.all(urls.map(async (url) => {
|
|
1544
|
+
const blob = await (await fetch(url)).blob();
|
|
1545
|
+
return new File([blob], 'file', { type: blob.type });
|
|
1546
|
+
}));
|
|
1547
|
+
clipboardData = {
|
|
1548
|
+
...clipboardData,
|
|
1549
|
+
files
|
|
1550
|
+
};
|
|
1551
|
+
}
|
|
1552
|
+
if (item.types.includes('text/html')) {
|
|
1553
|
+
const htmlContent = await blobAsString(await item.getType('text/html'));
|
|
1554
|
+
const htmlClipboardData = getClipboardFromHTMLText(htmlContent);
|
|
1555
|
+
if (htmlClipboardData) {
|
|
1556
|
+
clipboardData = { ...clipboardData, ...htmlClipboardData };
|
|
1557
|
+
return clipboardData;
|
|
1558
|
+
}
|
|
1559
|
+
if (htmlContent && htmlContent.trim()) {
|
|
1560
|
+
clipboardData = { ...clipboardData, html: htmlContent };
|
|
1574
1561
|
}
|
|
1575
1562
|
}
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
debugLog('log', 'measureHeightByIndics: index: ', index, 'preHeight: ', preHeight, 'height: ', currentHeight);
|
|
1563
|
+
if (item.types.includes('text/plain')) {
|
|
1564
|
+
const textContent = await blobAsString(await item.getType('text/plain'));
|
|
1565
|
+
clipboardData = {
|
|
1566
|
+
...clipboardData,
|
|
1567
|
+
text: stripHtml(textContent)
|
|
1568
|
+
};
|
|
1569
|
+
}
|
|
1584
1570
|
}
|
|
1585
|
-
}
|
|
1586
|
-
return
|
|
1587
|
-
};
|
|
1588
|
-
const getBusinessTop = (editor) => {
|
|
1589
|
-
return EDITOR_TO_BUSINESS_TOP.get(editor) ?? 0;
|
|
1571
|
+
}
|
|
1572
|
+
return clipboardData;
|
|
1590
1573
|
};
|
|
1591
|
-
|
|
1592
|
-
|
|
1574
|
+
|
|
1575
|
+
const buildHTMLText = (wrapper, attach, data) => {
|
|
1576
|
+
const stringObj = JSON.stringify(data);
|
|
1577
|
+
const encoded = window.btoa(encodeURIComponent(stringObj));
|
|
1578
|
+
attach.setAttribute(SlateFragmentAttributeKey, encoded);
|
|
1579
|
+
return wrapper.innerHTML;
|
|
1593
1580
|
};
|
|
1594
|
-
const
|
|
1595
|
-
const
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1581
|
+
const getClipboardFromHTMLText = (html) => {
|
|
1582
|
+
const fragmentAttribute = getSlateFragmentAttribute(html);
|
|
1583
|
+
if (fragmentAttribute) {
|
|
1584
|
+
try {
|
|
1585
|
+
const decoded = decodeURIComponent(window.atob(fragmentAttribute));
|
|
1586
|
+
const result = JSON.parse(decoded);
|
|
1587
|
+
if (result && Array.isArray(result) && result.length > 0) {
|
|
1588
|
+
return {
|
|
1589
|
+
elements: result
|
|
1590
|
+
};
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
catch (error) {
|
|
1594
|
+
console.error(error);
|
|
1595
|
+
return null;
|
|
1596
|
+
}
|
|
1603
1597
|
}
|
|
1604
1598
|
return null;
|
|
1605
1599
|
};
|
|
1606
|
-
const
|
|
1607
|
-
const
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
let
|
|
1614
|
-
if (
|
|
1615
|
-
|
|
1616
|
-
height = editor.getRoughHeight(children[i]);
|
|
1617
|
-
}
|
|
1618
|
-
catch (error) {
|
|
1619
|
-
console.error('buildHeightsAndAccumulatedHeights: getRoughHeight error', error);
|
|
1620
|
-
}
|
|
1600
|
+
const createClipboardData = (html, elements, text, files) => {
|
|
1601
|
+
const data = { elements, text, html, files };
|
|
1602
|
+
return data;
|
|
1603
|
+
};
|
|
1604
|
+
const getClipboardData = async (dataTransfer) => {
|
|
1605
|
+
let clipboardData = null;
|
|
1606
|
+
if (dataTransfer) {
|
|
1607
|
+
let filesData = {};
|
|
1608
|
+
if (dataTransfer.files.length) {
|
|
1609
|
+
filesData = { ...filesData, files: Array.from(dataTransfer.files) };
|
|
1621
1610
|
}
|
|
1622
|
-
|
|
1623
|
-
|
|
1611
|
+
clipboardData = getDataTransferClipboard(dataTransfer);
|
|
1612
|
+
return { ...clipboardData, ...filesData };
|
|
1624
1613
|
}
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
const virtualTopHeight = roundTo(accumulatedHeights[startIndex] ?? 0, 1);
|
|
1630
|
-
return virtualTopHeight;
|
|
1614
|
+
if (isClipboardReadSupported()) {
|
|
1615
|
+
return await getNavigatorClipboard();
|
|
1616
|
+
}
|
|
1617
|
+
return clipboardData;
|
|
1631
1618
|
};
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1619
|
+
/**
|
|
1620
|
+
* @param wrapper get wrapper.innerHTML string which will be written in clipboard
|
|
1621
|
+
* @param attach attach must be child element of wrapper which will be attached json data
|
|
1622
|
+
* @returns void
|
|
1623
|
+
*/
|
|
1624
|
+
const setClipboardData = async (clipboardData, wrapper, attach, dataTransfer) => {
|
|
1625
|
+
if (!clipboardData) {
|
|
1635
1626
|
return;
|
|
1636
1627
|
}
|
|
1637
|
-
const
|
|
1638
|
-
if (
|
|
1628
|
+
const { elements, text } = clipboardData;
|
|
1629
|
+
if (isClipboardWriteSupported()) {
|
|
1630
|
+
const htmlText = buildHTMLText(wrapper, attach, elements);
|
|
1631
|
+
// TODO
|
|
1632
|
+
// maybe fail to write when copy some cell in table
|
|
1633
|
+
return await setNavigatorClipboard(htmlText, elements, text);
|
|
1634
|
+
}
|
|
1635
|
+
if (dataTransfer) {
|
|
1636
|
+
const htmlText = buildHTMLText(wrapper, attach, elements);
|
|
1637
|
+
setDataTransferClipboard(dataTransfer, htmlText);
|
|
1638
|
+
setDataTransferClipboardText(dataTransfer, text);
|
|
1639
1639
|
return;
|
|
1640
1640
|
}
|
|
1641
|
-
const
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1641
|
+
const htmlText = buildHTMLText(wrapper, attach, elements);
|
|
1642
|
+
// Compatible with situations where navigator.clipboard.write is not supported and dataTransfer is empty
|
|
1643
|
+
// Such as contextmenu copy in Firefox.
|
|
1644
|
+
if (isClipboardWriteTextSupported()) {
|
|
1645
|
+
return await navigator.clipboard.writeText(htmlText);
|
|
1646
|
+
}
|
|
1647
|
+
else {
|
|
1648
|
+
return await fallbackCopyText(htmlText);
|
|
1649
|
+
}
|
|
1650
|
+
};
|
|
1651
|
+
const fallbackCopyText = async (text) => {
|
|
1652
|
+
return new Promise((resolve, reject) => {
|
|
1653
|
+
const textArea = document.createElement('textarea');
|
|
1654
|
+
textArea.value = text;
|
|
1655
|
+
textArea.style.position = 'fixed';
|
|
1656
|
+
textArea.style.left = '-999999px';
|
|
1657
|
+
textArea.style.top = '-999999px';
|
|
1658
|
+
textArea.style.opacity = '0';
|
|
1659
|
+
document.body.appendChild(textArea);
|
|
1660
|
+
textArea.focus();
|
|
1661
|
+
textArea.select();
|
|
1662
|
+
try {
|
|
1663
|
+
const successful = document.execCommand('copy');
|
|
1664
|
+
document.body.removeChild(textArea);
|
|
1665
|
+
if (successful) {
|
|
1666
|
+
resolve();
|
|
1667
|
+
}
|
|
1668
|
+
else {
|
|
1669
|
+
reject(new Error('execCommand error'));
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
catch (err) {
|
|
1673
|
+
document.body.removeChild(textArea);
|
|
1674
|
+
reject(err);
|
|
1675
|
+
}
|
|
1676
|
+
});
|
|
1649
1677
|
};
|
|
1650
1678
|
|
|
1651
1679
|
const withAngular = (editor, clipboardFormatKey = 'x-slate-fragment') => {
|
|
@@ -3341,10 +3369,10 @@ const forceOnDOMPaste = IS_SAFARI;
|
|
|
3341
3369
|
class SlateEditable {
|
|
3342
3370
|
set virtualScroll(config) {
|
|
3343
3371
|
this.virtualScrollConfig = config;
|
|
3372
|
+
EDITOR_TO_VIRTUAL_SCROLL_CONFIG.set(this.editor, config);
|
|
3344
3373
|
if (isDebugScrollTop) {
|
|
3345
3374
|
debugLog('log', 'virtualScrollConfig scrollTop:', config.scrollTop);
|
|
3346
3375
|
}
|
|
3347
|
-
IS_ENABLED_VIRTUAL_SCROLL.set(this.editor, config.enabled);
|
|
3348
3376
|
if (this.isEnabledVirtualScroll()) {
|
|
3349
3377
|
this.tryUpdateVirtualViewport();
|
|
3350
3378
|
}
|
|
@@ -3527,13 +3555,13 @@ class SlateEditable {
|
|
|
3527
3555
|
if (this.isEnabledVirtualScroll()) {
|
|
3528
3556
|
this.virtualScrollInitialized = true;
|
|
3529
3557
|
this.virtualTopHeightElement = document.createElement('div');
|
|
3530
|
-
this.virtualTopHeightElement.classList.add(
|
|
3558
|
+
this.virtualTopHeightElement.classList.add(VIRTUAL_TOP_HEIGHT_CLASS_NAME);
|
|
3531
3559
|
this.virtualTopHeightElement.contentEditable = 'false';
|
|
3532
3560
|
this.virtualBottomHeightElement = document.createElement('div');
|
|
3533
|
-
this.virtualBottomHeightElement.classList.add(
|
|
3561
|
+
this.virtualBottomHeightElement.classList.add(VIRTUAL_BOTTOM_HEIGHT_CLASS_NAME);
|
|
3534
3562
|
this.virtualBottomHeightElement.contentEditable = 'false';
|
|
3535
3563
|
this.virtualCenterOutlet = document.createElement('div');
|
|
3536
|
-
this.virtualCenterOutlet.classList.add(
|
|
3564
|
+
this.virtualCenterOutlet.classList.add(VIRTUAL_CENTER_OUTLET_CLASS_NAME);
|
|
3537
3565
|
this.elementRef.nativeElement.appendChild(this.virtualTopHeightElement);
|
|
3538
3566
|
this.elementRef.nativeElement.appendChild(this.virtualCenterOutlet);
|
|
3539
3567
|
this.elementRef.nativeElement.appendChild(this.virtualBottomHeightElement);
|
|
@@ -3589,17 +3617,6 @@ class SlateEditable {
|
|
|
3589
3617
|
});
|
|
3590
3618
|
}
|
|
3591
3619
|
}
|
|
3592
|
-
calcBusinessTop() {
|
|
3593
|
-
const virtualTopBoundingTop = this.virtualTopHeightElement.getBoundingClientRect()?.top ?? 0;
|
|
3594
|
-
const viewportBoundingTop = this.virtualScrollConfig.scrollContainer?.getBoundingClientRect()?.top ?? 0;
|
|
3595
|
-
const businessTop = Math.ceil(virtualTopBoundingTop) + Math.ceil(this.virtualScrollConfig.scrollTop) - Math.floor(viewportBoundingTop);
|
|
3596
|
-
EDITOR_TO_BUSINESS_TOP.set(this.editor, businessTop);
|
|
3597
|
-
if (isDebug) {
|
|
3598
|
-
debugLog('log', 'calcBusinessTop: ', businessTop);
|
|
3599
|
-
this.virtualTopHeightElement.setAttribute('data-business-top', businessTop.toString());
|
|
3600
|
-
}
|
|
3601
|
-
return businessTop;
|
|
3602
|
-
}
|
|
3603
3620
|
getChangedIndics(previousValue) {
|
|
3604
3621
|
const remeasureIndics = [];
|
|
3605
3622
|
this.inViewportChildren.forEach((child, index) => {
|
|
@@ -3733,7 +3750,7 @@ class SlateEditable {
|
|
|
3733
3750
|
const elementLength = children.length;
|
|
3734
3751
|
let businessTop = getBusinessTop(this.editor);
|
|
3735
3752
|
if (businessTop === 0 && this.virtualScrollConfig.scrollTop > 0) {
|
|
3736
|
-
businessTop = this.
|
|
3753
|
+
businessTop = calcBusinessTop(this.editor);
|
|
3737
3754
|
}
|
|
3738
3755
|
const { heights, accumulatedHeights } = buildHeightsAndAccumulatedHeights(this.editor, visibleStates);
|
|
3739
3756
|
const totalHeight = accumulatedHeights[elementLength] + businessTop;
|
|
@@ -4519,7 +4536,9 @@ class SlateEditable {
|
|
|
4519
4536
|
this.forceRender();
|
|
4520
4537
|
}
|
|
4521
4538
|
}
|
|
4522
|
-
if (AngularEditor.hasEditableTarget(this.editor, event.target) &&
|
|
4539
|
+
if (AngularEditor.hasEditableTarget(this.editor, event.target) &&
|
|
4540
|
+
!isSelectionInsideVoid(this.editor) &&
|
|
4541
|
+
!this.isDOMEventHandled(event, this.compositionStart)) {
|
|
4523
4542
|
this.isComposing = true;
|
|
4524
4543
|
}
|
|
4525
4544
|
this.render();
|
|
@@ -4531,7 +4550,9 @@ class SlateEditable {
|
|
|
4531
4550
|
if (!event.data && !Range.isCollapsed(this.editor.selection)) {
|
|
4532
4551
|
Transforms.delete(this.editor);
|
|
4533
4552
|
}
|
|
4534
|
-
if (AngularEditor.hasEditableTarget(this.editor, event.target) &&
|
|
4553
|
+
if (AngularEditor.hasEditableTarget(this.editor, event.target) &&
|
|
4554
|
+
!isSelectionInsideVoid(this.editor) &&
|
|
4555
|
+
!this.isDOMEventHandled(event, this.compositionEnd)) {
|
|
4535
4556
|
// COMPAT: In Chrome/Firefox, `beforeinput` events for compositions
|
|
4536
4557
|
// aren't correct and never fire the "insertFromComposition"
|
|
4537
4558
|
// type that we need. So instead, insert whenever a composition
|
|
@@ -4541,7 +4562,7 @@ class SlateEditable {
|
|
|
4541
4562
|
Editor.insertText(this.editor, event.data);
|
|
4542
4563
|
}
|
|
4543
4564
|
// COMPAT: In Firefox 87.0 CompositionEnd fire twice
|
|
4544
|
-
// so we need avoid repeat
|
|
4565
|
+
// so we need avoid repeat insertText by isComposing === true,
|
|
4545
4566
|
this.isComposing = false;
|
|
4546
4567
|
}
|
|
4547
4568
|
this.render();
|
|
@@ -5060,6 +5081,14 @@ const isTargetInsideVoid = (editor, target) => {
|
|
|
5060
5081
|
catch (error) { }
|
|
5061
5082
|
return slateNode && Element.isElement(slateNode) && Editor.isVoid(editor, slateNode);
|
|
5062
5083
|
};
|
|
5084
|
+
const isSelectionInsideVoid = (editor) => {
|
|
5085
|
+
const selection = editor.selection;
|
|
5086
|
+
if (selection && Range.isCollapsed(selection)) {
|
|
5087
|
+
const currentNode = Node.parent(editor, selection.anchor.path);
|
|
5088
|
+
return Element.isElement(currentNode) && Editor.isVoid(editor, currentNode);
|
|
5089
|
+
}
|
|
5090
|
+
return false;
|
|
5091
|
+
};
|
|
5063
5092
|
const hasStringTarget = (domSelection) => {
|
|
5064
5093
|
return ((domSelection.anchorNode.parentElement.hasAttribute('data-slate-string') ||
|
|
5065
5094
|
domSelection.anchorNode.parentElement.hasAttribute('data-slate-zero-width')) &&
|
|
@@ -5442,5 +5471,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImpo
|
|
|
5442
5471
|
* Generated bundle index. Do not edit.
|
|
5443
5472
|
*/
|
|
5444
5473
|
|
|
5445
|
-
export { AngularEditor, BaseComponent, BaseElementComponent, BaseElementFlavour, BaseFlavour, BaseLeafComponent, BaseLeafFlavour, BaseTextComponent, BaseTextFlavour, BlockCardRef, DEFAULT_ELEMENT_HEIGHT, DefaultTextFlavour, EDITOR_TO_AFTER_VIEW_INIT_QUEUE, EDITOR_TO_BUSINESS_TOP, EDITOR_TO_IS_FROM_SCROLL_TO, EDITOR_TO_ROOT_NODE_WIDTH, EDITOR_TO_VIEWPORT_HEIGHT, EDITOR_TO_VIRTUAL_SCROLL_SELECTION, ELEMENT_KEY_TO_HEIGHTS, ELEMENT_TO_COMPONENT, FAKE_LEFT_BLOCK_CARD_OFFSET, FAKE_RIGHT_BLOCK_CARD_OFFSET, FlavourRef, HAS_BEFORE_INPUT_SUPPORT, IS_ANDROID, IS_APPLE, IS_CHROME, IS_CHROME_LEGACY, IS_EDGE_LEGACY,
|
|
5474
|
+
export { AngularEditor, BaseComponent, BaseElementComponent, BaseElementFlavour, BaseFlavour, BaseLeafComponent, BaseLeafFlavour, BaseTextComponent, BaseTextFlavour, BlockCardRef, DEFAULT_ELEMENT_HEIGHT, DefaultTextFlavour, EDITOR_TO_AFTER_VIEW_INIT_QUEUE, EDITOR_TO_BUSINESS_TOP, EDITOR_TO_IS_FROM_SCROLL_TO, EDITOR_TO_ROOT_NODE_WIDTH, EDITOR_TO_VIEWPORT_HEIGHT, EDITOR_TO_VIRTUAL_SCROLL_CONFIG, EDITOR_TO_VIRTUAL_SCROLL_SELECTION, ELEMENT_KEY_TO_HEIGHTS, ELEMENT_TO_COMPONENT, FAKE_LEFT_BLOCK_CARD_OFFSET, FAKE_RIGHT_BLOCK_CARD_OFFSET, FlavourRef, HAS_BEFORE_INPUT_SUPPORT, IS_ANDROID, IS_APPLE, IS_CHROME, IS_CHROME_LEGACY, IS_EDGE_LEGACY, IS_FIREFOX, IS_FIREFOX_LEGACY, IS_IOS, IS_QQBROWSER, IS_SAFARI, IS_UC_MOBILE, IS_WECHATBROWSER, PLACEHOLDER_SYMBOL, SLATE_BLOCK_CARD_CLASS_NAME, SLATE_DEBUG_KEY, SLATE_DEBUG_KEY_SCROLL_TOP, SlateBlockCard, SlateChildrenOutlet, SlateEditable, SlateErrorCode, SlateFragmentAttributeKey, SlateModule, SlateString, VIRTUAL_BOTTOM_HEIGHT_CLASS_NAME, VIRTUAL_CENTER_OUTLET_CLASS_NAME, VIRTUAL_SCROLL_DEFAULT_BLOCK_HEIGHT, VIRTUAL_TOP_HEIGHT_CLASS_NAME, VoidTextFlavour, blobAsString, buildHTMLText, buildHeightsAndAccumulatedHeights, cacheHeightByElement, calcBusinessTop, calcHeightByElement, calculateVirtualTopHeight, check, clearMinHeightByElement, completeTable, createClipboardData, createText, createThrottleRAF, debugLog, defaultScrollSelectionIntoView, fallbackCopyText, getBlockCardByNativeElement, getBusinessTop, getCachedHeightByElement, getCardTargetAttribute, getClipboardData, getClipboardFromHTMLText, getContentHeight, getDataTransferClipboard, getDataTransferClipboardText, getNavigatorClipboard, getPlainText, getScrollContainer, getSelection, getSlateFragmentAttribute, getViewportHeight, getZeroTextNode, hasAfterContextChange, hasBeforeContextChange, hasBlockCard, hasBlockCardWithNode, hotkeys, isCardCenterByTargetAttr, isCardLeft, isCardLeftByTargetAttr, isCardRightByTargetAttr, isClipboardFile, isClipboardReadSupported, isClipboardWriteSupported, isClipboardWriteTextSupported, isComponentType, isDOMText, isDebug, isDebugScrollTop, isDecoratorRangeListEqual, isFlavourType, isInvalidTable, isSelectionInsideVoid, isTemplateRef, isValid, isValidNumber, measureHeightByIndics, normalize, roundTo, scrollToElement, setClipboardData, setDataTransferClipboard, setDataTransferClipboardText, setMinHeightByElement, setNavigatorClipboard, shallowCompare, stripHtml, withAngular };
|
|
5446
5475
|
//# sourceMappingURL=slate-angular.mjs.map
|