slate-angular 20.2.17 → 20.2.19
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 +942 -908
- package/fesm2022/slate-angular.mjs.map +1 -1
- package/index.d.ts +13 -5
- 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);
|
|
@@ -1276,365 +722,958 @@ class VirtualScrollDebugOverlay {
|
|
|
1276
722
|
this.resizeHandle = resizeHandle;
|
|
1277
723
|
this.applyState();
|
|
1278
724
|
}
|
|
1279
|
-
startDrag(event) {
|
|
1280
|
-
if (event.button !== 0) {
|
|
1281
|
-
return;
|
|
725
|
+
startDrag(event) {
|
|
726
|
+
if (event.button !== 0) {
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
if (!this.container) {
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
const rect = this.container.getBoundingClientRect();
|
|
733
|
+
this.isDragging = true;
|
|
734
|
+
this.wasDragged = false;
|
|
735
|
+
this.dragMoved = false;
|
|
736
|
+
this.dragOffsetX = event.clientX - rect.left;
|
|
737
|
+
this.dragOffsetY = event.clientY - rect.top;
|
|
738
|
+
this.container.style.transition = 'none';
|
|
739
|
+
this.doc.addEventListener('mousemove', this.onDragging);
|
|
740
|
+
this.doc.addEventListener('mouseup', this.onDragEnd);
|
|
741
|
+
if (!this.state.collapsed) {
|
|
742
|
+
event.preventDefault();
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
startResize(event) {
|
|
746
|
+
if (event.button !== 0 || this.state.collapsed) {
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
if (!this.container) {
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
const rect = this.container.getBoundingClientRect();
|
|
753
|
+
this.isResizing = true;
|
|
754
|
+
this.resizeStartX = event.clientX;
|
|
755
|
+
this.resizeStartY = event.clientY;
|
|
756
|
+
this.resizeStartWidth = rect.width;
|
|
757
|
+
this.resizeStartHeight = rect.height;
|
|
758
|
+
this.doc.addEventListener('mousemove', this.onResizing);
|
|
759
|
+
this.doc.addEventListener('mouseup', this.onResizeEnd);
|
|
760
|
+
event.preventDefault();
|
|
761
|
+
event.stopPropagation();
|
|
762
|
+
}
|
|
763
|
+
applyPosition() {
|
|
764
|
+
if (!this.container) {
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
this.container.style.left = `${this.state.left}px`;
|
|
768
|
+
this.container.style.top = `${this.state.top}px`;
|
|
769
|
+
}
|
|
770
|
+
applySize() {
|
|
771
|
+
if (!this.container) {
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
const width = Math.max(VirtualScrollDebugOverlay.minWidth, this.state.width || VirtualScrollDebugOverlay.defaultWidth);
|
|
775
|
+
const height = Math.max(VirtualScrollDebugOverlay.minHeight, this.state.height || VirtualScrollDebugOverlay.defaultHeight);
|
|
776
|
+
this.state.width = width;
|
|
777
|
+
this.state.height = height;
|
|
778
|
+
this.container.style.width = `${width}px`;
|
|
779
|
+
this.container.style.height = `${height}px`;
|
|
780
|
+
}
|
|
781
|
+
applyCollapsedState() {
|
|
782
|
+
if (!this.container || !this.contentWrapper || !this.bubble || !this.collapseToggle) {
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
if (this.state.collapsed) {
|
|
786
|
+
this.container.style.width = '36px';
|
|
787
|
+
this.container.style.height = '36px';
|
|
788
|
+
this.container.style.minWidth = '';
|
|
789
|
+
this.container.style.minHeight = '';
|
|
790
|
+
this.container.style.padding = '0';
|
|
791
|
+
this.container.style.borderRadius = '50%';
|
|
792
|
+
this.container.style.display = 'flex';
|
|
793
|
+
this.container.style.flexDirection = 'row';
|
|
794
|
+
this.container.style.alignItems = 'center';
|
|
795
|
+
this.container.style.justifyContent = 'center';
|
|
796
|
+
this.container.style.cursor = 'move';
|
|
797
|
+
this.contentWrapper.style.display = 'none';
|
|
798
|
+
this.bubble.style.display = 'flex';
|
|
799
|
+
this.collapseToggle.textContent = '展开';
|
|
800
|
+
this.resizeHandle.style.display = 'none';
|
|
801
|
+
}
|
|
802
|
+
else {
|
|
803
|
+
this.applySize();
|
|
804
|
+
this.container.style.padding = '12px';
|
|
805
|
+
this.container.style.borderRadius = '10px';
|
|
806
|
+
this.container.style.display = 'flex';
|
|
807
|
+
this.container.style.flexDirection = 'column';
|
|
808
|
+
this.container.style.gap = '10px';
|
|
809
|
+
this.container.style.cursor = 'default';
|
|
810
|
+
this.contentWrapper.style.display = 'flex';
|
|
811
|
+
this.bubble.style.display = 'none';
|
|
812
|
+
this.collapseToggle.textContent = '折叠';
|
|
813
|
+
this.resizeHandle.style.display = 'block';
|
|
814
|
+
}
|
|
815
|
+
}
|
|
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;
|
|
1282
883
|
}
|
|
1283
|
-
|
|
1284
|
-
return;
|
|
884
|
+
try {
|
|
885
|
+
return JSON.stringify(value);
|
|
1285
886
|
}
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
this.wasDragged = false;
|
|
1289
|
-
this.dragMoved = false;
|
|
1290
|
-
this.dragOffsetX = event.clientX - rect.left;
|
|
1291
|
-
this.dragOffsetY = event.clientY - rect.top;
|
|
1292
|
-
this.container.style.transition = 'none';
|
|
1293
|
-
this.doc.addEventListener('mousemove', this.onDragging);
|
|
1294
|
-
this.doc.addEventListener('mouseup', this.onDragEnd);
|
|
1295
|
-
if (!this.state.collapsed) {
|
|
1296
|
-
event.preventDefault();
|
|
887
|
+
catch (error) {
|
|
888
|
+
return String(value);
|
|
1297
889
|
}
|
|
1298
890
|
}
|
|
1299
|
-
|
|
1300
|
-
if (
|
|
1301
|
-
|
|
1302
|
-
}
|
|
1303
|
-
if (!this.container) {
|
|
1304
|
-
return;
|
|
891
|
+
setScrollTopValue(value) {
|
|
892
|
+
if (this.distanceInput) {
|
|
893
|
+
this.distanceInput.value = String(value ?? 0);
|
|
1305
894
|
}
|
|
1306
|
-
const rect = this.container.getBoundingClientRect();
|
|
1307
|
-
this.isResizing = true;
|
|
1308
|
-
this.resizeStartX = event.clientX;
|
|
1309
|
-
this.resizeStartY = event.clientY;
|
|
1310
|
-
this.resizeStartWidth = rect.width;
|
|
1311
|
-
this.resizeStartHeight = rect.height;
|
|
1312
|
-
this.doc.addEventListener('mousemove', this.onResizing);
|
|
1313
|
-
this.doc.addEventListener('mouseup', this.onResizeEnd);
|
|
1314
|
-
event.preventDefault();
|
|
1315
|
-
event.stopPropagation();
|
|
1316
895
|
}
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
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';
|
|
1320
931
|
}
|
|
1321
|
-
this.container.style.left = `${this.state.left}px`;
|
|
1322
|
-
this.container.style.top = `${this.state.top}px`;
|
|
1323
932
|
}
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
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';
|
|
1327
1059
|
}
|
|
1328
|
-
const width = Math.max(VirtualScrollDebugOverlay.minWidth, this.state.width || VirtualScrollDebugOverlay.defaultWidth);
|
|
1329
|
-
const height = Math.max(VirtualScrollDebugOverlay.minHeight, this.state.height || VirtualScrollDebugOverlay.defaultHeight);
|
|
1330
|
-
this.state.width = width;
|
|
1331
|
-
this.state.height = height;
|
|
1332
|
-
this.container.style.width = `${width}px`;
|
|
1333
|
-
this.container.style.height = `${height}px`;
|
|
1334
1060
|
}
|
|
1335
|
-
|
|
1336
|
-
|
|
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;
|
|
1074
|
+
}
|
|
1075
|
+
};
|
|
1076
|
+
const calcHeightByElement = (editor, element) => {
|
|
1077
|
+
const view = ELEMENT_TO_COMPONENT.get(element);
|
|
1078
|
+
if (!view) {
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
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);
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1337
1097
|
return;
|
|
1338
1098
|
}
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
this.container.style.minWidth = '';
|
|
1343
|
-
this.container.style.minHeight = '';
|
|
1344
|
-
this.container.style.padding = '0';
|
|
1345
|
-
this.container.style.borderRadius = '50%';
|
|
1346
|
-
this.container.style.display = 'flex';
|
|
1347
|
-
this.container.style.flexDirection = 'row';
|
|
1348
|
-
this.container.style.alignItems = 'center';
|
|
1349
|
-
this.container.style.justifyContent = 'center';
|
|
1350
|
-
this.container.style.cursor = 'move';
|
|
1351
|
-
this.contentWrapper.style.display = 'none';
|
|
1352
|
-
this.bubble.style.display = 'flex';
|
|
1353
|
-
this.collapseToggle.textContent = '展开';
|
|
1354
|
-
this.resizeHandle.style.display = 'none';
|
|
1099
|
+
const currentHeight = calcHeightByElement(editor, element);
|
|
1100
|
+
if (isValidNumber(currentHeight) && currentHeight !== preHeight) {
|
|
1101
|
+
hasChanged = true;
|
|
1355
1102
|
}
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
this.container.style.padding = '12px';
|
|
1359
|
-
this.container.style.borderRadius = '10px';
|
|
1360
|
-
this.container.style.display = 'flex';
|
|
1361
|
-
this.container.style.flexDirection = 'column';
|
|
1362
|
-
this.container.style.gap = '10px';
|
|
1363
|
-
this.container.style.cursor = 'default';
|
|
1364
|
-
this.contentWrapper.style.display = 'flex';
|
|
1365
|
-
this.bubble.style.display = 'none';
|
|
1366
|
-
this.collapseToggle.textContent = '折叠';
|
|
1367
|
-
this.resizeHandle.style.display = 'block';
|
|
1103
|
+
if (isDebug && isValidNumber(currentHeight)) {
|
|
1104
|
+
debugLog('log', 'measureHeightByIndics: index: ', index, 'preHeight: ', preHeight, 'height: ', currentHeight);
|
|
1368
1105
|
}
|
|
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;
|
|
1369
1125
|
}
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
this.applyCollapsedState();
|
|
1373
|
-
this.persistState();
|
|
1374
|
-
}
|
|
1375
|
-
applyState() {
|
|
1376
|
-
this.applyPosition();
|
|
1377
|
-
this.applyCollapsedState();
|
|
1126
|
+
if (heights?.has(key.id)) {
|
|
1127
|
+
console.error('getBlockHeight: invalid height value', key.id, height);
|
|
1378
1128
|
}
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
}
|
|
1396
|
-
if (typeof parsed.height === 'number') {
|
|
1397
|
-
this.state.height = parsed.height;
|
|
1398
|
-
}
|
|
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);
|
|
1399
1145
|
}
|
|
1400
1146
|
}
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
}
|
|
1147
|
+
heights[i] = height;
|
|
1148
|
+
accumulatedHeights[i + 1] = accumulatedHeights[i] + height;
|
|
1404
1149
|
}
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
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;
|
|
1408
1204
|
}
|
|
1409
|
-
|
|
1410
|
-
|
|
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)) {
|
|
1252
|
+
return;
|
|
1411
1253
|
}
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
if (!
|
|
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)) {
|
|
1415
1257
|
return;
|
|
1416
1258
|
}
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
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;
|
|
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 });
|
|
1437
1266
|
}
|
|
1438
|
-
|
|
1439
|
-
|
|
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();
|
|
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;
|
|
1482
1391
|
}
|
|
1483
|
-
|
|
1484
|
-
|
|
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
|
+
}
|
|
1485
1402
|
}
|
|
1403
|
+
return true;
|
|
1404
|
+
};
|
|
1405
|
+
|
|
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));
|
|
1410
|
+
};
|
|
1411
|
+
function normalize(document) {
|
|
1412
|
+
return document.filter(value => Element.isElement(value) && isValid(value));
|
|
1486
1413
|
}
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1414
|
+
|
|
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;
|
|
1493
1431
|
};
|
|
1494
1432
|
|
|
1495
|
-
const
|
|
1496
|
-
|
|
1497
|
-
const ELEMENT_KEY_TO_HEIGHTS = new WeakMap();
|
|
1498
|
-
const EDITOR_TO_BUSINESS_TOP = new WeakMap();
|
|
1499
|
-
const EDITOR_TO_ROOT_NODE_WIDTH = new WeakMap();
|
|
1500
|
-
const EDITOR_TO_IS_FROM_SCROLL_TO = new WeakMap();
|
|
1501
|
-
const isValidNumber = (value) => {
|
|
1502
|
-
return typeof value === 'number' && !Number.isNaN(value);
|
|
1433
|
+
const isClipboardReadSupported = () => {
|
|
1434
|
+
return 'clipboard' in navigator && 'read' in navigator.clipboard;
|
|
1503
1435
|
};
|
|
1504
|
-
const
|
|
1505
|
-
|
|
1506
|
-
VirtualScrollDebugOverlay.log(doc, type, ...args);
|
|
1436
|
+
const isClipboardWriteSupported = () => {
|
|
1437
|
+
return 'clipboard' in navigator && 'write' in navigator.clipboard;
|
|
1507
1438
|
};
|
|
1508
|
-
const
|
|
1509
|
-
|
|
1510
|
-
return;
|
|
1511
|
-
}
|
|
1512
|
-
if (!isValidNumber(height)) {
|
|
1513
|
-
console.error('cacheHeightByElement: height must be number', height);
|
|
1514
|
-
return;
|
|
1515
|
-
}
|
|
1516
|
-
const key = AngularEditor.findKey(editor, element);
|
|
1517
|
-
const heights = ELEMENT_KEY_TO_HEIGHTS.get(editor);
|
|
1518
|
-
heights.set(key.id, height);
|
|
1439
|
+
const isClipboardWriteTextSupported = () => {
|
|
1440
|
+
return 'clipboard' in navigator && 'writeText' in navigator.clipboard;
|
|
1519
1441
|
};
|
|
1520
|
-
const
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
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 };
|
|
1531
1497
|
}
|
|
1532
1498
|
}
|
|
1533
|
-
|
|
1534
|
-
const
|
|
1535
|
-
|
|
1536
|
-
return;
|
|
1499
|
+
else {
|
|
1500
|
+
const textData = getDataTransferClipboardText(data);
|
|
1501
|
+
return textData;
|
|
1537
1502
|
}
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
if (
|
|
1541
|
-
|
|
1542
|
-
return true;
|
|
1503
|
+
};
|
|
1504
|
+
const getDataTransferClipboardText = (data) => {
|
|
1505
|
+
if (!data) {
|
|
1506
|
+
return null;
|
|
1543
1507
|
}
|
|
1544
|
-
|
|
1545
|
-
|
|
1508
|
+
const text = data?.getData(`text/plain`);
|
|
1509
|
+
if (text) {
|
|
1510
|
+
const htmlClipboardData = getClipboardFromHTMLText(text);
|
|
1511
|
+
if (htmlClipboardData) {
|
|
1512
|
+
return htmlClipboardData;
|
|
1513
|
+
}
|
|
1546
1514
|
}
|
|
1515
|
+
return { text };
|
|
1547
1516
|
};
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
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
|
+
]);
|
|
1552
1529
|
}
|
|
1553
|
-
const height = view.calcHeight();
|
|
1554
|
-
cacheHeightByElement(editor, element, height);
|
|
1555
|
-
return height;
|
|
1556
1530
|
};
|
|
1557
|
-
const
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
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 };
|
|
1567
1561
|
}
|
|
1568
1562
|
}
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
debugLog('log', 'measureHeightByIndics: height not equal, 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
|
+
}
|
|
1577
1570
|
}
|
|
1578
|
-
}
|
|
1579
|
-
return
|
|
1571
|
+
}
|
|
1572
|
+
return clipboardData;
|
|
1580
1573
|
};
|
|
1581
|
-
|
|
1582
|
-
|
|
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;
|
|
1583
1580
|
};
|
|
1584
|
-
const
|
|
1585
|
-
const
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
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
|
+
}
|
|
1593
1597
|
}
|
|
1594
1598
|
return null;
|
|
1595
1599
|
};
|
|
1596
|
-
const
|
|
1597
|
-
const
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
let
|
|
1604
|
-
if (
|
|
1605
|
-
|
|
1606
|
-
height = editor.getRoughHeight(children[i]);
|
|
1607
|
-
}
|
|
1608
|
-
catch (error) {
|
|
1609
|
-
console.error('buildHeightsAndAccumulatedHeights: getRoughHeight error', error);
|
|
1610
|
-
}
|
|
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) };
|
|
1611
1610
|
}
|
|
1612
|
-
|
|
1613
|
-
|
|
1611
|
+
clipboardData = getDataTransferClipboard(dataTransfer);
|
|
1612
|
+
return { ...clipboardData, ...filesData };
|
|
1614
1613
|
}
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
return accumulatedHeights[startIndex] ?? 0;
|
|
1614
|
+
if (isClipboardReadSupported()) {
|
|
1615
|
+
return await getNavigatorClipboard();
|
|
1616
|
+
}
|
|
1617
|
+
return clipboardData;
|
|
1620
1618
|
};
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
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) {
|
|
1624
1626
|
return;
|
|
1625
1627
|
}
|
|
1626
|
-
const
|
|
1627
|
-
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);
|
|
1628
1639
|
return;
|
|
1629
1640
|
}
|
|
1630
|
-
const
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
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
|
+
});
|
|
1638
1677
|
};
|
|
1639
1678
|
|
|
1640
1679
|
const withAngular = (editor, clipboardFormatKey = 'x-slate-fragment') => {
|
|
@@ -3330,10 +3369,10 @@ const forceOnDOMPaste = IS_SAFARI;
|
|
|
3330
3369
|
class SlateEditable {
|
|
3331
3370
|
set virtualScroll(config) {
|
|
3332
3371
|
this.virtualScrollConfig = config;
|
|
3372
|
+
EDITOR_TO_VIRTUAL_SCROLL_CONFIG.set(this.editor, config);
|
|
3333
3373
|
if (isDebugScrollTop) {
|
|
3334
3374
|
debugLog('log', 'virtualScrollConfig scrollTop:', config.scrollTop);
|
|
3335
3375
|
}
|
|
3336
|
-
IS_ENABLED_VIRTUAL_SCROLL.set(this.editor, config.enabled);
|
|
3337
3376
|
if (this.isEnabledVirtualScroll()) {
|
|
3338
3377
|
this.tryUpdateVirtualViewport();
|
|
3339
3378
|
}
|
|
@@ -3384,8 +3423,6 @@ class SlateEditable {
|
|
|
3384
3423
|
this.virtualScrollConfig = {
|
|
3385
3424
|
enabled: false,
|
|
3386
3425
|
scrollTop: 0,
|
|
3387
|
-
viewportHeight: 0,
|
|
3388
|
-
viewportBoundingTop: 0,
|
|
3389
3426
|
scrollContainer: null
|
|
3390
3427
|
};
|
|
3391
3428
|
this.inViewportChildren = [];
|
|
@@ -3518,13 +3555,13 @@ class SlateEditable {
|
|
|
3518
3555
|
if (this.isEnabledVirtualScroll()) {
|
|
3519
3556
|
this.virtualScrollInitialized = true;
|
|
3520
3557
|
this.virtualTopHeightElement = document.createElement('div');
|
|
3521
|
-
this.virtualTopHeightElement.classList.add(
|
|
3558
|
+
this.virtualTopHeightElement.classList.add(VIRTUAL_TOP_HEIGHT_CLASS_NAME);
|
|
3522
3559
|
this.virtualTopHeightElement.contentEditable = 'false';
|
|
3523
3560
|
this.virtualBottomHeightElement = document.createElement('div');
|
|
3524
|
-
this.virtualBottomHeightElement.classList.add(
|
|
3561
|
+
this.virtualBottomHeightElement.classList.add(VIRTUAL_BOTTOM_HEIGHT_CLASS_NAME);
|
|
3525
3562
|
this.virtualBottomHeightElement.contentEditable = 'false';
|
|
3526
3563
|
this.virtualCenterOutlet = document.createElement('div');
|
|
3527
|
-
this.virtualCenterOutlet.classList.add(
|
|
3564
|
+
this.virtualCenterOutlet.classList.add(VIRTUAL_CENTER_OUTLET_CLASS_NAME);
|
|
3528
3565
|
this.elementRef.nativeElement.appendChild(this.virtualTopHeightElement);
|
|
3529
3566
|
this.elementRef.nativeElement.appendChild(this.virtualCenterOutlet);
|
|
3530
3567
|
this.elementRef.nativeElement.appendChild(this.virtualBottomHeightElement);
|
|
@@ -3548,6 +3585,17 @@ class SlateEditable {
|
|
|
3548
3585
|
}
|
|
3549
3586
|
});
|
|
3550
3587
|
this.editorResizeObserver.observe(this.elementRef.nativeElement);
|
|
3588
|
+
if (this.virtualScrollConfig.scrollContainer) {
|
|
3589
|
+
this.editorScrollContainerResizeObserver = new ResizeObserver(entries => {
|
|
3590
|
+
const height = this.virtualScrollConfig.scrollContainer.getBoundingClientRect().height;
|
|
3591
|
+
EDITOR_TO_VIEWPORT_HEIGHT.set(this.editor, height);
|
|
3592
|
+
if (isDebug) {
|
|
3593
|
+
debugLog('log', 'editorScrollContainerResizeObserver calc viewport height: ', height);
|
|
3594
|
+
this.virtualTopHeightElement.setAttribute('viewport-height', height.toString());
|
|
3595
|
+
}
|
|
3596
|
+
});
|
|
3597
|
+
this.editorScrollContainerResizeObserver.observe(this.virtualScrollConfig.scrollContainer);
|
|
3598
|
+
}
|
|
3551
3599
|
let pendingRemeasureIndics = [];
|
|
3552
3600
|
this.indicsOfNeedBeMeasured$
|
|
3553
3601
|
.pipe(tap((previousValue) => {
|
|
@@ -3582,9 +3630,9 @@ class SlateEditable {
|
|
|
3582
3630
|
if (!this.virtualScrollInitialized) {
|
|
3583
3631
|
return;
|
|
3584
3632
|
}
|
|
3585
|
-
this.virtualTopHeightElement.style.height = `${topHeight}px`;
|
|
3633
|
+
this.virtualTopHeightElement.style.height = `${roundTo(topHeight, 1)}px`;
|
|
3586
3634
|
if (bottomHeight !== undefined) {
|
|
3587
|
-
this.virtualBottomHeightElement.style.height = `${bottomHeight}px`;
|
|
3635
|
+
this.virtualBottomHeightElement.style.height = `${roundTo(bottomHeight, 1)}px`;
|
|
3588
3636
|
}
|
|
3589
3637
|
}
|
|
3590
3638
|
getActualVirtualTopHeight() {
|
|
@@ -3629,7 +3677,7 @@ class SlateEditable {
|
|
|
3629
3677
|
const refreshVirtualTopHeight = calculateVirtualTopHeight(this.editor, this.inViewportIndics[0], visibleStates);
|
|
3630
3678
|
if (topHeight !== refreshVirtualTopHeight) {
|
|
3631
3679
|
if (isDebug) {
|
|
3632
|
-
debugLog('log', 'update top height since dirty state
|
|
3680
|
+
debugLog('log', 'update top height since dirty state,增加高度: ', refreshVirtualTopHeight - topHeight);
|
|
3633
3681
|
}
|
|
3634
3682
|
this.setVirtualSpaceHeight(refreshVirtualTopHeight);
|
|
3635
3683
|
return;
|
|
@@ -3668,9 +3716,11 @@ class SlateEditable {
|
|
|
3668
3716
|
const newHeights = buildHeightsAndAccumulatedHeights(this.editor, visibleStates);
|
|
3669
3717
|
const actualTopHeightAfterAdd = newHeights.accumulatedHeights[startIndexBeforeAdd];
|
|
3670
3718
|
const newTopHeight = virtualView.top - (actualTopHeightAfterAdd - topHeightBeforeAdd);
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3719
|
+
if (topHeightBeforeAdd !== actualTopHeightAfterAdd) {
|
|
3720
|
+
this.setVirtualSpaceHeight(newTopHeight);
|
|
3721
|
+
if (isDebug) {
|
|
3722
|
+
debugLog('log', `update top height since will add element in top,减去高度: ${topHeightBeforeAdd - actualTopHeightAfterAdd}`);
|
|
3723
|
+
}
|
|
3674
3724
|
}
|
|
3675
3725
|
}
|
|
3676
3726
|
}
|
|
@@ -3696,32 +3746,12 @@ class SlateEditable {
|
|
|
3696
3746
|
};
|
|
3697
3747
|
}
|
|
3698
3748
|
const scrollTop = this.virtualScrollConfig.scrollTop;
|
|
3699
|
-
let viewportHeight = this.
|
|
3700
|
-
if (!viewportHeight) {
|
|
3701
|
-
return {
|
|
3702
|
-
inViewportChildren: [],
|
|
3703
|
-
inViewportIndics: [],
|
|
3704
|
-
top: 0,
|
|
3705
|
-
bottom: 0,
|
|
3706
|
-
heights: []
|
|
3707
|
-
};
|
|
3708
|
-
}
|
|
3749
|
+
let viewportHeight = getViewportHeight(this.editor);
|
|
3709
3750
|
const elementLength = children.length;
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
const virtualTopBoundingTop = this.virtualTopHeightElement.getBoundingClientRect()?.top ?? 0;
|
|
3714
|
-
const businessTop = Math.ceil(virtualTopBoundingTop) +
|
|
3715
|
-
Math.ceil(this.virtualScrollConfig.scrollTop) -
|
|
3716
|
-
Math.floor(this.virtualScrollConfig.viewportBoundingTop);
|
|
3717
|
-
EDITOR_TO_BUSINESS_TOP.set(this.editor, businessTop);
|
|
3718
|
-
if (isDebug) {
|
|
3719
|
-
debugLog('log', 'businessTop', businessTop);
|
|
3720
|
-
this.virtualTopHeightElement.setAttribute('data-business-top', businessTop.toString());
|
|
3721
|
-
}
|
|
3722
|
-
}, 100);
|
|
3751
|
+
let businessTop = getBusinessTop(this.editor);
|
|
3752
|
+
if (businessTop === 0 && this.virtualScrollConfig.scrollTop > 0) {
|
|
3753
|
+
businessTop = calcBusinessTop(this.editor);
|
|
3723
3754
|
}
|
|
3724
|
-
const businessTop = getBusinessTop(this.editor);
|
|
3725
3755
|
const { heights, accumulatedHeights } = buildHeightsAndAccumulatedHeights(this.editor, visibleStates);
|
|
3726
3756
|
const totalHeight = accumulatedHeights[elementLength] + businessTop;
|
|
3727
3757
|
let startPosition = Math.max(scrollTop - businessTop, 0);
|
|
@@ -3955,6 +3985,9 @@ class SlateEditable {
|
|
|
3955
3985
|
if (!hasDomSelectionInEditor && !AngularEditor.isFocused(this.editor)) {
|
|
3956
3986
|
return;
|
|
3957
3987
|
}
|
|
3988
|
+
if (AngularEditor.isReadOnly(this.editor) && (!selection || Range.isCollapsed(selection))) {
|
|
3989
|
+
return;
|
|
3990
|
+
}
|
|
3958
3991
|
// If the DOM selection is in the editor and the editor selection is already correct, we're done.
|
|
3959
3992
|
if (hasDomSelection && hasDomSelectionInEditor && selection && hasStringTarget(domSelection)) {
|
|
3960
3993
|
const rangeFromDOMSelection = AngularEditor.toSlateRange(this.editor, domSelection, {
|
|
@@ -4900,6 +4933,7 @@ class SlateEditable {
|
|
|
4900
4933
|
//#endregion
|
|
4901
4934
|
ngOnDestroy() {
|
|
4902
4935
|
this.editorResizeObserver?.disconnect();
|
|
4936
|
+
this.editorScrollContainerResizeObserver?.disconnect();
|
|
4903
4937
|
NODE_TO_ELEMENT.delete(this.editor);
|
|
4904
4938
|
this.manualListeners.forEach(manualListener => {
|
|
4905
4939
|
manualListener();
|
|
@@ -5425,5 +5459,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImpo
|
|
|
5425
5459
|
* Generated bundle index. Do not edit.
|
|
5426
5460
|
*/
|
|
5427
5461
|
|
|
5428
|
-
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_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,
|
|
5462
|
+
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, isTemplateRef, isValid, isValidNumber, measureHeightByIndics, normalize, roundTo, scrollToElement, setClipboardData, setDataTransferClipboard, setDataTransferClipboardText, setMinHeightByElement, setNavigatorClipboard, shallowCompare, stripHtml, withAngular };
|
|
5429
5463
|
//# sourceMappingURL=slate-angular.mjs.map
|