@37signals/lexxy 0.9.32 → 0.9.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lexxy.esm.js +1535 -641
- package/dist/stylesheets/lexxy-editor.css +103 -74
- package/dist/stylesheets/lexxy-variables.css +4 -4
- package/package.json +1 -1
package/dist/lexxy.esm.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
export { highlightCode, highlightElement } from './lexxy_helpers.esm.js';
|
|
2
2
|
import createDOMPurify from 'dompurify';
|
|
3
3
|
import { getStyleObjectFromCSS, getCSSFromStyleObject, $getSelectionStyleValueForProperty, $ensureForwardRangeSelection, $isAtNodeEnd, $patchStyleText, $setBlocksType, $forEachSelectedTextNode } from '@lexical/selection';
|
|
4
|
-
import { SKIP_DOM_SELECTION_TAG, CAN_UNDO_COMMAND, COMMAND_PRIORITY_LOW, CAN_REDO_COMMAND, $getSelection, $isRangeSelection, DecoratorNode, $createTextNode, defineExtension, $getRoot, $caretFromPoint, $setSelectionFromCaretRange, $getCaretRange, $normalizeCaret, $getChildCaret, $getCaretInDirection, $isParagraphNode, $isLineBreakNode, $createParagraphNode, $isElementNode, $isRootOrShadowRoot, $isRootNode, $
|
|
4
|
+
import { SKIP_DOM_SELECTION_TAG, CAN_UNDO_COMMAND, COMMAND_PRIORITY_LOW, CAN_REDO_COMMAND, $getSelection, $isRangeSelection, $isTextNode, DecoratorNode, $createTextNode, defineExtension, $createNodeSelection, $getRoot, $caretFromPoint, $setSelectionFromCaretRange, $getCaretRange, $normalizeCaret, $getChildCaret, $getCaretInDirection, $isParagraphNode, $isLineBreakNode, $createParagraphNode, $isElementNode, $isRootOrShadowRoot, $isRootNode, $isDecoratorNode, $getSiblingCaret, $rewindSiblingCaret, $splitAtPointCaretNext, $normalizeSelection__EXPERIMENTAL, $isNodeSelection, $isChildCaret, $isTextPointCaret, $isExtendableTextPointCaret, $isSiblingCaret, $getCommonAncestor, $findMatchingParent, TextNode, createCommand, COMMAND_PRIORITY_EDITOR, $getEditor, $getNodeByKey, HISTORY_MERGE_TAG, SKIP_SCROLL_INTO_VIEW_TAG, $cloneWithProperties, $getNearestRootOrShadowRoot, $createRangeSelection, $setSelection, ElementNode, $splitNode, createState, COMMAND_PRIORITY_NORMAL, $getState, $setState, $hasUpdateTag, PASTE_TAG, FORMAT_TEXT_COMMAND, UNDO_COMMAND, REDO_COMMAND, KEY_ARROW_RIGHT_COMMAND, KEY_TAB_COMMAND, INSERT_LINE_BREAK_COMMAND, COMMAND_PRIORITY_HIGH, INSERT_PARAGRAPH_COMMAND, OUTDENT_CONTENT_COMMAND, INDENT_CONTENT_COMMAND, KEY_ARROW_LEFT_COMMAND, KEY_ARROW_UP_COMMAND, KEY_ARROW_DOWN_COMMAND, DELETE_CHARACTER_COMMAND, SELECTION_CHANGE_COMMAND, COMMAND_PRIORITY_CRITICAL, $addUpdateTag, CLICK_COMMAND, isDOMNode, $getNearestNodeFromDOMNode, $getChildCaretAtIndex, $createLineBreakNode, SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, PASTE_COMMAND, $onUpdate, ParagraphNode, RootNode, DRAGSTART_COMMAND, DROP_COMMAND, KEY_DOWN_COMMAND, flipDirection, mergeRegister as mergeRegister$1, $createRangeSelectionFromDom, CLEAR_HISTORY_COMMAND, KEY_ENTER_COMMAND, KEY_SPACE_COMMAND, INPUT_COMMAND, KEY_BACKSPACE_COMMAND } from 'lexical';
|
|
5
5
|
import * as lexical from 'lexical';
|
|
6
6
|
export { lexical as Lexical };
|
|
7
7
|
import { LinkNode, $createAutoLinkNode, $toggleLink, $createLinkNode, $isLinkNode, AutoLinkNode } from '@lexical/link';
|
|
8
8
|
import { buildEditorFromExtensions } from '@lexical/extension';
|
|
9
9
|
import { ListNode, ListItemNode, $getListDepth, $isListNode, $isListItemNode, INSERT_UNORDERED_LIST_COMMAND, INSERT_ORDERED_LIST_COMMAND, $createListNode, $createListItemNode, registerList } from '@lexical/list';
|
|
10
|
-
import { $getNearestNodeOfType, $wrapNodeInElement, $lastToFirstIterator, $descendantsMatching,
|
|
10
|
+
import { $getNearestNodeOfType, $wrapNodeInElement, $lastToFirstIterator, $descendantsMatching, $insertFirst, $unwrapAndFilterDescendants, $firstToLastIterator, mergeRegister, $insertNodeToNearestRoot, $getNearestBlockElementAncestorOrThrow, IS_APPLE } from '@lexical/utils';
|
|
11
11
|
import { registerPlainText } from '@lexical/plain-text';
|
|
12
12
|
import { RichTextExtension, $isQuoteNode, QuoteNode, $isHeadingNode, $createHeadingNode, $createQuoteNode, HeadingNode, registerRichText } from '@lexical/rich-text';
|
|
13
13
|
import { $generateNodesFromDOM, $generateHtmlFromNodes } from '@lexical/html';
|
|
@@ -74,10 +74,6 @@ class Configuration {
|
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
function range(from, to) {
|
|
78
|
-
return [ ...Array(1 + to - from).keys() ].map(i => i + from)
|
|
79
|
-
}
|
|
80
|
-
|
|
81
77
|
const global = new Configuration({
|
|
82
78
|
attachmentTagName: "action-text-attachment",
|
|
83
79
|
attachmentContentTypeNamespace: "actiontext",
|
|
@@ -98,8 +94,28 @@ const presets = new Configuration({
|
|
|
98
94
|
headings: [ "h2", "h3", "h4" ],
|
|
99
95
|
highlight: {
|
|
100
96
|
buttons: {
|
|
101
|
-
color:
|
|
102
|
-
|
|
97
|
+
color: [
|
|
98
|
+
{ value: "var(--highlight-1)", label: "Yellow" },
|
|
99
|
+
{ value: "var(--highlight-2)", label: "Orange" },
|
|
100
|
+
{ value: "var(--highlight-3)", label: "Red" },
|
|
101
|
+
{ value: "var(--highlight-4)", label: "Magenta" },
|
|
102
|
+
{ value: "var(--highlight-5)", label: "Purple" },
|
|
103
|
+
{ value: "var(--highlight-6)", label: "Blue" },
|
|
104
|
+
{ value: "var(--highlight-7)", label: "Green" },
|
|
105
|
+
{ value: "var(--highlight-8)", label: "Sand" },
|
|
106
|
+
{ value: "var(--highlight-9)", label: "Gray" },
|
|
107
|
+
],
|
|
108
|
+
"background-color": [
|
|
109
|
+
{ value: "var(--highlight-bg-1)", label: "Yellow" },
|
|
110
|
+
{ value: "var(--highlight-bg-2)", label: "Orange" },
|
|
111
|
+
{ value: "var(--highlight-bg-3)", label: "Red" },
|
|
112
|
+
{ value: "var(--highlight-bg-4)", label: "Magenta" },
|
|
113
|
+
{ value: "var(--highlight-bg-5)", label: "Purple" },
|
|
114
|
+
{ value: "var(--highlight-bg-6)", label: "Blue" },
|
|
115
|
+
{ value: "var(--highlight-bg-7)", label: "Green" },
|
|
116
|
+
{ value: "var(--highlight-bg-8)", label: "Sand" },
|
|
117
|
+
{ value: "var(--highlight-bg-9)", label: "Gray" },
|
|
118
|
+
],
|
|
103
119
|
},
|
|
104
120
|
permit: {
|
|
105
121
|
color: [],
|
|
@@ -568,11 +584,16 @@ function checkVisibility(element, options) {
|
|
|
568
584
|
}
|
|
569
585
|
}
|
|
570
586
|
|
|
571
|
-
|
|
587
|
+
// Keyboard and programmatic clicks carry a synthetic pointer.
|
|
588
|
+
function isKeyboardActivation(event) {
|
|
589
|
+
return event instanceof PointerEvent && event.pointerId === -1
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
function handleRollingTabIndex(elements, event, { orientation = "horizontal", wrap = false } = {}) {
|
|
572
593
|
const previousActiveElement = document.activeElement;
|
|
573
594
|
|
|
574
595
|
if (elements.includes(previousActiveElement)) {
|
|
575
|
-
const finder = new NextElementFinder(elements, event.key);
|
|
596
|
+
const finder = new NextElementFinder(elements, event.key, { orientation, wrap });
|
|
576
597
|
|
|
577
598
|
if (finder.selectNext(previousActiveElement)) {
|
|
578
599
|
event.preventDefault();
|
|
@@ -581,9 +602,11 @@ function handleRollingTabIndex(elements, event) {
|
|
|
581
602
|
}
|
|
582
603
|
|
|
583
604
|
class NextElementFinder {
|
|
584
|
-
constructor(elements, key) {
|
|
605
|
+
constructor(elements, key, { orientation, wrap }) {
|
|
585
606
|
this.elements = elements;
|
|
586
607
|
this.key = key;
|
|
608
|
+
this.orientation = orientation;
|
|
609
|
+
this.wrap = wrap;
|
|
587
610
|
}
|
|
588
611
|
|
|
589
612
|
selectNext(fromElement) {
|
|
@@ -600,23 +623,36 @@ class NextElementFinder {
|
|
|
600
623
|
}
|
|
601
624
|
|
|
602
625
|
#findNextElement(fromElement) {
|
|
603
|
-
switch (this.key) {
|
|
604
|
-
case "
|
|
605
|
-
case "ArrowDown":
|
|
626
|
+
switch (this.#directionFor(this.key)) {
|
|
627
|
+
case "next":
|
|
606
628
|
return this.#findNextSibling(fromElement)
|
|
607
629
|
|
|
608
|
-
case "
|
|
609
|
-
case "ArrowUp":
|
|
630
|
+
case "previous":
|
|
610
631
|
return this.#findPreviousSibling(fromElement)
|
|
611
632
|
|
|
612
|
-
case "
|
|
633
|
+
case "first":
|
|
613
634
|
return this.#findFirst()
|
|
614
635
|
|
|
615
|
-
case "
|
|
636
|
+
case "last":
|
|
616
637
|
return this.#findLast()
|
|
617
638
|
}
|
|
618
639
|
}
|
|
619
640
|
|
|
641
|
+
#directionFor(key) {
|
|
642
|
+
if (key === "Home") return "first"
|
|
643
|
+
if (key === "End") return "last"
|
|
644
|
+
if (this.#nextKeys.includes(key)) return "next"
|
|
645
|
+
if (this.#previousKeys.includes(key)) return "previous"
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
get #nextKeys() {
|
|
649
|
+
return this.orientation === "both" ? [ "ArrowRight", "ArrowDown" ] : [ "ArrowRight" ]
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
get #previousKeys() {
|
|
653
|
+
return this.orientation === "both" ? [ "ArrowLeft", "ArrowUp" ] : [ "ArrowLeft" ]
|
|
654
|
+
}
|
|
655
|
+
|
|
620
656
|
#findFirst(elements = this.elements) {
|
|
621
657
|
return elements.find(isActiveAndVisible)
|
|
622
658
|
}
|
|
@@ -627,12 +663,20 @@ class NextElementFinder {
|
|
|
627
663
|
|
|
628
664
|
#findNextSibling(element) {
|
|
629
665
|
const afterElements = this.elements.slice(this.#indexOf(element) + 1);
|
|
630
|
-
return this.#findFirst(afterElements)
|
|
666
|
+
return this.#findFirst(afterElements) ?? this.#wrapToFirst()
|
|
631
667
|
}
|
|
632
668
|
|
|
633
669
|
#findPreviousSibling(element) {
|
|
634
670
|
const beforeElements = this.elements.slice(0, this.#indexOf(element));
|
|
635
|
-
return this.#findLast(beforeElements)
|
|
671
|
+
return this.#findLast(beforeElements) ?? this.#wrapToLast()
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
#wrapToFirst() {
|
|
675
|
+
return this.wrap ? this.#findFirst() : undefined
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
#wrapToLast() {
|
|
679
|
+
return this.wrap ? this.#findLast() : undefined
|
|
636
680
|
}
|
|
637
681
|
|
|
638
682
|
#indexOf(element) {
|
|
@@ -653,29 +697,29 @@ class NextElementFinder {
|
|
|
653
697
|
|
|
654
698
|
var ToolbarIcons = {
|
|
655
699
|
"bold":
|
|
656
|
-
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
|
700
|
+
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
657
701
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.05273 1.88232C10.6866 1.88237 12.0033 2.20353 12.9529 2.89673L13.1272 3.0293C13.974 3.70864 14.4008 4.63245 14.4009 5.76562C14.4008 6.49354 14.2316 7.15281 13.8845 7.73145C13.6683 8.09188 13.3997 8.40162 13.0818 8.66016C13.5902 8.92606 14.0196 9.28599 14.3635 9.74121C14.8586 10.3834 15.0945 11.1743 15.0945 12.0879C15.0944 13.3698 14.5922 14.3931 13.5879 15.1106L13.5857 15.1128C12.5967 15.805 11.196 16.125 9.43799 16.125H3.10547V1.88232L9.05273 1.88232ZM6.36108 13.4084H9.28418C10.224 13.4084 10.8634 13.2491 11.2581 12.9851C11.6259 12.7389 11.8198 12.3768 11.8198 11.8367C11.8197 11.2968 11.6259 10.9351 11.2581 10.689C10.8634 10.425 10.2241 10.2649 9.28418 10.2649H6.36108V13.4084ZM6.36108 7.56812H8.78247C9.5163 7.56809 10.0547 7.45371 10.429 7.25757L10.5791 7.16895C10.9438 6.92178 11.1255 6.57934 11.1255 6.09302C11.1254 5.59017 10.9414 5.25227 10.5835 5.02002L10.5784 5.01636L10.5732 5.01343C10.1994 4.75387 9.61878 4.59818 8.78247 4.59814H6.36108V7.56812Z"/>
|
|
658
702
|
</svg>`,
|
|
659
703
|
|
|
660
704
|
"italic":
|
|
661
|
-
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
|
705
|
+
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
662
706
|
<path d="M14.1379 3.91187L14.1086 4.06421H11.4668L9.49805 13.9431H12.0981L11.7473 15.7852L11.7188 15.9375H4.16675L4.51758 14.0955L4.54614 13.9431H7.18799L9.17505 4.06421H6.55664L6.90747 2.22217L6.93677 2.06982H14.4888L14.1379 3.91187Z"/>
|
|
663
707
|
</svg>`,
|
|
664
708
|
|
|
665
709
|
"strikethrough":
|
|
666
|
-
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
|
710
|
+
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
667
711
|
<path d="M14.3723 11.8015C14.3771 11.8858 14.3811 11.9756 14.3811 12.0681C14.3811 12.811 14.1777 13.4959 13.7725 14.1174L13.7717 14.1189C13.3624 14.7329 12.7463 15.2162 11.9377 15.5742L11.9348 15.5757C11.1214 15.9223 10.1306 16.092 8.96997 16.092C7.9356 16.092 6.93308 15.9348 5.96338 15.6204L5.96045 15.6189C5.00593 15.292 4.24112 14.8699 3.67676 14.3459L3.57568 14.2522L3.63501 14.1277L4.45605 12.397L4.64282 12.5654C5.13492 13.0083 5.76733 13.3759 6.54492 13.6648C7.33475 13.9406 8.14322 14.0786 8.96997 14.0786C10.0731 14.0786 10.8638 13.8932 11.3708 13.5513C11.8757 13.1982 12.1172 12.7464 12.1172 12.1838C12.1172 12.0662 12.1049 11.9556 12.0828 11.8513L12.0344 11.625H14.3621L14.3723 11.8015Z"/>
|
|
668
712
|
<path d="M9.2981 1.91602C10.111 1.91604 10.9109 2.02122 11.6975 2.23096C12.4855 2.44111 13.1683 2.74431 13.7417 3.14429L13.8655 3.23071L13.8083 3.36987L13.1726 4.91235L13.0869 5.1189L12.8987 4.99878C12.3487 4.64881 11.761 4.38633 11.1365 4.21143L11.1328 4.20996C10.585 4.04564 10.0484 3.95419 9.52295 3.93384L9.2981 3.92944C8.22329 3.92944 7.44693 4.12611 6.94043 4.49121C6.44619 4.85665 6.20874 5.31616 6.20874 5.88135L6.21533 6.03296C6.24495 6.37662 6.37751 6.65526 6.61011 6.87964L6.72144 6.97632C6.98746 7.19529 7.30625 7.37584 7.68018 7.51538L8.05151 7.63184C8.45325 7.75061 8.94669 7.87679 9.53247 8.01123L9.53467 8.01196C10.1213 8.15305 10.6426 8.29569 11.0991 8.4375H15C15.5178 8.4375 15.9375 8.85723 15.9375 9.375C15.9375 9.89277 15.5178 10.3125 15 10.3125H3C2.48223 10.3125 2.0625 9.89277 2.0625 9.375C2.0625 8.85723 2.48223 8.4375 3 8.4375H4.93726C4.83783 8.34526 4.74036 8.24896 4.64795 8.146L4.64502 8.14233C4.1721 7.58596 3.94482 6.85113 3.94482 5.95825C3.94483 5.20441 4.14059 4.51965 4.53369 3.90967L4.53516 3.90747C4.94397 3.29427 5.55262 2.81114 6.34863 2.45288C7.15081 2.0919 8.13683 1.91602 9.2981 1.91602Z"/>
|
|
669
713
|
</svg>`,
|
|
670
714
|
|
|
671
715
|
"underline":
|
|
672
|
-
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
|
716
|
+
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
673
717
|
<path d="M14 14C14.5523 14 15 14.4477 15 15C15 15.5523 14.5523 16 14 16H4C3.44772 16 3 15.5523 3 15C3 14.4477 3.44772 14 4 14H14Z"/>
|
|
674
718
|
<path d="M12.625 1.59375C13.25 1.59375 13.625 1.97656 13.625 2.64062V9.02344C13.625 11.4844 11.8516 13.1875 8.99219 13.1875C6.14062 13.1875 4.35938 11.4844 4.35938 9.02344V2.64062C4.35938 1.97656 4.74219 1.59375 5.36719 1.59375C6 1.59375 6.375 1.97656 6.375 2.64062V8.84375C6.375 10.3828 7.32031 11.4297 8.99219 11.4297C10.6641 11.4297 11.6172 10.3828 11.6172 8.84375V2.64062C11.6172 1.97656 11.9922 1.59375 12.625 1.59375Z"/>
|
|
675
719
|
</svg>`,
|
|
676
720
|
|
|
677
721
|
"heading":
|
|
678
|
-
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
|
722
|
+
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
679
723
|
<path d="M11.5 2C12.0523 2 12.5 2.44772 12.5 3V3.5C12.5 4.05228 12.0523 4.5 11.5 4.5H8V15C8 15.5523 7.55228 16 7 16H6.5C5.94772 16 5.5 15.5523 5.5 15V4.5H2C1.44772 4.5 1 4.05228 1 3.5V3C1 2.44772 1.44772 2 2 2H11.5ZM16 7C16.5523 7 17 7.44772 17 8V8.5C17 9.05228 16.5523 9.5 16 9.5H15V15C15 15.5523 14.5523 16 14 16H13.5C12.9477 16 12.5 15.5523 12.5 15V9.5H11.5C10.9477 9.5 10.5 9.05228 10.5 8.5V8C10.5 7.44772 10.9477 7 11.5 7H16Z"/>
|
|
680
724
|
</svg>`,
|
|
681
725
|
|
|
@@ -685,17 +729,17 @@ var ToolbarIcons = {
|
|
|
685
729
|
</svg>`,
|
|
686
730
|
|
|
687
731
|
"h2":
|
|
688
|
-
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
|
732
|
+
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
689
733
|
<path d="M8.12207 4.30078C8.84668 4.30078 9.25684 4.74512 9.25684 5.51758V12.5518C9.25677 13.3241 8.84662 13.7686 8.12207 13.7686C7.39752 13.7686 6.9942 13.3309 6.99414 12.5518V9.87207H3.56934V12.5518C3.56927 13.3241 3.15912 13.7686 2.43457 13.7686C1.71002 13.7686 1.3067 13.3309 1.30664 12.5518V5.51758C1.30664 4.73828 1.70996 4.30078 2.43457 4.30078C3.15918 4.30078 3.56934 4.74512 3.56934 5.51758V8.07422H6.99414V5.51758C6.99414 4.73828 7.39746 4.30078 8.12207 4.30078ZM13.6445 4.19824C15.5244 4.19824 16.8984 5.34668 16.8984 6.91211C16.8984 7.8759 16.4335 8.7237 15.292 9.84473L13.3438 11.8135V11.9092H16.1875C16.8232 11.9092 17.2197 12.251 17.2197 12.8115C17.2196 13.3651 16.83 13.7002 16.1875 13.7002H11.5117C10.8487 13.7002 10.4112 13.3241 10.4111 12.75C10.4111 12.3399 10.6368 11.9843 11.3203 11.3145L13.6855 8.88086C14.4169 8.13583 14.7245 7.64349 14.7246 7.12402C14.7246 6.4541 14.2393 6.00293 13.5215 6.00293C12.9404 6.00293 12.5166 6.29688 12.2158 6.90527C11.9151 7.37002 11.6552 7.54785 11.2588 7.54785C10.7188 7.54785 10.3429 7.17861 10.3428 6.65918C10.3428 5.3877 11.7783 4.19824 13.6445 4.19824Z"/>
|
|
690
734
|
</svg>`,
|
|
691
735
|
|
|
692
736
|
"h3":
|
|
693
|
-
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
|
737
|
+
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
694
738
|
<path d="M13.5967 4.19824C15.5928 4.19824 16.9873 5.23047 16.9873 6.7002C16.9872 7.705 16.2421 8.60059 15.2988 8.7168V8.86719C16.4199 8.94923 17.2607 9.89942 17.2607 11.0889C17.2606 12.7362 15.7362 13.9053 13.583 13.9053C11.6827 13.9053 10.1925 12.873 10.1924 11.7041C10.1924 11.1846 10.5547 10.8154 11.0537 10.8154C11.3818 10.8154 11.6553 10.9727 11.9629 11.3555C12.3799 11.9159 12.92 12.2031 13.583 12.2031C14.4853 12.2031 15.0731 11.7313 15.0732 11C15.0732 10.2754 14.4785 9.7832 13.5898 9.7832H13.0361C12.5645 9.7832 12.2159 9.4208 12.2158 8.92188C12.2158 8.44336 12.5576 8.07422 13.0361 8.07422H13.5693C14.3075 8.07422 14.8544 7.60928 14.8545 6.97363C14.8545 6.33789 14.3213 5.90039 13.5557 5.90039C12.9678 5.90039 12.5029 6.16016 12.0859 6.71387C11.8399 7.03508 11.5527 7.17871 11.1973 7.17871C10.671 7.17871 10.295 6.82314 10.2949 6.31738C10.2949 5.18945 11.751 4.19824 13.5967 4.19824ZM8.0332 4.30078C8.75781 4.30078 9.16797 4.74512 9.16797 5.51758V12.5518C9.1679 13.3241 8.75776 13.7686 8.0332 13.7686C7.30865 13.7686 6.90534 13.3309 6.90527 12.5518V9.87207H3.48047V12.5518C3.4804 13.3241 3.07026 13.7686 2.3457 13.7686C1.62115 13.7686 1.21784 13.3309 1.21777 12.5518V5.51758C1.21777 4.73828 1.62109 4.30078 2.3457 4.30078C3.07031 4.30078 3.48047 4.74512 3.48047 5.51758V8.07422H6.90527V5.51758C6.90527 4.73828 7.30859 4.30078 8.0332 4.30078Z"/>
|
|
695
739
|
</svg>`,
|
|
696
740
|
|
|
697
741
|
"h4":
|
|
698
|
-
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
|
742
|
+
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
699
743
|
<path d="M14.6357 4.22559C15.7432 4.22559 16.4336 4.80664 16.4336 5.73633V10.3164H16.7275C17.2881 10.3164 17.6436 10.6787 17.6436 11.2256C17.6435 11.7655 17.3017 12.1006 16.7275 12.1006H16.4336V12.6611C16.4335 13.3515 16.0234 13.7891 15.374 13.7891C14.7247 13.7891 14.3282 13.3583 14.3281 12.6611V12.1006H11.04C10.2335 12.1006 9.76863 11.6766 9.76855 10.918C9.76855 10.5762 9.85064 10.3026 10.1104 9.74219C10.7666 8.42289 11.5733 7.0146 12.5713 5.54492C13.2549 4.56738 13.7812 4.22559 14.6357 4.22559ZM7.88965 4.30078C8.61426 4.30078 9.02441 4.74512 9.02441 5.51758V12.5518C9.02435 13.3241 8.6142 13.7686 7.88965 13.7686C7.1651 13.7686 6.76178 13.3309 6.76172 12.5518V9.87207H3.33691V12.5518C3.33685 13.3241 2.9267 13.7686 2.20215 13.7686C1.4776 13.7686 1.07428 13.3309 1.07422 12.5518V5.51758C1.07422 4.73828 1.47754 4.30078 2.20215 4.30078C2.92676 4.30078 3.33691 4.74512 3.33691 5.51758V8.07422H6.76172V5.51758C6.76172 4.73828 7.16504 4.30078 7.88965 4.30078ZM14.2188 6.07812C13.6035 7.02841 12.2158 9.48929 11.7988 10.2686V10.3164H14.3281V6.07812H14.2188Z"/>
|
|
700
744
|
</svg>`,
|
|
701
745
|
|
|
@@ -710,44 +754,44 @@ var ToolbarIcons = {
|
|
|
710
754
|
</svg>`,
|
|
711
755
|
|
|
712
756
|
"paragraph":
|
|
713
|
-
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
|
757
|
+
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
714
758
|
<path d="M9 12C9.55228 12 10 12.4477 10 13C10 13.5523 9.55228 14 9 14H3C2.44772 14 2 13.5523 2 13C2 12.4477 2.44772 12 3 12H9ZM15 8C15.5523 8 16 8.44772 16 9C16 9.55228 15.5523 10 15 10H3C2.44772 10 2 9.55228 2 9C2 8.44772 2.44772 8 3 8H15ZM15 4C15.5523 4 16 4.44772 16 5C16 5.55228 15.5523 6 15 6H3C2.44772 6 2 5.55228 2 5C2 4.44772 2.44772 4 3 4H15Z"/>
|
|
715
759
|
</svg>`,
|
|
716
760
|
|
|
717
761
|
"clearFormatting":
|
|
718
|
-
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
|
762
|
+
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
719
763
|
<path d="M10.0607 2.07533C10.8417 1.29432 12.1078 1.2943 12.8888 2.07533L16.424 5.61049C17.205 6.39154 17.205 7.65854 16.424 8.43959L9.44937 15.4142C9.07435 15.7891 8.5656 16.0001 8.03531 16.0001H5.0148C4.55074 16.0001 4.10309 15.8385 3.74722 15.547L3.60074 15.4142L1.57534 13.3888C0.79431 12.6078 0.794336 11.3417 1.57534 10.5607L10.0607 2.07533ZM2.98941 11.9747L5.0148 14.0001H8.03531L9.71792 12.3165L6.18179 8.78139L2.98941 11.9747Z"/>
|
|
720
764
|
</svg>`,
|
|
721
765
|
|
|
722
766
|
"highlight":
|
|
723
|
-
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
|
767
|
+
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
724
768
|
<path d="M16.4564 14.4272C17.1356 15.5592 16.3204 17.0002 15.0003 17.0004C13.68 17.0004 12.864 15.5593 13.5433 14.4272L15.0003 12.0004L16.4564 14.4272ZM5.1214 1.70746C5.51192 1.31693 6.14494 1.31693 6.53546 1.70746L9.7171 4.8891L13.2532 8.42426C14.2295 9.40056 14.2295 10.9841 13.2532 11.9604L9.7171 15.4955C8.74078 16.4718 7.15822 16.4718 6.18195 15.4955L2.64679 11.9604C1.67048 10.9841 1.67048 9.40057 2.64679 8.42426L6.18195 4.8891C6.30299 4.76805 6.43323 4.66177 6.57062 4.57074L5.1214 3.12152C4.73091 2.73104 4.73099 2.09799 5.1214 1.70746ZM8.30304 6.30316C8.10776 6.10815 7.79119 6.10799 7.59601 6.30316L4.06085 9.83929L3.9964 9.91742C3.88661 10.0838 3.88645 10.3019 3.9964 10.4682L4.02277 10.5004H11.8763C12.0312 10.3043 12.02 10.0205 11.8392 9.83929L8.30304 6.30316Z"/>
|
|
725
769
|
</svg>`,
|
|
726
770
|
|
|
727
771
|
"link":
|
|
728
|
-
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
|
772
|
+
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
729
773
|
<path d="M12.8885 7.23091L13.9479 6.17155C14.5337 5.58576 14.5337 4.63602 13.9479 4.05023C13.3621 3.46444 12.4124 3.46444 11.8266 4.05023L8.29235 7.58446C7.9263 7.95051 7.90312 8.52994 8.2233 8.92271L8.36141 9.07463C8.68158 9.4674 8.65841 10.0468 8.29235 10.4129C7.90183 10.8034 7.26866 10.8034 6.87814 10.4129C5.70657 9.24131 5.70657 7.34182 6.87814 6.17025L10.4124 2.63602C11.7792 1.26918 13.9953 1.26918 15.3621 2.63602C16.729 4.00285 16.729 6.21893 15.3621 7.58576L14.3028 8.64512C13.9122 9.03564 13.2791 9.03564 12.8885 8.64512C12.498 8.2546 12.498 7.62143 12.8885 7.23091Z"/>
|
|
730
774
|
<path d="M5.11038 10.7664L4.04843 11.8284C3.46264 12.4142 3.46264 13.3639 4.04842 13.9497C4.63421 14.5355 5.58396 14.5355 6.16975 13.9497L9.70657 10.4129C10.0726 10.0468 10.0958 9.46741 9.77563 9.07464L9.63752 8.92272C9.31734 8.52995 9.34052 7.95052 9.70657 7.58446C10.0971 7.19394 10.7303 7.19394 11.1208 7.58446C12.2924 8.75604 12.2924 10.6555 11.1208 11.8271L7.58396 15.3639C6.21712 16.7308 4.00105 16.7308 2.63421 15.3639C1.26738 13.9971 1.26738 11.781 2.63421 10.4142L3.69617 9.35223C4.08669 8.96171 4.71986 8.96171 5.11038 9.35223C5.5009 9.74275 5.5009 10.3759 5.11038 10.7664Z"/>
|
|
731
775
|
</svg>`,
|
|
732
776
|
|
|
733
777
|
"quote":
|
|
734
|
-
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
|
778
|
+
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
735
779
|
<path d="M4.96387 4.23438C6.8769 4.23438 8.42767 5.78522 8.42773 7.69824C8.42773 8.32925 8.25769 8.92015 7.96289 9.42969L7.96387 9.43066L5.11816 14.3584C4.77659 14.95 4.02038 15.153 3.42871 14.8115C2.83701 14.4699 2.63397 13.7128 2.97559 13.1211L4.16113 11.0674C2.63532 10.7052 1.5 9.33485 1.5 7.69824C1.50006 5.78524 3.05086 4.2344 4.96387 4.23438ZM13.0361 4.23438C14.9491 4.23449 16.4999 5.7853 16.5 7.69824C16.5 8.32921 16.3299 8.92017 16.0352 9.42969L16.0361 9.43066L13.1904 14.3584C12.8488 14.9501 12.0917 15.1531 11.5 14.8115C10.9085 14.4698 10.7063 13.7127 11.0479 13.1211L12.2324 11.0674C10.7069 10.7049 9.57227 9.33461 9.57227 7.69824C9.57233 5.78522 11.1231 4.23438 13.0361 4.23438Z"/>
|
|
736
780
|
</svg>`,
|
|
737
781
|
|
|
738
782
|
"code":
|
|
739
|
-
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
|
783
|
+
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
740
784
|
<path d="M6.29289 3.79295C6.68342 3.40243 7.31643 3.40243 7.70696 3.79295C8.09748 4.18348 8.09748 4.81649 7.70696 5.20702L3.91399 8.99999L7.70696 12.793C8.09748 13.1835 8.09748 13.8165 7.70696 14.207C7.31643 14.5975 6.68342 14.5975 6.29289 14.207L1.79289 9.70702C1.40237 9.31649 1.40237 8.68348 1.79289 8.29295L6.29289 3.79295Z"/>
|
|
741
785
|
<path d="M11.707 3.79295C11.3164 3.40243 10.6834 3.40243 10.2929 3.79295C9.90237 4.18348 9.90237 4.81649 10.2929 5.20702L14.0859 8.99999L10.2929 12.793C9.90237 13.1835 9.90237 13.8165 10.2929 14.207C10.6834 14.5975 11.3164 14.5975 11.707 14.207L16.207 9.70702C16.5975 9.31649 16.5975 8.68348 16.207 8.29295L11.707 3.79295Z"/>
|
|
742
786
|
</svg>`,
|
|
743
787
|
|
|
744
788
|
"ul":
|
|
745
|
-
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
|
789
|
+
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
746
790
|
<path d="M3 12.5C3.82843 12.5 4.5 13.1716 4.5 14C4.5 14.8284 3.82843 15.5 3 15.5C2.17157 15.5 1.5 14.8284 1.5 14C1.5 13.1716 2.17157 12.5 3 12.5ZM15.5 13C16.0523 13 16.5 13.4477 16.5 14C16.5 14.5523 16.0523 15 15.5 15H7C6.44772 15 6 14.5523 6 14C6 13.4477 6.44772 13 7 13H15.5ZM3 7.5C3.82843 7.5 4.5 8.17157 4.5 9C4.5 9.82843 3.82843 10.5 3 10.5C2.17157 10.5 1.5 9.82843 1.5 9C1.5 8.17157 2.17157 7.5 3 7.5ZM15.5 8C16.0523 8 16.5 8.44772 16.5 9C16.5 9.55228 16.0523 10 15.5 10H7C6.44772 10 6 9.55228 6 9C6 8.44772 6.44772 8 7 8H15.5ZM3 2.5C3.82843 2.5 4.5 3.17157 4.5 4C4.5 4.82843 3.82843 5.5 3 5.5C2.17157 5.5 1.5 4.82843 1.5 4C1.5 3.17157 2.17157 2.5 3 2.5ZM15.5 3C16.0523 3 16.5 3.44772 16.5 4C16.5 4.55228 16.0523 5 15.5 5H7C6.44772 5 6 4.55228 6 4C6 3.44772 6.44772 3 7 3H15.5Z"/>
|
|
747
791
|
</svg>`,
|
|
748
792
|
|
|
749
793
|
"ol":
|
|
750
|
-
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
|
794
|
+
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
751
795
|
<path d="M15.5 13C16.0523 13 16.5 13.4477 16.5 14C16.5 14.5523 16.0523 15 15.5 15H7C6.44772 15 6 14.5523 6 14C6 13.4477 6.44772 13 7 13H15.5ZM15.5 8C16.0523 8 16.5 8.44772 16.5 9C16.5 9.55228 16.0523 10 15.5 10H7C6.44772 10 6 9.55228 6 9C6 8.44772 6.44772 8 7 8H15.5ZM15.5 3C16.0523 3 16.5 3.44772 16.5 4C16.5 4.55228 16.0523 5 15.5 5H7C6.44772 5 6 4.55228 6 4C6 3.44772 6.44772 3 7 3H15.5Z"/>
|
|
752
796
|
<path d="M2.98657 16.0967C2.68042 16.0967 2.41187 16.0465 2.18091 15.9463C1.95174 15.846 1.77002 15.7046 1.63574 15.522C1.50146 15.3376 1.42448 15.1227 1.40479 14.8774L1.4021 14.8452H2.34204L2.34741 14.8748C2.35815 14.9589 2.39038 15.035 2.44409 15.103C2.49959 15.1711 2.5721 15.2248 2.66162 15.2642C2.75293 15.3035 2.86035 15.3232 2.98389 15.3232C3.10563 15.3232 3.21037 15.3027 3.2981 15.2615C3.38761 15.2185 3.45654 15.1603 3.50488 15.0869C3.55322 15.0135 3.57739 14.9294 3.57739 14.8345V14.8291C3.57739 14.6715 3.51921 14.5516 3.40283 14.4692C3.28646 14.3869 3.12085 14.3457 2.90601 14.3457H2.48706V13.677H2.90063C3.02775 13.677 3.13607 13.6582 3.22559 13.6206C3.31689 13.583 3.38672 13.5302 3.43506 13.4622C3.48519 13.3941 3.51025 13.3153 3.51025 13.2258V13.2205C3.51025 13.1256 3.48877 13.0441 3.4458 12.9761C3.40462 12.9062 3.34375 12.8534 3.26318 12.8176C3.18441 12.78 3.08952 12.7612 2.97852 12.7612C2.86572 12.7612 2.76636 12.7809 2.68042 12.8203C2.59627 12.8579 2.52913 12.9125 2.479 12.9841C2.43066 13.054 2.40112 13.1363 2.39038 13.2312L2.3877 13.2581H1.49341L1.49609 13.2205C1.514 12.977 1.58561 12.7666 1.71094 12.5894C1.83805 12.4103 2.00903 12.2725 2.22388 12.1758C2.44051 12.0773 2.69206 12.0281 2.97852 12.0281C3.27393 12.0281 3.52995 12.0728 3.74658 12.1624C3.96322 12.2501 4.13062 12.3727 4.24878 12.5303C4.36694 12.6878 4.42603 12.8722 4.42603 13.0835V13.0889C4.42603 13.2518 4.38932 13.3941 4.31592 13.5159C4.2443 13.6358 4.14762 13.7343 4.02588 13.8113C3.90592 13.8883 3.77254 13.942 3.62573 13.9724V13.9912C3.91756 14.0199 4.14941 14.1121 4.32129 14.2678C4.49316 14.4236 4.5791 14.6295 4.5791 14.8855V14.8909C4.5791 15.1344 4.51375 15.3474 4.38306 15.53C4.25236 15.7109 4.06795 15.8505 3.82983 15.949C3.59172 16.0474 3.31063 16.0967 2.98657 16.0967Z"/>
|
|
753
797
|
<path d="M1.54443 11V10.342L2.76099 9.20874C2.95076 9.03507 3.09757 8.89274 3.20142 8.78174C3.30705 8.66895 3.37956 8.57316 3.41895 8.49438C3.46012 8.41382 3.48071 8.33415 3.48071 8.25537V8.24463C3.48071 8.14795 3.46012 8.0638 3.41895 7.99219C3.37777 7.92057 3.31779 7.86507 3.23901 7.82568C3.16024 7.7863 3.06714 7.7666 2.95972 7.7666C2.84692 7.7666 2.74756 7.78988 2.66162 7.83643C2.57747 7.88298 2.51123 7.94743 2.46289 8.02979C2.41455 8.11035 2.39038 8.20345 2.39038 8.30908V8.33057L1.48804 8.32788V8.31177C1.48804 8.05396 1.5507 7.82837 1.67603 7.63501C1.80314 7.44165 1.97949 7.29126 2.20508 7.18384C2.43245 7.07463 2.69653 7.02002 2.99731 7.02002C3.28556 7.02002 3.53711 7.06836 3.75195 7.16504C3.96859 7.25993 4.13688 7.39331 4.25684 7.56519C4.37858 7.73706 4.43945 7.93758 4.43945 8.16675V8.18018C4.43945 8.3252 4.40902 8.46932 4.34814 8.61255C4.28727 8.75578 4.18701 8.90885 4.04736 9.07178C3.90771 9.23291 3.71883 9.41642 3.48071 9.62231L2.58374 10.4092L2.85498 9.98486V10.4092L2.58374 10.2319H4.49048V11H1.54443Z"/>
|
|
@@ -755,37 +799,37 @@ var ToolbarIcons = {
|
|
|
755
799
|
</svg>`,
|
|
756
800
|
|
|
757
801
|
"image":
|
|
758
|
-
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
|
802
|
+
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
759
803
|
<path d="M14 2C15.6569 2 17 3.34315 17 5V13C17 14.6569 15.6569 16 14 16H4C2.34315 16 1 14.6569 1 13V5C1 3.34315 2.34315 2 4 2H14ZM3.06348 13.3496C3.2053 13.7294 3.57078 14 4 14H13.5859L11 11.4141L9.70703 12.707C9.31651 13.0976 8.68349 13.0976 8.29297 12.707C7.90244 12.3165 7.90244 11.6835 8.29297 11.293L8.58594 11L7 9.41406L3.06348 13.3496ZM4 4C3.44772 4 3 4.44772 3 5V10.5859L6.29297 7.29297L6.36914 7.22461C6.76191 6.90427 7.34092 6.92686 7.70703 7.29297L10 9.58594L10.293 9.29297L10.3691 9.22461C10.7619 8.90427 11.3409 8.92686 11.707 9.29297L15 12.5859V5C15 4.44772 14.5523 4 14 4H4ZM12.5 5C13.3284 5 14 5.67157 14 6.5C14 7.32843 13.3284 8 12.5 8C11.6716 8 11 7.32843 11 6.5C11 5.67157 11.6716 5 12.5 5Z"/>
|
|
760
804
|
</svg>`,
|
|
761
805
|
|
|
762
806
|
"attachment":
|
|
763
|
-
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
|
807
|
+
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
764
808
|
<path d="M13 13.5V6C13 4.067 11.433 2.5 9.5 2.5C7.567 2.5 6 4.067 6 6V13.5C6 14.6046 6.89543 15.5 8 15.5H8.23047C9.20759 15.5 10 14.7076 10 13.7305V7C10 6.72386 9.77614 6.5 9.5 6.5C9.22386 6.5 9 6.72386 9 7V12.5C9 13.0523 8.55228 13.5 8 13.5C7.44772 13.5 7 13.0523 7 12.5V7C7 5.61929 8.11929 4.5 9.5 4.5C10.8807 4.5 12 5.61929 12 7V13.7305C12 15.8122 10.3122 17.5 8.23047 17.5H8C5.79086 17.5 4 15.7091 4 13.5V6C4 2.96243 6.46243 0.5 9.5 0.5C12.5376 0.5 15 2.96243 15 6V13.5C15 14.0523 14.5523 14.5 14 14.5C13.4477 14.5 13 14.0523 13 13.5Z"/>
|
|
765
809
|
</svg>`,
|
|
766
810
|
|
|
767
811
|
"table":
|
|
768
|
-
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
|
812
|
+
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
769
813
|
<path d="M15 1C16.1046 1 17 1.89543 17 3V15C17 16.1046 16.1046 17 15 17H3C1.89543 17 1 16.1046 1 15V3C1 1.89543 1.89543 1 3 1H15ZM3 15H8V10H3V15ZM10 10V15H15V10H10ZM10 8H15V3H10V8ZM3 8H8V3H3V8Z"/>
|
|
770
814
|
</svg>`,
|
|
771
815
|
|
|
772
816
|
"hr":
|
|
773
|
-
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
|
817
|
+
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
774
818
|
<path d="M12.75 12C13.1642 12 13.5 12.3358 13.5 12.75V14.25C13.5 14.6642 13.1642 15 12.75 15H5.25C4.83579 15 4.5 14.6642 4.5 14.25V12.75C4.5 12.3358 4.83579 12 5.25 12H12.75ZM15.4863 8C16.0461 8 16.5 8.44771 16.5 9C16.5 9.55229 16.0461 10 15.4863 10H2.51367C1.95392 10 1.5 9.55229 1.5 9C1.5 8.44771 1.95392 8 2.51367 8H15.4863ZM12.75 3C13.1642 3 13.5 3.33579 13.5 3.75V5.25C13.5 5.66421 13.1642 6 12.75 6H5.25C4.83579 6 4.5 5.66421 4.5 5.25V3.75C4.5 3.33579 4.83579 3 5.25 3H12.75Z"/>
|
|
775
819
|
</svg>`,
|
|
776
820
|
|
|
777
821
|
"undo":
|
|
778
|
-
`<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
822
|
+
`<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
779
823
|
<path d="M8.36612 5.36612C8.85427 4.87796 9.64554 4.87796 10.1337 5.36612C10.6218 5.85428 10.6218 6.64557 10.1337 7.13369L7.26748 9.9999H15.2499C18.1494 9.99996 20.4999 12.3504 20.4999 15.2499V19.2499C20.4999 19.9402 19.9402 20.4999 19.2499 20.4999C18.5596 20.4999 18 19.9402 17.9999 19.2499V15.2499C17.9999 13.7312 16.7686 12.5 15.2499 12.4999H7.26748L10.1337 15.3661C10.6218 15.8543 10.6218 16.6456 10.1337 17.1337C9.64557 17.6218 8.85428 17.6218 8.36612 17.1337L3.36612 12.1337C2.87796 11.6455 2.87796 10.8543 3.36612 10.3661L8.36612 5.36612Z"/>
|
|
780
824
|
</svg>`,
|
|
781
825
|
|
|
782
826
|
"redo":
|
|
783
|
-
`<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
827
|
+
`<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
784
828
|
<path d="M15.6338 5.1163C15.1456 4.62814 14.3543 4.62814 13.8662 5.1163C13.3781 5.60446 13.3781 6.39575 13.8662 6.88388L16.7324 9.75009H8.74997C5.85052 9.75014 3.49997 12.1006 3.49997 15.0001V19.0001C3.50002 19.6904 4.05969 20.25 4.74997 20.2501C5.4403 20.2501 5.99992 19.6904 5.99997 19.0001V15.0001C5.99997 13.4813 7.23123 12.2501 8.74997 12.2501H16.7324L13.8662 15.1163C13.3781 15.6045 13.3781 16.3958 13.8662 16.8839C14.3543 17.372 15.1456 17.3719 15.6338 16.8839L20.6338 11.8839C21.1219 11.3957 21.1219 10.6045 20.6338 10.1163L15.6338 5.1163Z" />
|
|
785
829
|
</svg>`,
|
|
786
830
|
|
|
787
831
|
"overflow":
|
|
788
|
-
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
|
832
|
+
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
789
833
|
<path d="M3 6.75C4.24264 6.75 5.25 7.75736 5.25 9C5.25 10.2426 4.24264 11.25 3 11.25C1.75736 11.25 0.75 10.2426 0.75 9C0.75 7.75736 1.75736 6.75 3 6.75ZM9 6.75C10.2426 6.75 11.25 7.75736 11.25 9C11.25 10.2426 10.2426 11.25 9 11.25C7.75736 11.25 6.75 10.2426 6.75 9C6.75 7.75736 7.75736 6.75 9 6.75ZM15 6.75C16.2426 6.75 17.25 7.75736 17.25 9C17.25 10.2426 16.2426 11.25 15 11.25C13.7574 11.25 12.75 10.2426 12.75 9C12.75 7.75736 13.7574 6.75 15 6.75Z"/>
|
|
790
834
|
</svg>`
|
|
791
835
|
};
|
|
@@ -912,14 +956,21 @@ class LexicalToolbarElement extends HTMLElement {
|
|
|
912
956
|
}
|
|
913
957
|
}
|
|
914
958
|
|
|
915
|
-
#dispatchButtonCommand(event,
|
|
916
|
-
|
|
959
|
+
#dispatchButtonCommand(event, button) {
|
|
960
|
+
if (button.ariaDisabled === "true") return
|
|
961
|
+
|
|
962
|
+
const { command, payload } = button.dataset;
|
|
963
|
+
const shouldKeepToolbarFocus = isKeyboardActivation(event) && !this.#belongsToDropdown(button);
|
|
917
964
|
|
|
918
965
|
this.editor.update(() => {
|
|
919
966
|
this.editor.dispatchCommand(command, payload);
|
|
920
|
-
}, { tag:
|
|
967
|
+
}, { tag: shouldKeepToolbarFocus ? SKIP_DOM_SELECTION_TAG : undefined });
|
|
921
968
|
|
|
922
|
-
if (!
|
|
969
|
+
if (!shouldKeepToolbarFocus) this.editor.focus();
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
#belongsToDropdown(button) {
|
|
973
|
+
return button.closest("[data-dropdown-panel]") != null
|
|
923
974
|
}
|
|
924
975
|
|
|
925
976
|
#bindHotkeys() {
|
|
@@ -959,8 +1010,8 @@ class LexicalToolbarElement extends HTMLElement {
|
|
|
959
1010
|
}
|
|
960
1011
|
|
|
961
1012
|
#handleEditorFocus = () => {
|
|
962
|
-
const
|
|
963
|
-
if (
|
|
1013
|
+
const firstVisibleButton = this.#toolbarButtons.find(isActiveAndVisible);
|
|
1014
|
+
if (firstVisibleButton) firstVisibleButton.tabIndex = 0;
|
|
964
1015
|
}
|
|
965
1016
|
|
|
966
1017
|
#handleEditorBlur = () => {
|
|
@@ -968,11 +1019,11 @@ class LexicalToolbarElement extends HTMLElement {
|
|
|
968
1019
|
}
|
|
969
1020
|
|
|
970
1021
|
#handleKeydown = (event) => {
|
|
971
|
-
handleRollingTabIndex(this.#
|
|
1022
|
+
handleRollingTabIndex(this.#toolbarButtons, event);
|
|
972
1023
|
}
|
|
973
1024
|
|
|
974
1025
|
#resetTabIndexValues() {
|
|
975
|
-
this.#
|
|
1026
|
+
this.#toolbarButtons.forEach((button) => {
|
|
976
1027
|
button.tabIndex = -1;
|
|
977
1028
|
});
|
|
978
1029
|
}
|
|
@@ -1009,7 +1060,7 @@ class LexicalToolbarElement extends HTMLElement {
|
|
|
1009
1060
|
this.#setButtonPressed("underline", isUnderline);
|
|
1010
1061
|
|
|
1011
1062
|
this.#setButtonPressed("format", isInHeading);
|
|
1012
|
-
this.#
|
|
1063
|
+
this.#setButtonChecked("paragraph", !isInHeading);
|
|
1013
1064
|
|
|
1014
1065
|
this.querySelector("lexxy-heading-dropdown")
|
|
1015
1066
|
?.updateActiveHeading(headingTag);
|
|
@@ -1028,25 +1079,17 @@ class LexicalToolbarElement extends HTMLElement {
|
|
|
1028
1079
|
|
|
1029
1080
|
#setButtonPressed(name, isPressed) {
|
|
1030
1081
|
const button = this.querySelector(`[name="${name}"]`);
|
|
1031
|
-
if (button)
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1082
|
+
if (button) button.ariaPressed = isPressed;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
#setButtonChecked(name, isChecked) {
|
|
1086
|
+
const button = this.querySelector(`[name="${name}"]`);
|
|
1087
|
+
if (button) button.ariaChecked = isChecked;
|
|
1037
1088
|
}
|
|
1038
1089
|
|
|
1039
1090
|
#setButtonDisabled(name, isDisabled) {
|
|
1040
1091
|
const button = this.querySelector(`[name="${name}"]`);
|
|
1041
|
-
if (button)
|
|
1042
|
-
if (button.disabled !== isDisabled) {
|
|
1043
|
-
button.disabled = isDisabled;
|
|
1044
|
-
}
|
|
1045
|
-
const next = isDisabled.toString();
|
|
1046
|
-
if (button.getAttribute("aria-disabled") !== next) {
|
|
1047
|
-
button.setAttribute("aria-disabled", next);
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
1092
|
+
if (button) button.ariaDisabled = isDisabled;
|
|
1050
1093
|
}
|
|
1051
1094
|
|
|
1052
1095
|
#refreshOverflow() {
|
|
@@ -1147,8 +1190,10 @@ class LexicalToolbarElement extends HTMLElement {
|
|
|
1147
1190
|
return Array.from(this.querySelectorAll(":scope > button:not([data-prevent-overflow])"))
|
|
1148
1191
|
}
|
|
1149
1192
|
|
|
1150
|
-
get #
|
|
1151
|
-
return Array.from(this.querySelectorAll(":scope button"))
|
|
1193
|
+
get #toolbarButtons() {
|
|
1194
|
+
return Array.from(this.querySelectorAll(":scope button")).filter((button) => {
|
|
1195
|
+
return !button.closest("[data-dropdown-panel]")
|
|
1196
|
+
})
|
|
1152
1197
|
}
|
|
1153
1198
|
|
|
1154
1199
|
get #toolbarItems() {
|
|
@@ -1187,8 +1232,8 @@ class LexicalToolbarElement extends HTMLElement {
|
|
|
1187
1232
|
<button data-dropdown-trigger class="lexxy-editor__toolbar-button lexxy-editor__toolbar-button--chevron" type="button" name="format" title="Text formatting" aria-haspopup="menu" aria-expanded="false">
|
|
1188
1233
|
${ToolbarIcons.heading}
|
|
1189
1234
|
</button>
|
|
1190
|
-
<div data-dropdown-panel role="menu" class="lexxy-editor__toolbar-dropdown-list" hidden>
|
|
1191
|
-
<button type="button" name="paragraph" data-command="setFormatParagraph" title="Paragraph" role="
|
|
1235
|
+
<div data-dropdown-panel role="menu" aria-label="Text formatting" class="lexxy-editor__toolbar-dropdown-list" hidden>
|
|
1236
|
+
<button type="button" name="paragraph" data-command="setFormatParagraph" title="Paragraph" role="menuitemradio">
|
|
1192
1237
|
${ToolbarIcons.paragraph} <span>Normal</span>
|
|
1193
1238
|
</button>
|
|
1194
1239
|
<lexxy-heading-dropdown>
|
|
@@ -1205,7 +1250,7 @@ class LexicalToolbarElement extends HTMLElement {
|
|
|
1205
1250
|
<button data-dropdown-trigger class="lexxy-editor__toolbar-button lexxy-editor__toolbar-button--chevron" type="button" name="highlight" title="Color highlight" aria-haspopup="menu" aria-expanded="false">
|
|
1206
1251
|
${ToolbarIcons.highlight}
|
|
1207
1252
|
</button>
|
|
1208
|
-
<div data-dropdown-panel role="menu" hidden>
|
|
1253
|
+
<div data-dropdown-panel role="menu" aria-label="Color highlight" hidden>
|
|
1209
1254
|
<div class="lexxy-highlight-colors"></div>
|
|
1210
1255
|
<button data-command="removeHighlight" type="button" class="lexxy-editor__toolbar-button lexxy-editor__toolbar-dropdown-reset" role="menuitem">Remove all coloring</button>
|
|
1211
1256
|
</div>
|
|
@@ -1247,19 +1292,19 @@ class LexicalToolbarElement extends HTMLElement {
|
|
|
1247
1292
|
${ToolbarIcons.hr}
|
|
1248
1293
|
</button>
|
|
1249
1294
|
|
|
1250
|
-
<button class="lexxy-editor__toolbar-button lexxy-editor__toolbar-button--push-right" type="button" name="undo" data-command="undo" title="Undo"
|
|
1295
|
+
<button class="lexxy-editor__toolbar-button lexxy-editor__toolbar-button--push-right" type="button" name="undo" data-command="undo" title="Undo" aria-disabled="true">
|
|
1251
1296
|
${ToolbarIcons.undo}
|
|
1252
1297
|
</button>
|
|
1253
1298
|
|
|
1254
|
-
<button class="lexxy-editor__toolbar-button" type="button" name="redo" data-command="redo" title="Redo"
|
|
1299
|
+
<button class="lexxy-editor__toolbar-button" type="button" name="redo" data-command="redo" title="Redo" aria-disabled="true">
|
|
1255
1300
|
${ToolbarIcons.redo}
|
|
1256
1301
|
</button>
|
|
1257
1302
|
|
|
1258
1303
|
<lexxy-toolbar-dropdown class="lexxy-editor__toolbar-dropdown lexxy-editor__toolbar-button--push-right lexxy-editor__toolbar-overflow">
|
|
1259
|
-
<button data-dropdown-trigger class="lexxy-editor__toolbar-button" type="button" aria-haspopup="menu" aria-expanded="false"
|
|
1304
|
+
<button data-dropdown-trigger class="lexxy-editor__toolbar-button" type="button" title="More options" aria-haspopup="menu" aria-expanded="false">
|
|
1260
1305
|
${ToolbarIcons.overflow}
|
|
1261
1306
|
</button>
|
|
1262
|
-
<div data-dropdown-panel role="menu" class="lexxy-editor__toolbar-overflow-menu" aria-label="More
|
|
1307
|
+
<div data-dropdown-panel role="menu" class="lexxy-editor__toolbar-overflow-menu" aria-label="More options" hidden></div>
|
|
1263
1308
|
</lexxy-toolbar-dropdown>
|
|
1264
1309
|
`
|
|
1265
1310
|
}
|
|
@@ -1303,9 +1348,10 @@ function nextFrame() {
|
|
|
1303
1348
|
|
|
1304
1349
|
class ToolbarDropdown extends HTMLElement {
|
|
1305
1350
|
#listeners = new ListenerBin()
|
|
1351
|
+
#shouldReturnFocusToTrigger = false
|
|
1306
1352
|
|
|
1307
1353
|
connectedCallback() {
|
|
1308
|
-
this.#
|
|
1354
|
+
this.#onHostEditor(() => {
|
|
1309
1355
|
this.#registerListeners();
|
|
1310
1356
|
this.editorReady();
|
|
1311
1357
|
});
|
|
@@ -1327,16 +1373,16 @@ class ToolbarDropdown extends HTMLElement {
|
|
|
1327
1373
|
return this.querySelector(":scope > [data-dropdown-panel]")
|
|
1328
1374
|
}
|
|
1329
1375
|
|
|
1330
|
-
get
|
|
1331
|
-
return this.closest("lexxy-toolbar")
|
|
1376
|
+
get host() {
|
|
1377
|
+
return this.closest("lexxy-toolbar, lexxy-table-tools")
|
|
1332
1378
|
}
|
|
1333
1379
|
|
|
1334
1380
|
get editorElement() {
|
|
1335
|
-
return this.
|
|
1381
|
+
return this.host?.editorElement
|
|
1336
1382
|
}
|
|
1337
1383
|
|
|
1338
1384
|
get editor() {
|
|
1339
|
-
return this.
|
|
1385
|
+
return this.host?.editor
|
|
1340
1386
|
}
|
|
1341
1387
|
|
|
1342
1388
|
get isOpen() {
|
|
@@ -1353,7 +1399,7 @@ class ToolbarDropdown extends HTMLElement {
|
|
|
1353
1399
|
|
|
1354
1400
|
open() {
|
|
1355
1401
|
if (this.isOpen) return
|
|
1356
|
-
this.trigger.
|
|
1402
|
+
this.trigger.ariaExpanded = true;
|
|
1357
1403
|
this.panel.hidden = false;
|
|
1358
1404
|
this.onOpen();
|
|
1359
1405
|
this.#focusFirstInteractive();
|
|
@@ -1363,7 +1409,7 @@ class ToolbarDropdown extends HTMLElement {
|
|
|
1363
1409
|
if (focusEditor) this.editor?.focus();
|
|
1364
1410
|
|
|
1365
1411
|
if (this.isClosed) return
|
|
1366
|
-
this.trigger.
|
|
1412
|
+
this.trigger.ariaExpanded = false;
|
|
1367
1413
|
this.panel.hidden = true;
|
|
1368
1414
|
this.onClose();
|
|
1369
1415
|
}
|
|
@@ -1375,31 +1421,46 @@ class ToolbarDropdown extends HTMLElement {
|
|
|
1375
1421
|
);
|
|
1376
1422
|
}
|
|
1377
1423
|
|
|
1378
|
-
#handleTriggerClick = () => {
|
|
1424
|
+
#handleTriggerClick = (event) => {
|
|
1379
1425
|
if (this.isOpen) {
|
|
1380
1426
|
this.close({ focusEditor: false });
|
|
1381
1427
|
} else {
|
|
1382
|
-
this
|
|
1428
|
+
this.#shouldReturnFocusToTrigger = this.#isOpenedFromHost(event);
|
|
1429
|
+
this.host?.closeDropdowns({ except: this });
|
|
1383
1430
|
this.open();
|
|
1384
1431
|
}
|
|
1385
1432
|
}
|
|
1386
1433
|
|
|
1387
|
-
|
|
1388
|
-
|
|
1434
|
+
#isOpenedFromHost(event) {
|
|
1435
|
+
return isKeyboardActivation(event) && this.host?.contains(document.activeElement)
|
|
1436
|
+
}
|
|
1389
1437
|
|
|
1390
|
-
|
|
1391
|
-
if (this.
|
|
1438
|
+
async #onHostEditor(callback) {
|
|
1439
|
+
if (!this.host) return
|
|
1440
|
+
|
|
1441
|
+
await this.host.getEditorElement();
|
|
1442
|
+
if (this.isConnected && this.host) callback();
|
|
1392
1443
|
}
|
|
1393
1444
|
|
|
1394
1445
|
#handleKeyDown = (event) => {
|
|
1395
1446
|
if (event.key === "Escape") {
|
|
1396
1447
|
event.stopPropagation();
|
|
1397
|
-
this.close();
|
|
1448
|
+
this.close({ focusEditor: !this.#shouldReturnFocusToTrigger });
|
|
1449
|
+
if (this.#shouldReturnFocusToTrigger) this.trigger?.focus();
|
|
1450
|
+
} else if (this.#isNavigatingMenu(event)) {
|
|
1451
|
+
event.stopPropagation();
|
|
1452
|
+
handleRollingTabIndex(this.#buttons, event, { orientation: "both", wrap: true });
|
|
1398
1453
|
}
|
|
1399
1454
|
}
|
|
1400
1455
|
|
|
1456
|
+
#isNavigatingMenu(event) {
|
|
1457
|
+
return this.panel.role === "menu" && this.panel.contains(event.target)
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1401
1460
|
async #focusFirstInteractive() {
|
|
1402
|
-
|
|
1461
|
+
// Ask for the ring explicitly: opening the menu with the mouse otherwise leaves the
|
|
1462
|
+
// first item focused without a focus ring, since focus() inherits the mouse modality.
|
|
1463
|
+
this.#interactiveElements[0]?.focus({ focusVisible: true });
|
|
1403
1464
|
await this.#resetTabIndexValues();
|
|
1404
1465
|
}
|
|
1405
1466
|
|
|
@@ -1482,8 +1543,8 @@ class HeadingDropdown extends HTMLElement {
|
|
|
1482
1543
|
updateActiveHeading(tag) {
|
|
1483
1544
|
this.#headingButtons.forEach(button => {
|
|
1484
1545
|
const next = (button.dataset.heading === tag).toString();
|
|
1485
|
-
if (button.getAttribute("aria-
|
|
1486
|
-
button.setAttribute("aria-
|
|
1546
|
+
if (button.getAttribute("aria-checked") !== next) {
|
|
1547
|
+
button.setAttribute("aria-checked", next);
|
|
1487
1548
|
}
|
|
1488
1549
|
});
|
|
1489
1550
|
}
|
|
@@ -1528,7 +1589,7 @@ class HeadingDropdown extends HTMLElement {
|
|
|
1528
1589
|
button.classList.add("lexxy-heading-button");
|
|
1529
1590
|
button.name = name;
|
|
1530
1591
|
button.title = label;
|
|
1531
|
-
button.setAttribute("role", "
|
|
1592
|
+
button.setAttribute("role", "menuitemradio");
|
|
1532
1593
|
button.innerHTML = `${icon} <span>${label}</span>`;
|
|
1533
1594
|
return button
|
|
1534
1595
|
}
|
|
@@ -1563,6 +1624,143 @@ class HeadingDropdown extends HTMLElement {
|
|
|
1563
1624
|
}
|
|
1564
1625
|
}
|
|
1565
1626
|
|
|
1627
|
+
// Shared, strictly-contained element used to attach ephemeral nodes when we
|
|
1628
|
+
// need to read computed styles (e.g. canonicalizing style values, resolving
|
|
1629
|
+
// CSS custom properties). The container is created once and attached to
|
|
1630
|
+
// `document.body` once; subsequent child mutations happen *inside* the
|
|
1631
|
+
// contained subtree so they do not invalidate style on the rest of the page.
|
|
1632
|
+
//
|
|
1633
|
+
// Without this, `document.body.appendChild(...)` / `element.remove()` calls
|
|
1634
|
+
// forced the browser to re-evaluate every ancestor-dependent selector (`:has()`,
|
|
1635
|
+
// descendant combinators, universal sibling rules) across the document on each
|
|
1636
|
+
// invocation — a 13,000+ element style recalc per call on a typical Basecamp
|
|
1637
|
+
// page.
|
|
1638
|
+
|
|
1639
|
+
let resolverRoot = null;
|
|
1640
|
+
|
|
1641
|
+
function styleResolverRoot() {
|
|
1642
|
+
if (resolverRoot && resolverRoot.isConnected) return resolverRoot
|
|
1643
|
+
|
|
1644
|
+
resolverRoot = document.createElement("div");
|
|
1645
|
+
resolverRoot.setAttribute("aria-hidden", "true");
|
|
1646
|
+
resolverRoot.setAttribute("data-lexxy-style-resolver", "");
|
|
1647
|
+
// `contain: strict` (size, layout, paint, style) isolates everything.
|
|
1648
|
+
// The root itself paints nothing (visibility hidden), has zero
|
|
1649
|
+
// geometric impact (position fixed, intrinsic size via contain), and
|
|
1650
|
+
// never leaks style invalidation to its ancestors.
|
|
1651
|
+
resolverRoot.style.cssText = "contain: strict; position: fixed; top: 0; left: 0; visibility: hidden; pointer-events: none; width: 0; height: 0;";
|
|
1652
|
+
document.body.appendChild(resolverRoot);
|
|
1653
|
+
return resolverRoot
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
function isSelectionHighlighted(selection) {
|
|
1657
|
+
if (!$isRangeSelection(selection)) return false
|
|
1658
|
+
|
|
1659
|
+
if (selection.isCollapsed()) {
|
|
1660
|
+
return hasHighlightStyles(selection.style)
|
|
1661
|
+
} else {
|
|
1662
|
+
return selection.hasFormat("highlight")
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
function getHighlightStyles(selection) {
|
|
1667
|
+
if (!$isRangeSelection(selection)) return null
|
|
1668
|
+
|
|
1669
|
+
let styles = getStyleObjectFromCSS(selection.style);
|
|
1670
|
+
if (!styles.color && !styles["background-color"]) {
|
|
1671
|
+
const anchorNode = selection.anchor.getNode();
|
|
1672
|
+
if ($isTextNode(anchorNode)) {
|
|
1673
|
+
styles = getStyleObjectFromCSS(anchorNode.getStyle());
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
const color = styles.color || null;
|
|
1678
|
+
const backgroundColor = styles["background-color"] || null;
|
|
1679
|
+
if (!color && !backgroundColor) return null
|
|
1680
|
+
|
|
1681
|
+
return { color, backgroundColor }
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
function hasHighlightStyles(cssOrStyles) {
|
|
1685
|
+
const styles = typeof cssOrStyles === "string" ? getStyleObjectFromCSS(cssOrStyles) : cssOrStyles;
|
|
1686
|
+
return !!(styles.color || styles["background-color"])
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
function normalizeColorButtons(buttons) {
|
|
1690
|
+
return buttons.map((button) => (typeof button === "string" ? { value: button, label: button } : button))
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
function applyCanonicalizers(styles, canonicalizers = []) {
|
|
1694
|
+
return canonicalizers.reduce((css, canonicalizer) => {
|
|
1695
|
+
return canonicalizer.applyCanonicalization(css)
|
|
1696
|
+
}, styles)
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
class StyleCanonicalizer {
|
|
1700
|
+
constructor(property, allowedValues= []) {
|
|
1701
|
+
this._property = property;
|
|
1702
|
+
this._allowedValues = allowedValues;
|
|
1703
|
+
this._canonicalValues = this.#allowedValuesIdentityObject;
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
applyCanonicalization(css) {
|
|
1707
|
+
const styles = { ...getStyleObjectFromCSS(css) };
|
|
1708
|
+
|
|
1709
|
+
styles[this._property] = this.getCanonicalAllowedValue(styles[this._property]);
|
|
1710
|
+
if (!styles[this._property]) {
|
|
1711
|
+
delete styles[this._property];
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
return getCSSFromStyleObject(styles)
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
getCanonicalAllowedValue(value) {
|
|
1718
|
+
return this._canonicalValues[value] ||= this.#resolveCannonicalValue(value)
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
// Private
|
|
1722
|
+
|
|
1723
|
+
get #allowedValuesIdentityObject() {
|
|
1724
|
+
return this._allowedValues.reduce((object, value) => ({ ...object, [value]: value }), {})
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
#resolveCannonicalValue(value) {
|
|
1728
|
+
let index = this.#computedAllowedValues.indexOf(value);
|
|
1729
|
+
if (index === -1) {
|
|
1730
|
+
index = this.#computedAllowedValues.indexOf(computeStyleValues(this._property, [ value ])[0]);
|
|
1731
|
+
}
|
|
1732
|
+
return index === -1 ? null : this._allowedValues[index]
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
get #computedAllowedValues() {
|
|
1736
|
+
return this._computedAllowedValues ||= computeStyleValues(this._property, this._allowedValues)
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
// Separates DOM writes from layout reads to avoid forced reflows, and attaches
|
|
1741
|
+
// resolver elements to a strictly-contained root (outside the normal document
|
|
1742
|
+
// flow) so neither the attach nor the detach invalidate styles on the rest of
|
|
1743
|
+
// the page. Without containment, appending to `document.body` triggered a
|
|
1744
|
+
// page-wide style recalc on every canonicalization pass.
|
|
1745
|
+
function computeStyleValues(property, values) {
|
|
1746
|
+
const fragment = document.createDocumentFragment();
|
|
1747
|
+
|
|
1748
|
+
const elements = values.map(value => {
|
|
1749
|
+
const element = createElement("span", { style: `display: none; ${property}: ${value};` });
|
|
1750
|
+
fragment.appendChild(element);
|
|
1751
|
+
return element
|
|
1752
|
+
});
|
|
1753
|
+
|
|
1754
|
+
styleResolverRoot().appendChild(fragment);
|
|
1755
|
+
|
|
1756
|
+
const computed = elements.map(element =>
|
|
1757
|
+
window.getComputedStyle(element).getPropertyValue(property)
|
|
1758
|
+
);
|
|
1759
|
+
|
|
1760
|
+
elements.forEach(element => element.remove());
|
|
1761
|
+
return computed
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1566
1764
|
const APPLY_HIGHLIGHT_SELECTOR = "button.lexxy-highlight-button";
|
|
1567
1765
|
const REMOVE_HIGHLIGHT_SELECTOR = "[data-command='removeHighlight']";
|
|
1568
1766
|
|
|
@@ -1594,27 +1792,36 @@ class HighlightDropdown extends ToolbarDropdown {
|
|
|
1594
1792
|
|
|
1595
1793
|
const colorGroups = this.editorElement.config.get("highlight.buttons");
|
|
1596
1794
|
|
|
1597
|
-
this.#
|
|
1598
|
-
this.#
|
|
1795
|
+
this.#appendColorGroup("Text color", "color", colorGroups.color);
|
|
1796
|
+
this.#appendColorGroup("Background color", "background-color", colorGroups["background-color"]);
|
|
1599
1797
|
|
|
1600
1798
|
const maxNumberOfColors = Math.max(colorGroups.color.length, colorGroups["background-color"].length);
|
|
1601
1799
|
this.panel.style.setProperty("--max-colors", maxNumberOfColors);
|
|
1602
1800
|
}
|
|
1603
1801
|
|
|
1604
|
-
#
|
|
1605
|
-
|
|
1606
|
-
|
|
1802
|
+
#appendColorGroup(label, attribute, buttons) {
|
|
1803
|
+
const group = createElement("div", {
|
|
1804
|
+
role: "group",
|
|
1805
|
+
class: "lexxy-highlight-colors__group",
|
|
1806
|
+
"aria-label": label
|
|
1807
|
+
});
|
|
1808
|
+
|
|
1809
|
+
normalizeColorButtons(buttons).forEach((button, index) => {
|
|
1810
|
+
group.appendChild(this.#createButton(attribute, button, index));
|
|
1607
1811
|
});
|
|
1812
|
+
|
|
1813
|
+
this.#buttonContainer.append(group);
|
|
1608
1814
|
}
|
|
1609
1815
|
|
|
1610
|
-
#createButton(attribute,
|
|
1816
|
+
#createButton(attribute, button, index) {
|
|
1611
1817
|
return createElement("button", {
|
|
1612
1818
|
type: "button",
|
|
1613
|
-
dataset: { value, style: attribute },
|
|
1614
|
-
style: `${attribute}: ${value}`,
|
|
1819
|
+
dataset: { value: button.value, style: attribute },
|
|
1820
|
+
style: `${attribute}: ${button.value}`,
|
|
1615
1821
|
class: "lexxy-editor__toolbar-button lexxy-highlight-button",
|
|
1616
1822
|
name: `${attribute}-${index}`,
|
|
1617
|
-
role: "
|
|
1823
|
+
role: "menuitemcheckbox",
|
|
1824
|
+
"aria-label": button.label
|
|
1618
1825
|
})
|
|
1619
1826
|
}
|
|
1620
1827
|
|
|
@@ -1638,11 +1845,7 @@ class HighlightDropdown extends ToolbarDropdown {
|
|
|
1638
1845
|
const backgroundColor = $getSelectionStyleValueForProperty(selection, "background-color", NO_STYLE);
|
|
1639
1846
|
|
|
1640
1847
|
this.#colorButtons.forEach(button => {
|
|
1641
|
-
|
|
1642
|
-
const next = matchesSelection.toString();
|
|
1643
|
-
if (button.getAttribute("aria-pressed") !== next) {
|
|
1644
|
-
button.setAttribute("aria-pressed", next);
|
|
1645
|
-
}
|
|
1848
|
+
button.ariaChecked = button.dataset.value === textColor || button.dataset.value === backgroundColor;
|
|
1646
1849
|
});
|
|
1647
1850
|
|
|
1648
1851
|
const hasHighlight = textColor !== NO_STYLE || backgroundColor !== NO_STYLE;
|
|
@@ -1939,13 +2142,35 @@ class CustomActionTextAttachmentNode extends DecoratorNode {
|
|
|
1939
2142
|
// is cosmetic: it is a nested attachment's rendering inside this one, not the
|
|
1940
2143
|
// attribute anything re-imports from.
|
|
1941
2144
|
figure.insertAdjacentHTML("beforeend", EditorSanitizer.for(editor).sanitize(this.innerHtml, { safeForXml: true }));
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
figure
|
|
2145
|
+
this.#markImagesAsDecorative(figure);
|
|
2146
|
+
this.#tagLabelImage(figure);
|
|
2147
|
+
this.#tagLabelMirrors(figure);
|
|
1945
2148
|
|
|
1946
2149
|
return figure
|
|
1947
2150
|
}
|
|
1948
2151
|
|
|
2152
|
+
exposeLabel(figure) {
|
|
2153
|
+
const labelImage = figure.querySelector("[data-lexxy-label-image]");
|
|
2154
|
+
if (labelImage) labelImage.alt = this.label;
|
|
2155
|
+
|
|
2156
|
+
for (const span of figure.querySelectorAll("[data-lexxy-label-mirror]")) {
|
|
2157
|
+
span.setAttribute("aria-hidden", "true");
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
|
|
2161
|
+
get label() {
|
|
2162
|
+
return this.getReadableTextContent()
|
|
2163
|
+
}
|
|
2164
|
+
|
|
2165
|
+
restoreLabel(figure) {
|
|
2166
|
+
const labelImage = figure.querySelector("[data-lexxy-label-image]");
|
|
2167
|
+
if (labelImage) labelImage.alt = labelImage.dataset.lexxyOriginalAlt;
|
|
2168
|
+
|
|
2169
|
+
for (const span of figure.querySelectorAll("[data-lexxy-label-mirror]")) {
|
|
2170
|
+
span.removeAttribute("aria-hidden");
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
2173
|
+
|
|
1949
2174
|
updateDOM() {
|
|
1950
2175
|
return false
|
|
1951
2176
|
}
|
|
@@ -1986,6 +2211,34 @@ class CustomActionTextAttachmentNode extends DecoratorNode {
|
|
|
1986
2211
|
decorate() {
|
|
1987
2212
|
return null
|
|
1988
2213
|
}
|
|
2214
|
+
|
|
2215
|
+
#markImagesAsDecorative(figure) {
|
|
2216
|
+
for (const img of figure.querySelectorAll("img:not([alt])")) {
|
|
2217
|
+
img.alt = "";
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
2220
|
+
|
|
2221
|
+
// Images injected later by other modules must not become the announcement image.
|
|
2222
|
+
#tagLabelImage(figure) {
|
|
2223
|
+
const image = figure.querySelector("img");
|
|
2224
|
+
if (image) {
|
|
2225
|
+
image.setAttribute("data-lexxy-label-image", "");
|
|
2226
|
+
image.dataset.lexxyOriginalAlt = image.alt;
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2230
|
+
// Duplicate labels would be spoken twice. Only authored spans should be silenced,
|
|
2231
|
+
// since the fake selection injects its own label later.
|
|
2232
|
+
#tagLabelMirrors(figure) {
|
|
2233
|
+
const trimmedLabel = this.label.trim();
|
|
2234
|
+
const matches = [ ...figure.querySelectorAll("span") ].filter((span) => span.textContent.trim() === trimmedLabel);
|
|
2235
|
+
const deepest = matches.filter((span) => !matches.some((other) => other !== span && span.contains(other)));
|
|
2236
|
+
for (const span of deepest) {
|
|
2237
|
+
if (span.getAttribute("aria-hidden") !== "true") {
|
|
2238
|
+
span.setAttribute("data-lexxy-label-mirror", "");
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
1989
2242
|
}
|
|
1990
2243
|
|
|
1991
2244
|
function $isCustomActionTextAttachmentNode(node) {
|
|
@@ -2089,6 +2342,35 @@ function $createNodeSelectionWith(...nodes) {
|
|
|
2089
2342
|
return selection
|
|
2090
2343
|
}
|
|
2091
2344
|
|
|
2345
|
+
function $singleSelectedNode() {
|
|
2346
|
+
const selection = $getSelection();
|
|
2347
|
+
if ($isNodeSelection(selection)) {
|
|
2348
|
+
const nodes = selection.getNodes();
|
|
2349
|
+
if (nodes.length === 1) {
|
|
2350
|
+
return nodes[0]
|
|
2351
|
+
} else {
|
|
2352
|
+
return null
|
|
2353
|
+
}
|
|
2354
|
+
} else {
|
|
2355
|
+
return null
|
|
2356
|
+
}
|
|
2357
|
+
}
|
|
2358
|
+
|
|
2359
|
+
function $selectedLabelledDecoratorNode() {
|
|
2360
|
+
const node = $singleSelectedNode();
|
|
2361
|
+
if (typeof node?.label === "string") {
|
|
2362
|
+
return { key: node.getKey(), label: node.label }
|
|
2363
|
+
} else {
|
|
2364
|
+
return null
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2368
|
+
function registerLabelledDecoratorSelection(editor, onChange) {
|
|
2369
|
+
return editor.registerUpdateListener(({ editorState }) => {
|
|
2370
|
+
onChange(editorState.read($selectedLabelledDecoratorNode));
|
|
2371
|
+
})
|
|
2372
|
+
}
|
|
2373
|
+
|
|
2092
2374
|
function $isShadowRoot(node) {
|
|
2093
2375
|
return $isElementNode(node) && $isRootOrShadowRoot(node) && !$isRootNode(node)
|
|
2094
2376
|
}
|
|
@@ -2114,6 +2396,10 @@ function getListType(node) {
|
|
|
2114
2396
|
return list?.getListType() ?? null
|
|
2115
2397
|
}
|
|
2116
2398
|
|
|
2399
|
+
function announceFromEditor(editor, message) {
|
|
2400
|
+
editor.getRootElement()?.closest("lexxy-editor")?.announce(message);
|
|
2401
|
+
}
|
|
2402
|
+
|
|
2117
2403
|
function isEditorFocused(editor) {
|
|
2118
2404
|
const rootElement = editor.getRootElement();
|
|
2119
2405
|
return rootElement !== null && rootElement.contains(document.activeElement)
|
|
@@ -2766,14 +3052,27 @@ class ActionTextAttachmentNode extends DecoratorNode {
|
|
|
2766
3052
|
updateDOM(prevNode, dom) {
|
|
2767
3053
|
if (this.uploadError !== prevNode.uploadError) return true
|
|
2768
3054
|
|
|
2769
|
-
const
|
|
2770
|
-
if (
|
|
2771
|
-
|
|
3055
|
+
const image = dom.querySelector("img");
|
|
3056
|
+
if (image && prevNode.altText !== this.altText) {
|
|
3057
|
+
image.alt = this.altText;
|
|
3058
|
+
}
|
|
3059
|
+
|
|
3060
|
+
if (prevNode.caption !== this.caption) {
|
|
3061
|
+
dom.querySelector("figcaption")?.toggleAttribute("data-placeholder", !this.caption);
|
|
3062
|
+
}
|
|
3063
|
+
|
|
3064
|
+
const captionText = dom.querySelector("figcaption .attachment__caption-text");
|
|
3065
|
+
if (captionText && prevNode.captionLabel !== this.captionLabel) {
|
|
3066
|
+
captionText.textContent = this.captionLabel;
|
|
2772
3067
|
}
|
|
2773
3068
|
|
|
2774
3069
|
return false
|
|
2775
3070
|
}
|
|
2776
3071
|
|
|
3072
|
+
get captionLabel() {
|
|
3073
|
+
return this.caption || this.fileName || ""
|
|
3074
|
+
}
|
|
3075
|
+
|
|
2777
3076
|
getTextContent() {
|
|
2778
3077
|
return `[${this.caption || this.fileName}]\n\n`
|
|
2779
3078
|
}
|
|
@@ -2835,9 +3134,6 @@ class ActionTextAttachmentNode extends DecoratorNode {
|
|
|
2835
3134
|
figure.draggable = true;
|
|
2836
3135
|
figure.dataset.lexicalNodeKey = this.__key;
|
|
2837
3136
|
|
|
2838
|
-
const deleteButton = createElement("lexxy-node-delete-button");
|
|
2839
|
-
figure.appendChild(deleteButton);
|
|
2840
|
-
|
|
2841
3137
|
return figure
|
|
2842
3138
|
}
|
|
2843
3139
|
|
|
@@ -2853,6 +3149,18 @@ class ActionTextAttachmentNode extends DecoratorNode {
|
|
|
2853
3149
|
return this.contentType.startsWith("video/")
|
|
2854
3150
|
}
|
|
2855
3151
|
|
|
3152
|
+
get label() {
|
|
3153
|
+
if (this.caption && this.altText && this.altText !== this.caption && this.altText !== this.fileName) {
|
|
3154
|
+
return `${this.caption}. ${this.altText}`
|
|
3155
|
+
} else {
|
|
3156
|
+
return this.caption || this.altText || this.fileName || ""
|
|
3157
|
+
}
|
|
3158
|
+
}
|
|
3159
|
+
|
|
3160
|
+
focusCaption() {
|
|
3161
|
+
return this.editor.getRootElement()?.closest("lexxy-editor")?.captionEditor.open(this.getKey()) ?? false
|
|
3162
|
+
}
|
|
3163
|
+
|
|
2856
3164
|
#createDOMForPendingPreview() {
|
|
2857
3165
|
const figure = this.createAttachmentFigure(false);
|
|
2858
3166
|
figure.appendChild(this.#createDOMForFile());
|
|
@@ -3043,55 +3351,18 @@ class ActionTextAttachmentNode extends DecoratorNode {
|
|
|
3043
3351
|
}
|
|
3044
3352
|
|
|
3045
3353
|
#createEditableCaption() {
|
|
3046
|
-
const caption = createElement("figcaption", { className: "attachment__caption" });
|
|
3047
|
-
const
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3354
|
+
const caption = createElement("figcaption", { className: "attachment__caption attachment__caption--editable" });
|
|
3355
|
+
const text = createElement("span", { className: "attachment__caption-text", textContent: this.captionLabel });
|
|
3356
|
+
caption.appendChild(text);
|
|
3357
|
+
caption.toggleAttribute("data-placeholder", !this.caption);
|
|
3358
|
+
caption.addEventListener("mousedown", event => event.preventDefault());
|
|
3359
|
+
caption.addEventListener("click", event => {
|
|
3360
|
+
event.stopPropagation();
|
|
3361
|
+
this.focusCaption();
|
|
3051
3362
|
});
|
|
3052
|
-
|
|
3053
|
-
input.addEventListener("focusin", () => input.placeholder = "Add caption...");
|
|
3054
|
-
input.addEventListener("blur", (event) => this.#handleCaptionInputBlurred(event));
|
|
3055
|
-
input.addEventListener("keydown", (event) => this.#handleCaptionInputKeydown(event));
|
|
3056
|
-
input.addEventListener("copy", (event) => event.stopPropagation());
|
|
3057
|
-
input.addEventListener("cut", (event) => event.stopPropagation());
|
|
3058
|
-
input.addEventListener("paste", (event) => event.stopPropagation());
|
|
3059
|
-
|
|
3060
|
-
caption.appendChild(input);
|
|
3061
|
-
|
|
3062
3363
|
return caption
|
|
3063
3364
|
}
|
|
3064
|
-
|
|
3065
|
-
#handleCaptionInputBlurred(event) {
|
|
3066
|
-
this.#updateCaptionValueFromInput(event.target);
|
|
3067
|
-
}
|
|
3068
|
-
|
|
3069
|
-
#updateCaptionValueFromInput(input) {
|
|
3070
|
-
input.placeholder = this.fileName;
|
|
3071
|
-
this.editor.update(() => {
|
|
3072
|
-
this.getWritable().caption = input.value;
|
|
3073
|
-
});
|
|
3074
|
-
}
|
|
3075
|
-
|
|
3076
|
-
#handleCaptionInputKeydown(event) {
|
|
3077
|
-
if (event.key === "Enter") {
|
|
3078
|
-
event.preventDefault();
|
|
3079
|
-
event.target.blur();
|
|
3080
|
-
|
|
3081
|
-
this.editor.update(() => {
|
|
3082
|
-
// Place the cursor after the current image
|
|
3083
|
-
this.selectNext(0, 0);
|
|
3084
|
-
}, {
|
|
3085
|
-
tag: HISTORY_MERGE_TAG
|
|
3086
|
-
});
|
|
3087
|
-
}
|
|
3088
|
-
|
|
3089
|
-
// Stop all keydown events from bubbling to the Lexical root element.
|
|
3090
|
-
// The caption textarea is outside Lexical's content model and should
|
|
3091
|
-
// handle its own keyboard events natively (Ctrl+A, Ctrl+C, Ctrl+X, etc.).
|
|
3092
|
-
event.stopPropagation();
|
|
3093
|
-
}
|
|
3094
|
-
}
|
|
3365
|
+
}
|
|
3095
3366
|
|
|
3096
3367
|
function $createActionTextAttachmentNode(...args) {
|
|
3097
3368
|
return new ActionTextAttachmentNode(...args)
|
|
@@ -3210,14 +3481,11 @@ class HorizontalDividerNode extends DecoratorNode {
|
|
|
3210
3481
|
super(key);
|
|
3211
3482
|
}
|
|
3212
3483
|
|
|
3484
|
+
// The hr already provides separator semantics; a figure boundary would be redundant.
|
|
3213
3485
|
createDOM() {
|
|
3214
|
-
const figure = createElement("figure", { className: "horizontal-divider" });
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
figure.appendChild(hr);
|
|
3218
|
-
|
|
3219
|
-
const deleteButton = createElement("lexxy-node-delete-button");
|
|
3220
|
-
figure.appendChild(deleteButton);
|
|
3486
|
+
const figure = createElement("figure", { className: "horizontal-divider", role: "presentation" });
|
|
3487
|
+
figure.appendChild(createElement("hr"));
|
|
3488
|
+
figure.dataset.lexicalNodeKey = this.__key;
|
|
3221
3489
|
|
|
3222
3490
|
return figure
|
|
3223
3491
|
}
|
|
@@ -3234,6 +3502,10 @@ class HorizontalDividerNode extends DecoratorNode {
|
|
|
3234
3502
|
return false
|
|
3235
3503
|
}
|
|
3236
3504
|
|
|
3505
|
+
get label() {
|
|
3506
|
+
return "Horizontal divider"
|
|
3507
|
+
}
|
|
3508
|
+
|
|
3237
3509
|
exportDOM() {
|
|
3238
3510
|
const hr = createElement("hr");
|
|
3239
3511
|
return { element: hr }
|
|
@@ -3499,189 +3771,366 @@ var theme = {
|
|
|
3499
3771
|
}
|
|
3500
3772
|
};
|
|
3501
3773
|
|
|
3502
|
-
class
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3774
|
+
class ImageGalleryNode extends ElementNode {
|
|
3775
|
+
$config() {
|
|
3776
|
+
return this.config("image_gallery", {
|
|
3777
|
+
extends: ElementNode,
|
|
3778
|
+
})
|
|
3507
3779
|
}
|
|
3508
3780
|
|
|
3509
|
-
|
|
3510
|
-
|
|
3781
|
+
static transform() {
|
|
3782
|
+
return (gallery) => {
|
|
3783
|
+
gallery.unwrapEmptyNode()
|
|
3784
|
+
|| gallery.replaceWithSingularChild()
|
|
3785
|
+
|| gallery.splitAroundInvalidChild();
|
|
3786
|
+
}
|
|
3511
3787
|
}
|
|
3512
3788
|
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3789
|
+
static importDOM() {
|
|
3790
|
+
return {
|
|
3791
|
+
div: (element) => {
|
|
3792
|
+
if (!this.#isGalleryElement(element)) return null
|
|
3793
|
+
|
|
3794
|
+
return {
|
|
3795
|
+
conversion: () => {
|
|
3796
|
+
return {
|
|
3797
|
+
node: $createImageGalleryNode()
|
|
3798
|
+
}
|
|
3799
|
+
},
|
|
3800
|
+
priority: 2
|
|
3801
|
+
}
|
|
3802
|
+
}
|
|
3518
3803
|
}
|
|
3519
3804
|
}
|
|
3520
3805
|
|
|
3521
|
-
|
|
3522
|
-
this
|
|
3806
|
+
static canCollapseWith(node) {
|
|
3807
|
+
return $isImageGalleryNode(node) || this.isValidChild(node)
|
|
3523
3808
|
}
|
|
3524
|
-
}
|
|
3525
3809
|
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
// the stretches no range covers. Ranges must be sorted and non-overlapping.
|
|
3530
|
-
function segmentTextByRanges(text, textStart, ranges) {
|
|
3531
|
-
const segments = [];
|
|
3532
|
-
let cursor = 0;
|
|
3810
|
+
static isValidChild(node) {
|
|
3811
|
+
return $isActionTextAttachmentNode(node) && node.isPreviewableImage
|
|
3812
|
+
}
|
|
3533
3813
|
|
|
3534
|
-
|
|
3535
|
-
const
|
|
3536
|
-
|
|
3814
|
+
static #isGalleryElement(element) {
|
|
3815
|
+
const attachmentChildren = element.querySelectorAll(`:scope > :is(${this.#attachmentTags.join()})`);
|
|
3816
|
+
return element.textContent.trim() === ""
|
|
3817
|
+
&& attachmentChildren.length > 0
|
|
3818
|
+
&& element.children.length === attachmentChildren.length
|
|
3819
|
+
}
|
|
3537
3820
|
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3821
|
+
static get #attachmentTags() {
|
|
3822
|
+
return Object.keys(ActionTextAttachmentNode.importDOM())
|
|
3823
|
+
}
|
|
3824
|
+
|
|
3825
|
+
createDOM() {
|
|
3826
|
+
const div = document.createElement("div");
|
|
3827
|
+
div.className = this.#galleryClassNames;
|
|
3828
|
+
return div
|
|
3829
|
+
}
|
|
3830
|
+
|
|
3831
|
+
updateDOM(_prevNode, dom) {
|
|
3832
|
+
dom.className = this.#galleryClassNames;
|
|
3833
|
+
return false
|
|
3834
|
+
}
|
|
3835
|
+
|
|
3836
|
+
canBeEmpty() {
|
|
3837
|
+
// Return `true` to conform to `$isBlock(node)`
|
|
3838
|
+
// We clean-up empty galleries with a transform
|
|
3839
|
+
return true
|
|
3840
|
+
}
|
|
3841
|
+
|
|
3842
|
+
collapseAtStart(_selection) {
|
|
3843
|
+
return true
|
|
3844
|
+
}
|
|
3845
|
+
|
|
3846
|
+
insertNewAfter(selection, restoreSelection) {
|
|
3847
|
+
const selectionBeforeLastChild = selection.anchor.getNode().is(this) && selection.anchor.offset == this.getChildrenSize() - 1;
|
|
3848
|
+
if (selectionBeforeLastChild) {
|
|
3849
|
+
const paragraph = $createParagraphNode();
|
|
3850
|
+
this.insertAfter(paragraph, false);
|
|
3851
|
+
paragraph.insertAfter(this.getLastChild(), false);
|
|
3852
|
+
paragraph.selectEnd();
|
|
3853
|
+
|
|
3854
|
+
// return null as selection has been managed
|
|
3855
|
+
return null
|
|
3544
3856
|
}
|
|
3857
|
+
|
|
3858
|
+
const newNode = $createImageGalleryNode();
|
|
3859
|
+
this.insertAfter(newNode, restoreSelection);
|
|
3860
|
+
return newNode
|
|
3545
3861
|
}
|
|
3546
3862
|
|
|
3547
|
-
|
|
3548
|
-
|
|
3863
|
+
getImageAttachments() {
|
|
3864
|
+
const children = this.getChildren();
|
|
3865
|
+
return children.filter($isActionTextAttachmentNode)
|
|
3549
3866
|
}
|
|
3550
3867
|
|
|
3551
|
-
|
|
3552
|
-
|
|
3868
|
+
exportDOM() {
|
|
3869
|
+
const div = document.createElement("div");
|
|
3870
|
+
div.className = this.#galleryClassNames;
|
|
3871
|
+
return { element: div }
|
|
3872
|
+
}
|
|
3553
3873
|
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
// CSS custom properties). The container is created once and attached to
|
|
3557
|
-
// `document.body` once; subsequent child mutations happen *inside* the
|
|
3558
|
-
// contained subtree so they do not invalidate style on the rest of the page.
|
|
3559
|
-
//
|
|
3560
|
-
// Without this, `document.body.appendChild(...)` / `element.remove()` calls
|
|
3561
|
-
// forced the browser to re-evaluate every ancestor-dependent selector (`:has()`,
|
|
3562
|
-
// descendant combinators, universal sibling rules) across the document on each
|
|
3563
|
-
// invocation — a 13,000+ element style recalc per call on a typical Basecamp
|
|
3564
|
-
// page.
|
|
3874
|
+
collapseWith(node, backwards) {
|
|
3875
|
+
if (!ImageGalleryNode.canCollapseWith(node)) return false
|
|
3565
3876
|
|
|
3566
|
-
|
|
3877
|
+
if (backwards) {
|
|
3878
|
+
$insertFirst(this, node);
|
|
3879
|
+
} else {
|
|
3880
|
+
this.append(node);
|
|
3881
|
+
}
|
|
3567
3882
|
|
|
3568
|
-
|
|
3569
|
-
if (resolverRoot && resolverRoot.isConnected) return resolverRoot
|
|
3883
|
+
$unwrapAndFilterDescendants(this, ImageGalleryNode.isValidChild);
|
|
3570
3884
|
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
resolverRoot.setAttribute("data-lexxy-style-resolver", "");
|
|
3574
|
-
// `contain: strict` (size, layout, paint, style) isolates everything.
|
|
3575
|
-
// The root itself paints nothing (visibility hidden), has zero
|
|
3576
|
-
// geometric impact (position fixed, intrinsic size via contain), and
|
|
3577
|
-
// never leaks style invalidation to its ancestors.
|
|
3578
|
-
resolverRoot.style.cssText = "contain: strict; position: fixed; top: 0; left: 0; visibility: hidden; pointer-events: none; width: 0; height: 0;";
|
|
3579
|
-
document.body.appendChild(resolverRoot);
|
|
3580
|
-
return resolverRoot
|
|
3581
|
-
}
|
|
3885
|
+
return true
|
|
3886
|
+
}
|
|
3582
3887
|
|
|
3583
|
-
|
|
3584
|
-
|
|
3888
|
+
unwrapEmptyNode() {
|
|
3889
|
+
if (this.isEmpty()) {
|
|
3890
|
+
const paragraph = $createParagraphNode();
|
|
3891
|
+
return this.replace(paragraph)
|
|
3892
|
+
}
|
|
3893
|
+
}
|
|
3585
3894
|
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3895
|
+
replaceWithSingularChild() {
|
|
3896
|
+
if (this.#hasSingularChild) {
|
|
3897
|
+
const child = this.getFirstChild();
|
|
3898
|
+
return this.replace($makeSafeForRoot(child))
|
|
3899
|
+
}
|
|
3590
3900
|
}
|
|
3591
|
-
}
|
|
3592
3901
|
|
|
3593
|
-
|
|
3594
|
-
|
|
3902
|
+
splitAroundInvalidChild() {
|
|
3903
|
+
for (const child of $firstToLastIterator(this)) {
|
|
3904
|
+
if (ImageGalleryNode.isValidChild(child)) continue
|
|
3595
3905
|
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3906
|
+
const poppedNode = $makeSafeForRoot(child);
|
|
3907
|
+
const [ topGallery, secondGallery ] = this.splitAtIndex(poppedNode.getIndexWithinParent());
|
|
3908
|
+
topGallery.insertAfter(poppedNode);
|
|
3909
|
+
poppedNode.selectEnd();
|
|
3910
|
+
|
|
3911
|
+
// remove an empty gallery rather than let it unwrap to a paragraph
|
|
3912
|
+
if (secondGallery.isEmpty()) secondGallery.remove();
|
|
3913
|
+
|
|
3914
|
+
break
|
|
3601
3915
|
}
|
|
3602
3916
|
}
|
|
3603
3917
|
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3918
|
+
splitAtIndex(index) {
|
|
3919
|
+
return $splitNode(this, index)
|
|
3920
|
+
}
|
|
3607
3921
|
|
|
3608
|
-
|
|
3922
|
+
get #hasSingularChild() {
|
|
3923
|
+
return this.getChildrenSize() === 1
|
|
3924
|
+
}
|
|
3925
|
+
|
|
3926
|
+
get #galleryClassNames() {
|
|
3927
|
+
return `attachment-gallery attachment-gallery--${this.getChildrenSize()}`
|
|
3928
|
+
}
|
|
3609
3929
|
}
|
|
3610
3930
|
|
|
3611
|
-
function
|
|
3612
|
-
|
|
3613
|
-
return !!(styles.color || styles["background-color"])
|
|
3931
|
+
function $createImageGalleryNode() {
|
|
3932
|
+
return new ImageGalleryNode()
|
|
3614
3933
|
}
|
|
3615
3934
|
|
|
3616
|
-
function
|
|
3617
|
-
return
|
|
3618
|
-
return canonicalizer.applyCanonicalization(css)
|
|
3619
|
-
}, styles)
|
|
3935
|
+
function $isImageGalleryNode(node) {
|
|
3936
|
+
return node instanceof ImageGalleryNode
|
|
3620
3937
|
}
|
|
3621
3938
|
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3939
|
+
function $findOrCreateGalleryForImage(node) {
|
|
3940
|
+
if (!ImageGalleryNode.canCollapseWith(node)) return null
|
|
3941
|
+
|
|
3942
|
+
const existingGallery = $getNearestNodeOfType(node, ImageGalleryNode);
|
|
3943
|
+
return existingGallery ?? $wrapNodeInElement(node, $createImageGalleryNode)
|
|
3944
|
+
}
|
|
3945
|
+
|
|
3946
|
+
class CaptionEditor {
|
|
3947
|
+
#editorElement
|
|
3948
|
+
#editor
|
|
3949
|
+
#input = createElement("textarea", { className: "lexxy-caption-editor", hidden: true, tabIndex: -1, rows: 1, placeholder: "Add caption..." })
|
|
3950
|
+
#nodeKey
|
|
3951
|
+
#caption
|
|
3952
|
+
#listeners = new ListenerBin()
|
|
3953
|
+
#resizeObserver = new ResizeObserver(() => this.#updatePosition())
|
|
3954
|
+
|
|
3955
|
+
constructor(editorElement) {
|
|
3956
|
+
this.#editorElement = editorElement;
|
|
3957
|
+
this.#editor = editorElement.editor;
|
|
3958
|
+
editorElement.append(this.#input);
|
|
3959
|
+
this.#listeners.track(
|
|
3960
|
+
registerEventListener(this.#input, "blur", () => this.#save()),
|
|
3961
|
+
registerEventListener(this.#input, "keydown", event => this.#handleKeydown(event)),
|
|
3962
|
+
registerEventListener(this.#input, "input", () => this.#updatePosition()),
|
|
3963
|
+
registerEventListener(editorElement, "scroll", () => this.#updatePosition(), { capture: true, passive: true }),
|
|
3964
|
+
this.#editor.registerUpdateListener(() => this.#checkAttachment())
|
|
3965
|
+
);
|
|
3966
|
+
}
|
|
3967
|
+
|
|
3968
|
+
dispose() {
|
|
3969
|
+
this.#listeners.dispose();
|
|
3970
|
+
this.#close();
|
|
3971
|
+
this.#input.remove();
|
|
3972
|
+
}
|
|
3973
|
+
|
|
3974
|
+
open(nodeKey) {
|
|
3975
|
+
const caption = this.#editor.getElementByKey(nodeKey)?.querySelector("figcaption.attachment__caption--editable");
|
|
3976
|
+
if (caption) {
|
|
3977
|
+
if (this.#nodeKey !== nodeKey) {
|
|
3978
|
+
this.#edit(nodeKey, caption);
|
|
3979
|
+
}
|
|
3980
|
+
return true
|
|
3981
|
+
} else {
|
|
3982
|
+
return false
|
|
3983
|
+
}
|
|
3984
|
+
}
|
|
3985
|
+
|
|
3986
|
+
#save() {
|
|
3987
|
+
if (this.#nodeKey) {
|
|
3988
|
+
const key = this.#nodeKey;
|
|
3989
|
+
const value = this.#input.value;
|
|
3990
|
+
this.#close();
|
|
3991
|
+
this.#editor.update(() => {
|
|
3992
|
+
const node = $getNodeByKey(key);
|
|
3993
|
+
if (node && node.caption !== value) node.getWritable().caption = value;
|
|
3994
|
+
}, { tag: SKIP_DOM_SELECTION_TAG, discrete: true });
|
|
3995
|
+
}
|
|
3996
|
+
}
|
|
3997
|
+
|
|
3998
|
+
#close() {
|
|
3999
|
+
this.#nodeKey = null;
|
|
4000
|
+
this.#resizeObserver.disconnect();
|
|
4001
|
+
if (this.#caption) {
|
|
4002
|
+
this.#caption.classList.remove("attachment__caption--editing");
|
|
4003
|
+
this.#caption.style.removeProperty("min-height");
|
|
4004
|
+
this.#caption = null;
|
|
4005
|
+
}
|
|
4006
|
+
this.#input.hidden = true;
|
|
4007
|
+
}
|
|
4008
|
+
|
|
4009
|
+
#handleKeydown(event) {
|
|
4010
|
+
if (!event.isComposing && [ "Enter", "Escape" ].includes(event.key)) {
|
|
4011
|
+
event.preventDefault();
|
|
4012
|
+
const key = this.#nodeKey;
|
|
4013
|
+
this.#input.blur();
|
|
4014
|
+
this.#editor.getRootElement()?.focus({ preventScroll: true });
|
|
4015
|
+
this.#editor.update(() => {
|
|
4016
|
+
const node = $getNodeByKey(key);
|
|
4017
|
+
if (node) {
|
|
4018
|
+
if (event.key === "Enter") {
|
|
4019
|
+
if ($isImageGalleryNode(node.getParent()) && !node.getNextSibling()) {
|
|
4020
|
+
node.getParent().selectNext(0, 0);
|
|
4021
|
+
} else {
|
|
4022
|
+
node.selectNext(0, 0);
|
|
4023
|
+
}
|
|
4024
|
+
} else {
|
|
4025
|
+
$setSelection($createNodeSelectionWith(node));
|
|
4026
|
+
}
|
|
4027
|
+
}
|
|
4028
|
+
}, { tag: HISTORY_MERGE_TAG });
|
|
4029
|
+
}
|
|
4030
|
+
event.stopPropagation();
|
|
3627
4031
|
}
|
|
3628
4032
|
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
4033
|
+
#updatePosition() {
|
|
4034
|
+
if (this.#caption?.isConnected) {
|
|
4035
|
+
const rect = this.#caption.getBoundingClientRect();
|
|
4036
|
+
const editorRect = this.#editorElement.getBoundingClientRect();
|
|
4037
|
+
this.#input.style.left = `${rect.left - editorRect.left - this.#editorElement.clientLeft + this.#editorElement.scrollLeft}px`;
|
|
4038
|
+
this.#input.style.top = `${rect.top - editorRect.top - this.#editorElement.clientTop + this.#editorElement.scrollTop}px`;
|
|
4039
|
+
this.#input.style.width = `${rect.width}px`;
|
|
4040
|
+
this.#input.style.height = "0px";
|
|
4041
|
+
const height = this.#input.scrollHeight;
|
|
4042
|
+
this.#input.style.height = `${height}px`;
|
|
4043
|
+
this.#caption.style.minHeight = `${height}px`;
|
|
3635
4044
|
}
|
|
4045
|
+
}
|
|
3636
4046
|
|
|
3637
|
-
|
|
4047
|
+
#checkAttachment() {
|
|
4048
|
+
if (this.#nodeKey && this.#editor.getElementByKey(this.#nodeKey)?.querySelector("figcaption") !== this.#caption) {
|
|
4049
|
+
this.#close();
|
|
4050
|
+
} else {
|
|
4051
|
+
this.#updatePosition();
|
|
4052
|
+
}
|
|
3638
4053
|
}
|
|
3639
4054
|
|
|
3640
|
-
|
|
3641
|
-
|
|
4055
|
+
#edit(nodeKey, caption) {
|
|
4056
|
+
this.#save();
|
|
4057
|
+
this.#nodeKey = nodeKey;
|
|
4058
|
+
this.#caption = caption;
|
|
4059
|
+
this.#editor.getEditorState().read(() => {
|
|
4060
|
+
const node = $getNodeByKey(nodeKey);
|
|
4061
|
+
this.#input.value = node.caption;
|
|
4062
|
+
if (node.isVideo) {
|
|
4063
|
+
this.#input.ariaLabel = "Video caption";
|
|
4064
|
+
} else {
|
|
4065
|
+
this.#input.ariaLabel = "Image caption";
|
|
4066
|
+
}
|
|
4067
|
+
});
|
|
4068
|
+
|
|
4069
|
+
caption.scrollIntoView({ block: "nearest", inline: "nearest" });
|
|
4070
|
+
const style = getComputedStyle(caption);
|
|
4071
|
+
this.#input.style.font = style.font;
|
|
4072
|
+
this.#input.style.padding = style.padding;
|
|
4073
|
+
this.#input.style.color = style.color;
|
|
4074
|
+
this.#input.style.textAlign = getComputedStyle(caption.firstElementChild).textAlign;
|
|
4075
|
+
this.#input.hidden = false;
|
|
4076
|
+
caption.classList.add("attachment__caption--editing");
|
|
4077
|
+
this.#resizeObserver.observe(caption);
|
|
4078
|
+
this.#resizeObserver.observe(this.#editorElement);
|
|
4079
|
+
this.#updatePosition();
|
|
4080
|
+
this.#input.focus({ preventScroll: true });
|
|
3642
4081
|
}
|
|
4082
|
+
}
|
|
3643
4083
|
|
|
3644
|
-
|
|
4084
|
+
class UploadRequests {
|
|
4085
|
+
#requestsByKey = new Map()
|
|
3645
4086
|
|
|
3646
|
-
|
|
3647
|
-
|
|
4087
|
+
track(key, request) {
|
|
4088
|
+
this.#requestsByKey.set(key, request);
|
|
3648
4089
|
}
|
|
3649
4090
|
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
4091
|
+
forget(key) {
|
|
4092
|
+
this.#requestsByKey.delete(key);
|
|
4093
|
+
}
|
|
4094
|
+
|
|
4095
|
+
abort(key) {
|
|
4096
|
+
const request = this.#requestsByKey.get(key);
|
|
4097
|
+
if (request) {
|
|
4098
|
+
this.#requestsByKey.delete(key);
|
|
4099
|
+
request.abort();
|
|
3654
4100
|
}
|
|
3655
|
-
return index === -1 ? null : this._allowedValues[index]
|
|
3656
4101
|
}
|
|
3657
4102
|
|
|
3658
|
-
|
|
3659
|
-
|
|
4103
|
+
clear() {
|
|
4104
|
+
this.#requestsByKey.clear();
|
|
3660
4105
|
}
|
|
3661
4106
|
}
|
|
3662
4107
|
|
|
3663
|
-
//
|
|
3664
|
-
//
|
|
3665
|
-
//
|
|
3666
|
-
// the
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
4108
|
+
// Partition a stretch of text into consecutive { start, end, range } segments,
|
|
4109
|
+
// where offsets are relative to the text and range is the covering source
|
|
4110
|
+
// range — expressed in outer coordinates as { start, end, ... } — or null for
|
|
4111
|
+
// the stretches no range covers. Ranges must be sorted and non-overlapping.
|
|
4112
|
+
function segmentTextByRanges(text, textStart, ranges) {
|
|
4113
|
+
const segments = [];
|
|
4114
|
+
let cursor = 0;
|
|
3670
4115
|
|
|
3671
|
-
const
|
|
3672
|
-
const
|
|
3673
|
-
|
|
3674
|
-
return element
|
|
3675
|
-
});
|
|
4116
|
+
for (const range of ranges) {
|
|
4117
|
+
const from = Math.max(range.start - textStart, cursor);
|
|
4118
|
+
const to = Math.min(range.end - textStart, text.length);
|
|
3676
4119
|
|
|
3677
|
-
|
|
4120
|
+
if (from < to) {
|
|
4121
|
+
if (from > cursor) {
|
|
4122
|
+
segments.push({ start: cursor, end: from, range: null });
|
|
4123
|
+
}
|
|
4124
|
+
segments.push({ start: from, end: to, range });
|
|
4125
|
+
cursor = to;
|
|
4126
|
+
}
|
|
4127
|
+
}
|
|
3678
4128
|
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
4129
|
+
if (cursor < text.length) {
|
|
4130
|
+
segments.push({ start: cursor, end: text.length, range: null });
|
|
4131
|
+
}
|
|
3682
4132
|
|
|
3683
|
-
|
|
3684
|
-
return computed
|
|
4133
|
+
return segments
|
|
3685
4134
|
}
|
|
3686
4135
|
|
|
3687
4136
|
const TOGGLE_HIGHLIGHT_COMMAND = createCommand();
|
|
@@ -3966,11 +4415,15 @@ function $extractHighlightRangesFromCodeNode(codeNode) {
|
|
|
3966
4415
|
|
|
3967
4416
|
function buildCanonicalizers(config) {
|
|
3968
4417
|
return [
|
|
3969
|
-
new StyleCanonicalizer("color", [ ...config.buttons.color, ...config.permit.color ]),
|
|
3970
|
-
new StyleCanonicalizer("background-color", [ ...config.buttons["background-color"], ...config.permit["background-color"] ])
|
|
4418
|
+
new StyleCanonicalizer("color", [ ...colorValues(config.buttons.color), ...config.permit.color ]),
|
|
4419
|
+
new StyleCanonicalizer("background-color", [ ...colorValues(config.buttons["background-color"]), ...config.permit["background-color"] ])
|
|
3971
4420
|
]
|
|
3972
4421
|
}
|
|
3973
4422
|
|
|
4423
|
+
function colorValues(buttons) {
|
|
4424
|
+
return normalizeColorButtons(buttons).map((button) => button.value)
|
|
4425
|
+
}
|
|
4426
|
+
|
|
3974
4427
|
function $toggleSelectionStyles(editor, styles) {
|
|
3975
4428
|
const selection = $getSelection();
|
|
3976
4429
|
if (!$isRangeSelection(selection)) return
|
|
@@ -4909,29 +5362,25 @@ class Selection {
|
|
|
4909
5362
|
}, COMMAND_PRIORITY_LOW),
|
|
4910
5363
|
|
|
4911
5364
|
this.editor.registerCommand(SELECTION_CHANGE_COMMAND, () => {
|
|
4912
|
-
this.#
|
|
5365
|
+
this.#preserveFocusOutsideEditor();
|
|
4913
5366
|
return false
|
|
4914
5367
|
}, COMMAND_PRIORITY_CRITICAL)
|
|
4915
5368
|
);
|
|
4916
5369
|
}
|
|
4917
5370
|
|
|
4918
|
-
//
|
|
4919
|
-
//
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
// tag the update so Lexical leaves the DOM selection (and with it, focus) alone.
|
|
4923
|
-
#preserveFocusOfOtherFields() {
|
|
4924
|
-
if (this.#anotherFieldIsFocused) {
|
|
5371
|
+
// Selection changes can arrive after Tab moves focus to a toolbar or another field.
|
|
5372
|
+
// Reconciling that stale selection would pull focus back into the editor.
|
|
5373
|
+
#preserveFocusOutsideEditor() {
|
|
5374
|
+
if (this.#isAnotherElementFocused) {
|
|
4925
5375
|
$addUpdateTag(SKIP_DOM_SELECTION_TAG);
|
|
4926
5376
|
}
|
|
4927
5377
|
}
|
|
4928
5378
|
|
|
4929
|
-
get #
|
|
5379
|
+
get #isAnotherElementFocused() {
|
|
4930
5380
|
const rootElement = this.editor.getRootElement();
|
|
4931
5381
|
const activeElement = rootElement?.ownerDocument.activeElement;
|
|
4932
5382
|
|
|
4933
|
-
return activeElement && !rootElement.contains(activeElement)
|
|
4934
|
-
(activeElement.tagName === "INPUT" || activeElement.tagName === "TEXTAREA" || activeElement.isContentEditable)
|
|
5383
|
+
return activeElement && activeElement !== rootElement.ownerDocument.body && !rootElement.contains(activeElement)
|
|
4935
5384
|
}
|
|
4936
5385
|
|
|
4937
5386
|
#listenForNodeSelections() {
|
|
@@ -5464,240 +5913,68 @@ class Selection {
|
|
|
5464
5913
|
#getEdgeCharRect(element, edge) {
|
|
5465
5914
|
const walker = document.createTreeWalker(element, 4 /* NodeFilter.SHOW_TEXT */);
|
|
5466
5915
|
let textNode;
|
|
5467
|
-
|
|
5468
|
-
if (edge === "first") {
|
|
5469
|
-
textNode = walker.nextNode();
|
|
5470
|
-
} else {
|
|
5471
|
-
while (walker.nextNode()) textNode = walker.currentNode;
|
|
5472
|
-
}
|
|
5473
|
-
|
|
5474
|
-
if (!textNode || textNode.length === 0) return null
|
|
5475
|
-
|
|
5476
|
-
const range = document.createRange();
|
|
5477
|
-
if (edge === "first") {
|
|
5478
|
-
range.setStart(textNode, 0);
|
|
5479
|
-
range.setEnd(textNode, 1);
|
|
5480
|
-
} else {
|
|
5481
|
-
range.setStart(textNode, textNode.length - 1);
|
|
5482
|
-
range.setEnd(textNode, textNode.length);
|
|
5483
|
-
}
|
|
5484
|
-
|
|
5485
|
-
return range.getBoundingClientRect()
|
|
5486
|
-
}
|
|
5487
|
-
}
|
|
5488
|
-
|
|
5489
|
-
class EditorConfiguration {
|
|
5490
|
-
#editorElement
|
|
5491
|
-
#config
|
|
5492
|
-
|
|
5493
|
-
constructor(editorElement) {
|
|
5494
|
-
this.#editorElement = editorElement;
|
|
5495
|
-
this.#config = new Configuration(
|
|
5496
|
-
Lexxy.presets.get("default"),
|
|
5497
|
-
Lexxy.presets.get(editorElement.preset),
|
|
5498
|
-
this.#overrides
|
|
5499
|
-
);
|
|
5500
|
-
}
|
|
5501
|
-
|
|
5502
|
-
get(path) {
|
|
5503
|
-
return this.#config.get(path)
|
|
5504
|
-
}
|
|
5505
|
-
|
|
5506
|
-
get #overrides() {
|
|
5507
|
-
const overrides = {};
|
|
5508
|
-
for (const option of this.#defaultOptions) {
|
|
5509
|
-
const attribute = dasherize(option);
|
|
5510
|
-
if (this.#editorElement.hasAttribute(attribute)) {
|
|
5511
|
-
overrides[option] = this.#parseAttribute(attribute);
|
|
5512
|
-
}
|
|
5513
|
-
}
|
|
5514
|
-
return overrides
|
|
5515
|
-
}
|
|
5516
|
-
|
|
5517
|
-
get #defaultOptions() {
|
|
5518
|
-
return Object.keys(Lexxy.presets.get("default"))
|
|
5519
|
-
}
|
|
5520
|
-
|
|
5521
|
-
#parseAttribute(attribute) {
|
|
5522
|
-
const value = this.#editorElement.getAttribute(attribute);
|
|
5523
|
-
try {
|
|
5524
|
-
return JSON.parse(value)
|
|
5525
|
-
} catch {
|
|
5526
|
-
return value
|
|
5527
|
-
}
|
|
5528
|
-
}
|
|
5529
|
-
}
|
|
5530
|
-
|
|
5531
|
-
class ImageGalleryNode extends ElementNode {
|
|
5532
|
-
$config() {
|
|
5533
|
-
return this.config("image_gallery", {
|
|
5534
|
-
extends: ElementNode,
|
|
5535
|
-
})
|
|
5536
|
-
}
|
|
5537
|
-
|
|
5538
|
-
static transform() {
|
|
5539
|
-
return (gallery) => {
|
|
5540
|
-
gallery.unwrapEmptyNode()
|
|
5541
|
-
|| gallery.replaceWithSingularChild()
|
|
5542
|
-
|| gallery.splitAroundInvalidChild();
|
|
5543
|
-
}
|
|
5544
|
-
}
|
|
5545
|
-
|
|
5546
|
-
static importDOM() {
|
|
5547
|
-
return {
|
|
5548
|
-
div: (element) => {
|
|
5549
|
-
if (!this.#isGalleryElement(element)) return null
|
|
5550
|
-
|
|
5551
|
-
return {
|
|
5552
|
-
conversion: () => {
|
|
5553
|
-
return {
|
|
5554
|
-
node: $createImageGalleryNode()
|
|
5555
|
-
}
|
|
5556
|
-
},
|
|
5557
|
-
priority: 2
|
|
5558
|
-
}
|
|
5559
|
-
}
|
|
5560
|
-
}
|
|
5561
|
-
}
|
|
5562
|
-
|
|
5563
|
-
static canCollapseWith(node) {
|
|
5564
|
-
return $isImageGalleryNode(node) || this.isValidChild(node)
|
|
5565
|
-
}
|
|
5566
|
-
|
|
5567
|
-
static isValidChild(node) {
|
|
5568
|
-
return $isActionTextAttachmentNode(node) && node.isPreviewableImage
|
|
5569
|
-
}
|
|
5570
|
-
|
|
5571
|
-
static #isGalleryElement(element) {
|
|
5572
|
-
const attachmentChildren = element.querySelectorAll(`:scope > :is(${this.#attachmentTags.join()})`);
|
|
5573
|
-
return element.textContent.trim() === ""
|
|
5574
|
-
&& attachmentChildren.length > 0
|
|
5575
|
-
&& element.children.length === attachmentChildren.length
|
|
5576
|
-
}
|
|
5577
|
-
|
|
5578
|
-
static get #attachmentTags() {
|
|
5579
|
-
return Object.keys(ActionTextAttachmentNode.importDOM())
|
|
5580
|
-
}
|
|
5581
|
-
|
|
5582
|
-
createDOM() {
|
|
5583
|
-
const div = document.createElement("div");
|
|
5584
|
-
div.className = this.#galleryClassNames;
|
|
5585
|
-
return div
|
|
5586
|
-
}
|
|
5587
|
-
|
|
5588
|
-
updateDOM(_prevNode, dom) {
|
|
5589
|
-
dom.className = this.#galleryClassNames;
|
|
5590
|
-
return false
|
|
5591
|
-
}
|
|
5592
|
-
|
|
5593
|
-
canBeEmpty() {
|
|
5594
|
-
// Return `true` to conform to `$isBlock(node)`
|
|
5595
|
-
// We clean-up empty galleries with a transform
|
|
5596
|
-
return true
|
|
5597
|
-
}
|
|
5598
|
-
|
|
5599
|
-
collapseAtStart(_selection) {
|
|
5600
|
-
return true
|
|
5601
|
-
}
|
|
5602
|
-
|
|
5603
|
-
insertNewAfter(selection, restoreSelection) {
|
|
5604
|
-
const selectionBeforeLastChild = selection.anchor.getNode().is(this) && selection.anchor.offset == this.getChildrenSize() - 1;
|
|
5605
|
-
if (selectionBeforeLastChild) {
|
|
5606
|
-
const paragraph = $createParagraphNode();
|
|
5607
|
-
this.insertAfter(paragraph, false);
|
|
5608
|
-
paragraph.insertAfter(this.getLastChild(), false);
|
|
5609
|
-
paragraph.selectEnd();
|
|
5610
|
-
|
|
5611
|
-
// return null as selection has been managed
|
|
5612
|
-
return null
|
|
5613
|
-
}
|
|
5614
|
-
|
|
5615
|
-
const newNode = $createImageGalleryNode();
|
|
5616
|
-
this.insertAfter(newNode, restoreSelection);
|
|
5617
|
-
return newNode
|
|
5618
|
-
}
|
|
5619
|
-
|
|
5620
|
-
getImageAttachments() {
|
|
5621
|
-
const children = this.getChildren();
|
|
5622
|
-
return children.filter($isActionTextAttachmentNode)
|
|
5623
|
-
}
|
|
5624
|
-
|
|
5625
|
-
exportDOM() {
|
|
5626
|
-
const div = document.createElement("div");
|
|
5627
|
-
div.className = this.#galleryClassNames;
|
|
5628
|
-
return { element: div }
|
|
5629
|
-
}
|
|
5630
|
-
|
|
5631
|
-
collapseWith(node, backwards) {
|
|
5632
|
-
if (!ImageGalleryNode.canCollapseWith(node)) return false
|
|
5633
|
-
|
|
5634
|
-
if (backwards) {
|
|
5635
|
-
$insertFirst(this, node);
|
|
5916
|
+
|
|
5917
|
+
if (edge === "first") {
|
|
5918
|
+
textNode = walker.nextNode();
|
|
5636
5919
|
} else {
|
|
5637
|
-
|
|
5920
|
+
while (walker.nextNode()) textNode = walker.currentNode;
|
|
5638
5921
|
}
|
|
5639
5922
|
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
return true
|
|
5643
|
-
}
|
|
5923
|
+
if (!textNode || textNode.length === 0) return null
|
|
5644
5924
|
|
|
5645
|
-
|
|
5646
|
-
if (
|
|
5647
|
-
|
|
5648
|
-
|
|
5925
|
+
const range = document.createRange();
|
|
5926
|
+
if (edge === "first") {
|
|
5927
|
+
range.setStart(textNode, 0);
|
|
5928
|
+
range.setEnd(textNode, 1);
|
|
5929
|
+
} else {
|
|
5930
|
+
range.setStart(textNode, textNode.length - 1);
|
|
5931
|
+
range.setEnd(textNode, textNode.length);
|
|
5649
5932
|
}
|
|
5650
|
-
}
|
|
5651
5933
|
|
|
5652
|
-
|
|
5653
|
-
if (this.#hasSingularChild) {
|
|
5654
|
-
const child = this.getFirstChild();
|
|
5655
|
-
return this.replace($makeSafeForRoot(child))
|
|
5656
|
-
}
|
|
5934
|
+
return range.getBoundingClientRect()
|
|
5657
5935
|
}
|
|
5936
|
+
}
|
|
5658
5937
|
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
const poppedNode = $makeSafeForRoot(child);
|
|
5664
|
-
const [ topGallery, secondGallery ] = this.splitAtIndex(poppedNode.getIndexWithinParent());
|
|
5665
|
-
topGallery.insertAfter(poppedNode);
|
|
5666
|
-
poppedNode.selectEnd();
|
|
5667
|
-
|
|
5668
|
-
// remove an empty gallery rather than let it unwrap to a paragraph
|
|
5669
|
-
if (secondGallery.isEmpty()) secondGallery.remove();
|
|
5938
|
+
class EditorConfiguration {
|
|
5939
|
+
#editorElement
|
|
5940
|
+
#config
|
|
5670
5941
|
|
|
5671
|
-
|
|
5672
|
-
|
|
5942
|
+
constructor(editorElement) {
|
|
5943
|
+
this.#editorElement = editorElement;
|
|
5944
|
+
this.#config = new Configuration(
|
|
5945
|
+
Lexxy.presets.get("default"),
|
|
5946
|
+
Lexxy.presets.get(editorElement.preset),
|
|
5947
|
+
this.#overrides
|
|
5948
|
+
);
|
|
5673
5949
|
}
|
|
5674
5950
|
|
|
5675
|
-
|
|
5676
|
-
return
|
|
5951
|
+
get(path) {
|
|
5952
|
+
return this.#config.get(path)
|
|
5677
5953
|
}
|
|
5678
5954
|
|
|
5679
|
-
get #
|
|
5680
|
-
|
|
5955
|
+
get #overrides() {
|
|
5956
|
+
const overrides = {};
|
|
5957
|
+
for (const option of this.#defaultOptions) {
|
|
5958
|
+
const attribute = dasherize(option);
|
|
5959
|
+
if (this.#editorElement.hasAttribute(attribute)) {
|
|
5960
|
+
overrides[option] = this.#parseAttribute(attribute);
|
|
5961
|
+
}
|
|
5962
|
+
}
|
|
5963
|
+
return overrides
|
|
5681
5964
|
}
|
|
5682
5965
|
|
|
5683
|
-
get #
|
|
5684
|
-
return
|
|
5966
|
+
get #defaultOptions() {
|
|
5967
|
+
return Object.keys(Lexxy.presets.get("default"))
|
|
5685
5968
|
}
|
|
5686
|
-
}
|
|
5687
|
-
|
|
5688
|
-
function $createImageGalleryNode() {
|
|
5689
|
-
return new ImageGalleryNode()
|
|
5690
|
-
}
|
|
5691
|
-
|
|
5692
|
-
function $isImageGalleryNode(node) {
|
|
5693
|
-
return node instanceof ImageGalleryNode
|
|
5694
|
-
}
|
|
5695
5969
|
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
|
|
5970
|
+
#parseAttribute(attribute) {
|
|
5971
|
+
const value = this.#editorElement.getAttribute(attribute);
|
|
5972
|
+
try {
|
|
5973
|
+
return JSON.parse(value)
|
|
5974
|
+
} catch {
|
|
5975
|
+
return value
|
|
5976
|
+
}
|
|
5977
|
+
}
|
|
5701
5978
|
}
|
|
5702
5979
|
|
|
5703
5980
|
class Uploader {
|
|
@@ -8788,75 +9065,387 @@ class AttachmentDragAndDrop {
|
|
|
8788
9065
|
if (position === "before") {
|
|
8789
9066
|
targetNode.insertBefore(draggedNode);
|
|
8790
9067
|
} else {
|
|
8791
|
-
targetNode.insertAfter(draggedNode);
|
|
9068
|
+
targetNode.insertAfter(draggedNode);
|
|
9069
|
+
}
|
|
9070
|
+
}
|
|
9071
|
+
|
|
9072
|
+
#dropIntoList(draggedNode, target) {
|
|
9073
|
+
const listItemNode = $getNearestNodeFromDOMNode(target.element);
|
|
9074
|
+
if (!listItemNode || !$isListItemNode(listItemNode)) return
|
|
9075
|
+
|
|
9076
|
+
const listNode = listItemNode.getParent();
|
|
9077
|
+
if (!listNode || !$isListNode(listNode)) return
|
|
9078
|
+
|
|
9079
|
+
const children = listNode.getChildren();
|
|
9080
|
+
const index = children.indexOf(listItemNode);
|
|
9081
|
+
if (index === -1) return
|
|
9082
|
+
|
|
9083
|
+
const splitIndex = target.position === "before" ? index : index + 1;
|
|
9084
|
+
|
|
9085
|
+
draggedNode.remove();
|
|
9086
|
+
|
|
9087
|
+
if (splitIndex === 0) {
|
|
9088
|
+
listNode.insertBefore(draggedNode);
|
|
9089
|
+
} else if (splitIndex >= children.length) {
|
|
9090
|
+
listNode.insertAfter(draggedNode);
|
|
9091
|
+
} else {
|
|
9092
|
+
const [ , listAfter ] = $splitNode(listNode, splitIndex);
|
|
9093
|
+
listAfter.insertBefore(draggedNode);
|
|
9094
|
+
}
|
|
9095
|
+
}
|
|
9096
|
+
|
|
9097
|
+
#dropBetweenBlocks(draggedNode, target) {
|
|
9098
|
+
const targetNode = $getNearestNodeFromDOMNode(target.element);
|
|
9099
|
+
if (!targetNode) return
|
|
9100
|
+
|
|
9101
|
+
const topLevelTarget = targetNode.getTopLevelElement?.() || targetNode;
|
|
9102
|
+
if (draggedNode.is(topLevelTarget)) return
|
|
9103
|
+
|
|
9104
|
+
draggedNode.remove();
|
|
9105
|
+
|
|
9106
|
+
if (target.position === "before") {
|
|
9107
|
+
topLevelTarget.insertBefore(draggedNode);
|
|
9108
|
+
} else {
|
|
9109
|
+
topLevelTarget.insertAfter(draggedNode);
|
|
9110
|
+
}
|
|
9111
|
+
}
|
|
9112
|
+
|
|
9113
|
+
// -- Lifecycle helpers -----------------------------------------------------
|
|
9114
|
+
|
|
9115
|
+
#cleanup() {
|
|
9116
|
+
this.#clearDropIndicators();
|
|
9117
|
+
|
|
9118
|
+
if (this.#draggedNodeKey) {
|
|
9119
|
+
const rootElement = this.#editor.getRootElement();
|
|
9120
|
+
if (rootElement) {
|
|
9121
|
+
const figure = rootElement.querySelector(`[data-lexical-node-key="${this.#draggedNodeKey}"]`);
|
|
9122
|
+
figure?.classList.remove("lexxy-dragging");
|
|
9123
|
+
}
|
|
9124
|
+
}
|
|
9125
|
+
|
|
9126
|
+
this.#draggedNodeKey = null;
|
|
9127
|
+
|
|
9128
|
+
if (this.#rafId) {
|
|
9129
|
+
cancelAnimationFrame(this.#rafId);
|
|
9130
|
+
this.#rafId = null;
|
|
9131
|
+
}
|
|
9132
|
+
|
|
9133
|
+
if (this.#draggingRafId) {
|
|
9134
|
+
cancelAnimationFrame(this.#draggingRafId);
|
|
9135
|
+
this.#draggingRafId = null;
|
|
9136
|
+
}
|
|
9137
|
+
}
|
|
9138
|
+
}
|
|
9139
|
+
|
|
9140
|
+
// Lexical clears the DOM range on NodeSelection, leaving screen readers in focus
|
|
9141
|
+
// mode to read stray adjacent characters unless we provide a labelled range.
|
|
9142
|
+
class AttachmentFakeSelection {
|
|
9143
|
+
#editor
|
|
9144
|
+
#listeners = new ListenerBin()
|
|
9145
|
+
#container = createElement("span", { className: "lexxy-fake-selection" })
|
|
9146
|
+
|
|
9147
|
+
constructor(editor) {
|
|
9148
|
+
this.#editor = editor;
|
|
9149
|
+
this.#listeners.track(
|
|
9150
|
+
registerLabelledDecoratorSelection(editor, (target) => this.#update(target))
|
|
9151
|
+
);
|
|
9152
|
+
}
|
|
9153
|
+
|
|
9154
|
+
destroy() {
|
|
9155
|
+
this.#listeners.dispose();
|
|
9156
|
+
this.#container.remove();
|
|
9157
|
+
this.#container = null;
|
|
9158
|
+
}
|
|
9159
|
+
|
|
9160
|
+
#update(target) {
|
|
9161
|
+
if (this.#container) {
|
|
9162
|
+
const figure = target && this.#editor.getElementByKey(target.key);
|
|
9163
|
+
if (figure) {
|
|
9164
|
+
const label = target.label || "Attachment";
|
|
9165
|
+
if (this.#container.textContent !== label) this.#container.textContent = label;
|
|
9166
|
+
if (this.#container.parentNode !== figure) figure.append(this.#container);
|
|
9167
|
+
this.#parkSelection();
|
|
9168
|
+
} else {
|
|
9169
|
+
this.#container.remove();
|
|
9170
|
+
}
|
|
9171
|
+
}
|
|
9172
|
+
}
|
|
9173
|
+
|
|
9174
|
+
#parkSelection() {
|
|
9175
|
+
const textNode = this.#container.firstChild;
|
|
9176
|
+
if (textNode && this.#isEditorFocused && !this.#isAlreadyParkedHere) {
|
|
9177
|
+
const range = document.createRange();
|
|
9178
|
+
range.setStart(textNode, 0);
|
|
9179
|
+
range.setEnd(textNode, textNode.length);
|
|
9180
|
+
|
|
9181
|
+
const selection = window.getSelection();
|
|
9182
|
+
selection.removeAllRanges();
|
|
9183
|
+
selection.addRange(range);
|
|
9184
|
+
}
|
|
9185
|
+
}
|
|
9186
|
+
|
|
9187
|
+
// Parking the range while a nested control is focused steals its screen reader cursor.
|
|
9188
|
+
get #isEditorFocused() {
|
|
9189
|
+
const root = this.#editor.getRootElement();
|
|
9190
|
+
return root != null && document.activeElement === root
|
|
9191
|
+
}
|
|
9192
|
+
|
|
9193
|
+
get #isAlreadyParkedHere() {
|
|
9194
|
+
const selection = window.getSelection();
|
|
9195
|
+
if (selection?.rangeCount === 1) {
|
|
9196
|
+
const textNode = this.#container.firstChild;
|
|
9197
|
+
return selection.anchorNode === textNode
|
|
9198
|
+
&& selection.focusNode === textNode
|
|
9199
|
+
&& selection.anchorOffset === 0
|
|
9200
|
+
&& selection.focusOffset === textNode.length
|
|
9201
|
+
} else {
|
|
9202
|
+
return false
|
|
9203
|
+
}
|
|
9204
|
+
}
|
|
9205
|
+
}
|
|
9206
|
+
|
|
9207
|
+
class AttachmentKeyboardMove {
|
|
9208
|
+
#editor
|
|
9209
|
+
#listeners = new ListenerBin()
|
|
9210
|
+
#scrollFrame
|
|
9211
|
+
|
|
9212
|
+
constructor(editor) {
|
|
9213
|
+
this.#editor = editor;
|
|
9214
|
+
|
|
9215
|
+
this.#listeners.track(
|
|
9216
|
+
editor.registerCommand(KEY_DOWN_COMMAND, this.#tryReorder, COMMAND_PRIORITY_HIGH)
|
|
9217
|
+
);
|
|
9218
|
+
}
|
|
9219
|
+
|
|
9220
|
+
destroy() {
|
|
9221
|
+
this.#listeners.dispose();
|
|
9222
|
+
cancelAnimationFrame(this.#scrollFrame);
|
|
9223
|
+
}
|
|
9224
|
+
|
|
9225
|
+
#tryReorder = (event) => {
|
|
9226
|
+
const node = $singleSelectedNode();
|
|
9227
|
+
if (event.altKey && event.shiftKey && event.key.startsWith("Arrow") && $isActionTextAttachmentNode(node)) {
|
|
9228
|
+
event.preventDefault();
|
|
9229
|
+
const message = this.#executeMove(event.key, node);
|
|
9230
|
+
$setSelection($createNodeSelectionWith(node));
|
|
9231
|
+
announceFromEditor(this.#editor, message);
|
|
9232
|
+
this.#keepInView(node);
|
|
9233
|
+
return true
|
|
9234
|
+
} else {
|
|
9235
|
+
return false
|
|
9236
|
+
}
|
|
9237
|
+
}
|
|
9238
|
+
|
|
9239
|
+
#executeMove(key, node) {
|
|
9240
|
+
switch (key) {
|
|
9241
|
+
case "ArrowUp": return new AttachmentReorder(node, "previous").moveVertically()
|
|
9242
|
+
case "ArrowDown": return new AttachmentReorder(node, "next").moveVertically()
|
|
9243
|
+
case "ArrowLeft": return new AttachmentReorder(node, "previous").reorderInGallery()
|
|
9244
|
+
case "ArrowRight": return new AttachmentReorder(node, "next").reorderInGallery()
|
|
9245
|
+
default: return null
|
|
9246
|
+
}
|
|
9247
|
+
}
|
|
9248
|
+
|
|
9249
|
+
// Node selections don't scroll into view automatically like a caret does.
|
|
9250
|
+
#keepInView(node) {
|
|
9251
|
+
const key = node.getKey();
|
|
9252
|
+
cancelAnimationFrame(this.#scrollFrame);
|
|
9253
|
+
this.#scrollFrame = requestAnimationFrame(() => {
|
|
9254
|
+
this.#editor.getElementByKey(key)?.scrollIntoView({ block: "nearest" });
|
|
9255
|
+
});
|
|
9256
|
+
}
|
|
9257
|
+
}
|
|
9258
|
+
|
|
9259
|
+
class AttachmentReorder {
|
|
9260
|
+
constructor(node, direction) {
|
|
9261
|
+
this.node = node;
|
|
9262
|
+
this.direction = direction;
|
|
9263
|
+
}
|
|
9264
|
+
|
|
9265
|
+
moveVertically() {
|
|
9266
|
+
if ($isImageGalleryNode(this.node.getParent())) {
|
|
9267
|
+
return this.#extractFromGallery()
|
|
9268
|
+
} else {
|
|
9269
|
+
return this.#moveBlock() || this.#blockedMessage
|
|
9270
|
+
}
|
|
9271
|
+
}
|
|
9272
|
+
|
|
9273
|
+
reorderInGallery() {
|
|
9274
|
+
const sibling = $getSiblingCaret(this.node, this.direction).getNodeAtCaret();
|
|
9275
|
+
if ($isImageGalleryNode(this.node.getParent()) && sibling) {
|
|
9276
|
+
$getSiblingCaret(sibling, this.direction).insert(this.node);
|
|
9277
|
+
return "Image reordered in gallery"
|
|
9278
|
+
} else {
|
|
9279
|
+
return this.#blockedMessage
|
|
9280
|
+
}
|
|
9281
|
+
}
|
|
9282
|
+
|
|
9283
|
+
#extractFromGallery() {
|
|
9284
|
+
const gallery = this.node.getParent();
|
|
9285
|
+
$getSiblingCaret(gallery, this.direction).insert(this.node);
|
|
9286
|
+
return "Image removed from gallery"
|
|
9287
|
+
}
|
|
9288
|
+
|
|
9289
|
+
#moveBlock() {
|
|
9290
|
+
const target = this.#skipProvisional($getSiblingCaret(this.node, this.direction).getNodeAtCaret());
|
|
9291
|
+
if (this.#canJoinGallery(target)) {
|
|
9292
|
+
return this.#joinGallery(target)
|
|
9293
|
+
} else if (this.#canFormGalleryWith(target)) {
|
|
9294
|
+
return this.#formGalleryWith(target)
|
|
9295
|
+
} else if (target) {
|
|
9296
|
+
return this.#swapWith(target)
|
|
9297
|
+
} else {
|
|
9298
|
+
return null
|
|
9299
|
+
}
|
|
9300
|
+
}
|
|
9301
|
+
|
|
9302
|
+
#skipProvisional(node) {
|
|
9303
|
+
let current = node;
|
|
9304
|
+
while (current && $isProvisionalParagraphNode(current)) {
|
|
9305
|
+
current = $getSiblingCaret(current, this.direction).getNodeAtCaret();
|
|
9306
|
+
}
|
|
9307
|
+
return current
|
|
9308
|
+
}
|
|
9309
|
+
|
|
9310
|
+
#canJoinGallery(target) {
|
|
9311
|
+
return $isImageGalleryNode(target) && ImageGalleryNode.isValidChild(this.node)
|
|
9312
|
+
}
|
|
9313
|
+
|
|
9314
|
+
#joinGallery(gallery) {
|
|
9315
|
+
$getChildCaret(gallery, this.direction).insert(this.node);
|
|
9316
|
+
return "Image added to gallery"
|
|
9317
|
+
}
|
|
9318
|
+
|
|
9319
|
+
#canFormGalleryWith(target) {
|
|
9320
|
+
return $isActionTextAttachmentNode(target)
|
|
9321
|
+
&& target.isPreviewableImage
|
|
9322
|
+
&& this.node.isPreviewableImage
|
|
9323
|
+
}
|
|
9324
|
+
|
|
9325
|
+
#formGalleryWith(target) {
|
|
9326
|
+
if ($findOrCreateGalleryForImage(target)) {
|
|
9327
|
+
$getSiblingCaret(target, flipDirection(this.direction)).insert(this.node);
|
|
9328
|
+
return "Gallery created"
|
|
9329
|
+
} else {
|
|
9330
|
+
return null
|
|
9331
|
+
}
|
|
9332
|
+
}
|
|
9333
|
+
|
|
9334
|
+
#swapWith(target) {
|
|
9335
|
+
$getSiblingCaret(target, this.direction).insert(this.node);
|
|
9336
|
+
if (this.direction === "next") {
|
|
9337
|
+
return "Attachment moved down"
|
|
9338
|
+
} else {
|
|
9339
|
+
return "Attachment moved up"
|
|
8792
9340
|
}
|
|
8793
9341
|
}
|
|
8794
9342
|
|
|
8795
|
-
#
|
|
8796
|
-
|
|
8797
|
-
|
|
9343
|
+
get #blockedMessage() {
|
|
9344
|
+
if (this.direction === "next") {
|
|
9345
|
+
return "Already at the end"
|
|
9346
|
+
} else {
|
|
9347
|
+
return "Already at the start"
|
|
9348
|
+
}
|
|
9349
|
+
}
|
|
9350
|
+
}
|
|
8798
9351
|
|
|
8799
|
-
|
|
8800
|
-
|
|
9352
|
+
// Using a mention's label as avatar alt text can interrupt line-by-line reading,
|
|
9353
|
+
// but character navigation needs it near the caret to announce the mention.
|
|
9354
|
+
class CustomAttachmentLabels {
|
|
9355
|
+
#editor
|
|
9356
|
+
#listeners = new ListenerBin()
|
|
9357
|
+
#active = null
|
|
8801
9358
|
|
|
8802
|
-
|
|
8803
|
-
|
|
8804
|
-
|
|
9359
|
+
constructor(editor) {
|
|
9360
|
+
this.#editor = editor;
|
|
9361
|
+
this.#listeners.track(
|
|
9362
|
+
editor.registerUpdateListener(this.#updateLabel),
|
|
9363
|
+
// A blur command opens a Lexical update that can pull focus back into the editor.
|
|
9364
|
+
editor.registerRootListener(rootElement => {
|
|
9365
|
+
if (rootElement) {
|
|
9366
|
+
return registerEventListener(rootElement, "blur", () => this.#restoreLabel())
|
|
9367
|
+
}
|
|
9368
|
+
})
|
|
9369
|
+
);
|
|
9370
|
+
}
|
|
8805
9371
|
|
|
8806
|
-
|
|
9372
|
+
destroy() {
|
|
9373
|
+
this.#listeners.dispose();
|
|
9374
|
+
this.#restoreLabel();
|
|
9375
|
+
}
|
|
8807
9376
|
|
|
8808
|
-
|
|
9377
|
+
#updateLabel = ({ editorState }) => {
|
|
9378
|
+
if (document.activeElement === this.#editor.getRootElement()) {
|
|
9379
|
+
editorState.read(() => {
|
|
9380
|
+
const selection = $getSelection();
|
|
9381
|
+
const attachment = customAttachmentSelectedBy(selection) || customAttachmentBesideAnchor(selection);
|
|
8809
9382
|
|
|
8810
|
-
|
|
8811
|
-
|
|
8812
|
-
|
|
8813
|
-
|
|
9383
|
+
if (attachment) {
|
|
9384
|
+
this.#exposeLabelOn(attachment);
|
|
9385
|
+
} else {
|
|
9386
|
+
this.#restoreLabel();
|
|
9387
|
+
}
|
|
9388
|
+
});
|
|
8814
9389
|
} else {
|
|
8815
|
-
|
|
8816
|
-
listAfter.insertBefore(draggedNode);
|
|
9390
|
+
this.#restoreLabel();
|
|
8817
9391
|
}
|
|
8818
9392
|
}
|
|
8819
9393
|
|
|
8820
|
-
#
|
|
8821
|
-
const
|
|
8822
|
-
if (
|
|
8823
|
-
|
|
8824
|
-
|
|
8825
|
-
|
|
9394
|
+
#exposeLabelOn(attachment) {
|
|
9395
|
+
const figure = this.#editor.getElementByKey(attachment.getKey());
|
|
9396
|
+
if (figure && figure !== this.#active?.figure) {
|
|
9397
|
+
this.#restoreLabel();
|
|
9398
|
+
attachment.exposeLabel(figure);
|
|
9399
|
+
this.#active = { figure, attachment };
|
|
9400
|
+
}
|
|
9401
|
+
}
|
|
8826
9402
|
|
|
8827
|
-
|
|
9403
|
+
#restoreLabel() {
|
|
9404
|
+
if (this.#active) {
|
|
9405
|
+
const { attachment, figure } = this.#active;
|
|
9406
|
+
attachment.restoreLabel(figure);
|
|
9407
|
+
this.#active = null;
|
|
9408
|
+
}
|
|
9409
|
+
}
|
|
9410
|
+
}
|
|
8828
9411
|
|
|
8829
|
-
|
|
8830
|
-
|
|
9412
|
+
function customAttachmentSelectedBy(selection) {
|
|
9413
|
+
if ($isNodeSelection(selection)) {
|
|
9414
|
+
const nodes = selection.getNodes();
|
|
9415
|
+
if (nodes.length === 1 && $isCustomActionTextAttachmentNode(nodes[0])) {
|
|
9416
|
+
return nodes[0]
|
|
8831
9417
|
} else {
|
|
8832
|
-
|
|
9418
|
+
return null
|
|
8833
9419
|
}
|
|
9420
|
+
} else {
|
|
9421
|
+
return null
|
|
8834
9422
|
}
|
|
9423
|
+
}
|
|
8835
9424
|
|
|
8836
|
-
|
|
8837
|
-
|
|
8838
|
-
|
|
8839
|
-
|
|
9425
|
+
// Screen readers need the label one character early to announce it as the caret
|
|
9426
|
+
// crosses the figure.
|
|
9427
|
+
function customAttachmentBesideAnchor(selection) {
|
|
9428
|
+
if ($isRangeSelection(selection) && selection.isCollapsed()) {
|
|
9429
|
+
const anchor = selection.anchor;
|
|
9430
|
+
const parent = anchor.getNode();
|
|
9431
|
+
const candidates = [];
|
|
8840
9432
|
|
|
8841
|
-
if (
|
|
8842
|
-
const
|
|
8843
|
-
if (
|
|
8844
|
-
|
|
8845
|
-
|
|
8846
|
-
}
|
|
9433
|
+
if (anchor.type === "text") {
|
|
9434
|
+
const toEnd = parent.getTextContentSize() - anchor.offset;
|
|
9435
|
+
if (toEnd <= 1) candidates.push({ node: parent.getNextSibling(), distance: toEnd });
|
|
9436
|
+
if (anchor.offset <= 1) candidates.push({ node: parent.getPreviousSibling(), distance: anchor.offset });
|
|
9437
|
+
} else {
|
|
9438
|
+
candidates.push({ node: parent.getChildAtIndex(anchor.offset), distance: 0 });
|
|
9439
|
+
candidates.push({ node: parent.getChildAtIndex(anchor.offset - 1), distance: 0 });
|
|
8847
9440
|
}
|
|
8848
9441
|
|
|
8849
|
-
|
|
8850
|
-
|
|
8851
|
-
|
|
8852
|
-
cancelAnimationFrame(this.#rafId);
|
|
8853
|
-
this.#rafId = null;
|
|
8854
|
-
}
|
|
9442
|
+
const nearest = candidates
|
|
9443
|
+
.filter(({ node }) => $isCustomActionTextAttachmentNode(node))
|
|
9444
|
+
.sort((a, b) => a.distance - b.distance)[0];
|
|
8855
9445
|
|
|
8856
|
-
|
|
8857
|
-
|
|
8858
|
-
|
|
8859
|
-
}
|
|
9446
|
+
return nearest?.node ?? null
|
|
9447
|
+
} else {
|
|
9448
|
+
return null
|
|
8860
9449
|
}
|
|
8861
9450
|
}
|
|
8862
9451
|
|
|
@@ -8886,12 +9475,19 @@ class AttachmentsExtension extends LexxyExtension {
|
|
|
8886
9475
|
],
|
|
8887
9476
|
register: (editor) => {
|
|
8888
9477
|
const dragAndDrop = new AttachmentDragAndDrop(editor);
|
|
9478
|
+
const keyboardMove = new AttachmentKeyboardMove(editor);
|
|
9479
|
+
const fakeSelection = new AttachmentFakeSelection(editor);
|
|
9480
|
+
const customAttachmentLabels = new CustomAttachmentLabels(editor);
|
|
8889
9481
|
|
|
8890
9482
|
return mergeRegister(
|
|
8891
9483
|
editor.registerNodeTransform(ActionTextAttachmentNode, $extractAttachmentFromParagraph),
|
|
8892
9484
|
editor.registerCommand(DELETE_CHARACTER_COMMAND, $collapseIntoGallery, COMMAND_PRIORITY_NORMAL),
|
|
9485
|
+
editor.registerCommand(KEY_TAB_COMMAND, $focusCaptionFromSelectedAttachment, COMMAND_PRIORITY_HIGH),
|
|
8893
9486
|
editor.registerMutationListener(ActionTextAttachmentUploadNode, this.#handleUploadMutations.bind(this)),
|
|
8894
|
-
() => dragAndDrop.destroy()
|
|
9487
|
+
() => dragAndDrop.destroy(),
|
|
9488
|
+
() => keyboardMove.destroy(),
|
|
9489
|
+
() => fakeSelection.destroy(),
|
|
9490
|
+
() => customAttachmentLabels.destroy()
|
|
8895
9491
|
)
|
|
8896
9492
|
}
|
|
8897
9493
|
})
|
|
@@ -8989,6 +9585,16 @@ function $collapseAtGalleryEdge(anchor, backwards) {
|
|
|
8989
9585
|
}
|
|
8990
9586
|
}
|
|
8991
9587
|
|
|
9588
|
+
function $focusCaptionFromSelectedAttachment(event) {
|
|
9589
|
+
const node = $singleSelectedNode();
|
|
9590
|
+
if (!event.shiftKey && $isActionTextAttachmentNode(node) && node.focusCaption()) {
|
|
9591
|
+
event.preventDefault();
|
|
9592
|
+
return true
|
|
9593
|
+
} else {
|
|
9594
|
+
return false
|
|
9595
|
+
}
|
|
9596
|
+
}
|
|
9597
|
+
|
|
8992
9598
|
// Manual selection handling to prevent Lexical merging the gallery with a <p> and unwrapping it
|
|
8993
9599
|
function $moveSelectionBeforeGallery(anchor) {
|
|
8994
9600
|
const previousNode = anchor.getNode().getPreviousSibling();
|
|
@@ -9720,6 +10326,7 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
9720
10326
|
#validity = new Map()
|
|
9721
10327
|
#validationTextArea = document.createElement("textarea")
|
|
9722
10328
|
#uploadRequests
|
|
10329
|
+
#liveRegion
|
|
9723
10330
|
|
|
9724
10331
|
constructor() {
|
|
9725
10332
|
super();
|
|
@@ -9750,6 +10357,10 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
9750
10357
|
this.clipboard = new Clipboard(this);
|
|
9751
10358
|
this.#disposables.push(this.clipboard);
|
|
9752
10359
|
|
|
10360
|
+
this.#liveRegion = this.querySelector("lexxy-live-region") ?? createElement("lexxy-live-region");
|
|
10361
|
+
this.append(this.#liveRegion);
|
|
10362
|
+
this.#disposables.push(this.#liveRegion);
|
|
10363
|
+
|
|
9753
10364
|
this.adapter = new BrowserAdapter();
|
|
9754
10365
|
this.#uploadRequests = new UploadRequests();
|
|
9755
10366
|
|
|
@@ -9895,6 +10506,16 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
9895
10506
|
return dispatch(this, "lexxy:file-accept", { file }, true)
|
|
9896
10507
|
}
|
|
9897
10508
|
|
|
10509
|
+
announce(message) {
|
|
10510
|
+
if (message) {
|
|
10511
|
+
if (typeof document.ariaNotify === "function") {
|
|
10512
|
+
document.ariaNotify(message, { priority: "high" });
|
|
10513
|
+
} else {
|
|
10514
|
+
this.#liveRegion?.announce(message);
|
|
10515
|
+
}
|
|
10516
|
+
}
|
|
10517
|
+
}
|
|
10518
|
+
|
|
9898
10519
|
$generateNodesFromDOM(doc, { editor = this.editor } = {}) {
|
|
9899
10520
|
let nodes = $generateNodesFromDOM(editor, doc);
|
|
9900
10521
|
if ($hasUpdateTag(PASTE_TAG)) nodes = $convertInlineImageDataURIs(nodes, this);
|
|
@@ -10260,6 +10881,9 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
10260
10881
|
);
|
|
10261
10882
|
this.#registerTableComponents();
|
|
10262
10883
|
this.#registerCodeLanguagePicker();
|
|
10884
|
+
if (this.supportsAttachments) {
|
|
10885
|
+
this.#registerAttachmentToolbar();
|
|
10886
|
+
}
|
|
10263
10887
|
if (this.supportsMarkdown) {
|
|
10264
10888
|
const transformers = [ ...TRANSFORMERS, HORIZONTAL_DIVIDER ];
|
|
10265
10889
|
registered.push(
|
|
@@ -10288,6 +10912,15 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
10288
10912
|
this.#disposables.push(codeLanguagePicker);
|
|
10289
10913
|
}
|
|
10290
10914
|
|
|
10915
|
+
#registerAttachmentToolbar() {
|
|
10916
|
+
let attachmentToolbar = this.querySelector("lexxy-attachment-toolbar");
|
|
10917
|
+
attachmentToolbar ??= createElement("lexxy-attachment-toolbar");
|
|
10918
|
+
this.append(attachmentToolbar);
|
|
10919
|
+
this.#disposables.push(attachmentToolbar);
|
|
10920
|
+
this.captionEditor = new CaptionEditor(this);
|
|
10921
|
+
this.#disposables.push(this.captionEditor);
|
|
10922
|
+
}
|
|
10923
|
+
|
|
10291
10924
|
#handleEnter() {
|
|
10292
10925
|
// We can't prevent these externally using regular keydown because Lexical handles it first.
|
|
10293
10926
|
this.#listeners.track(this.editor.registerCommand(
|
|
@@ -10496,21 +11129,22 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
10496
11129
|
// — triggering at most one forced reflow. The previous implementation interleaved
|
|
10497
11130
|
// setProperty/getComputedStyle/removeProperty on the same element, forcing a style
|
|
10498
11131
|
// recalc on every iteration during editor initialization.
|
|
10499
|
-
#resolveColors(property,
|
|
11132
|
+
#resolveColors(property, buttons) {
|
|
10500
11133
|
const container = document.createElement("span");
|
|
10501
11134
|
container.style.display = "none";
|
|
10502
11135
|
|
|
10503
|
-
const resolvers =
|
|
11136
|
+
const resolvers = normalizeColorButtons(buttons).map(({ value, label }) => {
|
|
10504
11137
|
const element = document.createElement("span");
|
|
10505
|
-
element.style.setProperty(property,
|
|
11138
|
+
element.style.setProperty(property, value);
|
|
10506
11139
|
container.appendChild(element);
|
|
10507
|
-
return { element, name:
|
|
11140
|
+
return { element, name: value, label }
|
|
10508
11141
|
});
|
|
10509
11142
|
|
|
10510
11143
|
styleResolverRoot().appendChild(container);
|
|
10511
11144
|
|
|
10512
|
-
const resolved = resolvers.map(({ element, name }) => ({
|
|
11145
|
+
const resolved = resolvers.map(({ element, name, label }) => ({
|
|
10513
11146
|
name,
|
|
11147
|
+
label,
|
|
10514
11148
|
value: window.getComputedStyle(element).getPropertyValue(property)
|
|
10515
11149
|
}));
|
|
10516
11150
|
|
|
@@ -10574,6 +11208,39 @@ function $getReadableTextContent(node) {
|
|
|
10574
11208
|
return node.getTextContent()
|
|
10575
11209
|
}
|
|
10576
11210
|
|
|
11211
|
+
const REMOVAL_DELAY = 1000;
|
|
11212
|
+
|
|
11213
|
+
class LiveRegion extends HTMLElement {
|
|
11214
|
+
#additions = createElement("span", { ariaLive: "assertive", ariaRelevant: "additions" })
|
|
11215
|
+
#removalTimeouts = new Set()
|
|
11216
|
+
|
|
11217
|
+
connectedCallback() {
|
|
11218
|
+
this.replaceChildren(this.#additions);
|
|
11219
|
+
}
|
|
11220
|
+
|
|
11221
|
+
disconnectedCallback() {
|
|
11222
|
+
this.dispose();
|
|
11223
|
+
}
|
|
11224
|
+
|
|
11225
|
+
dispose() {
|
|
11226
|
+
for (const timeout of this.#removalTimeouts) clearTimeout(timeout);
|
|
11227
|
+
this.#removalTimeouts.clear();
|
|
11228
|
+
this.#additions.replaceChildren();
|
|
11229
|
+
this.replaceChildren();
|
|
11230
|
+
}
|
|
11231
|
+
|
|
11232
|
+
announce(message) {
|
|
11233
|
+
const announcement = createElement("div", { textContent: message });
|
|
11234
|
+
this.#additions.appendChild(announcement);
|
|
11235
|
+
|
|
11236
|
+
const timeout = setTimeout(() => {
|
|
11237
|
+
announcement.remove();
|
|
11238
|
+
this.#removalTimeouts.delete(timeout);
|
|
11239
|
+
}, REMOVAL_DELAY);
|
|
11240
|
+
this.#removalTimeouts.add(timeout);
|
|
11241
|
+
}
|
|
11242
|
+
}
|
|
11243
|
+
|
|
10577
11244
|
class BaseSource {
|
|
10578
11245
|
// Template method to override
|
|
10579
11246
|
async buildListItems(filter = "") {
|
|
@@ -11460,46 +12127,233 @@ const DELETE_ICON = `<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg"
|
|
|
11460
12127
|
<path d="M11.2041 1.01074C12.2128 1.113 13 1.96435 13 3V4H15L15.1025 4.00488C15.6067 4.05621 16 4.48232 16 5C16 5.55228 15.5523 6 15 6H14.8457L14.1416 15.1533C14.0614 16.1953 13.1925 17 12.1475 17H5.85254L5.6582 16.9902C4.76514 16.9041 4.03607 16.2296 3.88184 15.3457L3.8584 15.1533L3.1543 6H3C2.44772 6 2 5.55228 2 5C2 4.44772 2.44772 4 3 4H5V3C5 1.89543 5.89543 1 7 1H11L11.2041 1.01074ZM5.85254 15H12.1475L12.8398 6H5.16016L5.85254 15ZM7 4H11V3H7V4Z"/>
|
|
11461
12128
|
</svg>`;
|
|
11462
12129
|
|
|
11463
|
-
class
|
|
12130
|
+
class AttachmentToolbar extends HTMLElement {
|
|
12131
|
+
#listeners = new ListenerBin()
|
|
12132
|
+
#buttons = []
|
|
12133
|
+
#currentNodeKey = null
|
|
12134
|
+
#reflowObserver = new ResizeObserver(() => this.#updatePosition())
|
|
12135
|
+
#presentationObserver = new MutationObserver(() => this.#refresh())
|
|
12136
|
+
#observedElement = null
|
|
12137
|
+
|
|
11464
12138
|
connectedCallback() {
|
|
11465
|
-
this.editorElement = this.closest("lexxy-editor");
|
|
11466
|
-
this.editor = this.editorElement.editor;
|
|
11467
12139
|
this.classList.add("lexxy-floating-controls");
|
|
12140
|
+
this.role = "toolbar";
|
|
12141
|
+
this.hidden = true;
|
|
11468
12142
|
|
|
11469
|
-
if (
|
|
11470
|
-
this.#
|
|
12143
|
+
if (this.#editor) {
|
|
12144
|
+
this.#setUpButtons();
|
|
12145
|
+
this.#monitorSelection();
|
|
12146
|
+
this.#followScrolling();
|
|
12147
|
+
this.#registerKeyboardShortcut();
|
|
11471
12148
|
}
|
|
11472
12149
|
}
|
|
11473
12150
|
|
|
11474
12151
|
disconnectedCallback() {
|
|
11475
|
-
this.
|
|
11476
|
-
|
|
12152
|
+
this.dispose();
|
|
12153
|
+
}
|
|
12154
|
+
|
|
12155
|
+
dispose() {
|
|
12156
|
+
this.#listeners.dispose();
|
|
12157
|
+
this.#reflowObserver.disconnect();
|
|
12158
|
+
this.#presentationObserver.disconnect();
|
|
12159
|
+
}
|
|
12160
|
+
|
|
12161
|
+
get #editor() {
|
|
12162
|
+
return this.#editorElement?.editor
|
|
12163
|
+
}
|
|
12164
|
+
|
|
12165
|
+
get #editorElement() {
|
|
12166
|
+
return this.closest("lexxy-editor")
|
|
11477
12167
|
}
|
|
11478
12168
|
|
|
11479
|
-
#
|
|
12169
|
+
#setUpButtons() {
|
|
12170
|
+
this.innerHTML = "";
|
|
12171
|
+
|
|
11480
12172
|
const container = createElement("div", { className: "lexxy-floating-controls__group" });
|
|
12173
|
+
container.appendChild(this.#createRemoveButton());
|
|
12174
|
+
this.appendChild(container);
|
|
12175
|
+
|
|
12176
|
+
this.#buttons = Array.from(this.querySelectorAll("button"));
|
|
12177
|
+
this.#listeners.track(
|
|
12178
|
+
registerEventListener(this, "keydown", this.#navigateOrExit),
|
|
12179
|
+
registerEventListener(this, "focusout", this.#resetTabIndexOnExit)
|
|
12180
|
+
);
|
|
12181
|
+
}
|
|
11481
12182
|
|
|
11482
|
-
|
|
11483
|
-
|
|
12183
|
+
#createRemoveButton() {
|
|
12184
|
+
const button = createElement("button", {
|
|
11484
12185
|
type: "button",
|
|
11485
|
-
|
|
12186
|
+
className: "lexxy-attachment-toolbar__remove",
|
|
12187
|
+
ariaLabel: "Remove",
|
|
12188
|
+
tabIndex: -1
|
|
11486
12189
|
});
|
|
11487
|
-
|
|
11488
|
-
this.
|
|
12190
|
+
button.innerHTML = DELETE_ICON;
|
|
12191
|
+
this.#listeners.track(registerEventListener(button, "click", () => this.#removeSelectedNode()));
|
|
12192
|
+
return button
|
|
12193
|
+
}
|
|
11489
12194
|
|
|
11490
|
-
|
|
11491
|
-
|
|
11492
|
-
|
|
12195
|
+
#removeSelectedNode() {
|
|
12196
|
+
if (this.#hasSelectedNode) {
|
|
12197
|
+
this.#editor.update(() => $getNodeByKey(this.#currentNodeKey)?.remove());
|
|
12198
|
+
this.#editor.focus();
|
|
12199
|
+
}
|
|
12200
|
+
}
|
|
11493
12201
|
|
|
11494
|
-
|
|
12202
|
+
get #hasSelectedNode() {
|
|
12203
|
+
return this.#currentNodeKey !== null
|
|
11495
12204
|
}
|
|
11496
12205
|
|
|
11497
|
-
#
|
|
11498
|
-
|
|
11499
|
-
|
|
11500
|
-
|
|
12206
|
+
#navigateOrExit = (event) => {
|
|
12207
|
+
if (event.key === "Escape") {
|
|
12208
|
+
event.preventDefault();
|
|
12209
|
+
event.stopPropagation();
|
|
12210
|
+
this.#returnFocusToEditor();
|
|
12211
|
+
} else {
|
|
12212
|
+
handleRollingTabIndex(this.#buttons, event);
|
|
12213
|
+
}
|
|
12214
|
+
}
|
|
12215
|
+
|
|
12216
|
+
#returnFocusToEditor() {
|
|
12217
|
+
this.#editor.getRootElement()?.focus({ preventScroll: true });
|
|
12218
|
+
if (this.#currentNodeKey) {
|
|
12219
|
+
const nodeKey = this.#currentNodeKey;
|
|
12220
|
+
this.#editor.update(() => {
|
|
12221
|
+
const node = $getNodeByKey(nodeKey);
|
|
12222
|
+
if (node) $setSelection($createNodeSelectionWith(node));
|
|
12223
|
+
});
|
|
12224
|
+
}
|
|
12225
|
+
}
|
|
12226
|
+
|
|
12227
|
+
#resetTabIndexOnExit = (event) => {
|
|
12228
|
+
if (!this.contains(event.relatedTarget)) {
|
|
12229
|
+
this.#buttons.forEach(button => button.tabIndex = -1);
|
|
12230
|
+
}
|
|
12231
|
+
}
|
|
12232
|
+
|
|
12233
|
+
#monitorSelection() {
|
|
12234
|
+
this.#listeners.track(
|
|
12235
|
+
registerLabelledDecoratorSelection(this.#editor, (target) => this.#updateForSelection(target))
|
|
12236
|
+
);
|
|
12237
|
+
}
|
|
12238
|
+
|
|
12239
|
+
#updateForSelection(target) {
|
|
12240
|
+
if (target) {
|
|
12241
|
+
this.#show(target.key);
|
|
12242
|
+
} else {
|
|
12243
|
+
this.#hide();
|
|
12244
|
+
}
|
|
12245
|
+
}
|
|
12246
|
+
|
|
12247
|
+
#show(nodeKey) {
|
|
12248
|
+
this.#currentNodeKey = nodeKey;
|
|
12249
|
+
this.#refresh();
|
|
12250
|
+
this.hidden = false;
|
|
12251
|
+
this.#reflowObserver.observe(this.#editorElement);
|
|
12252
|
+
}
|
|
12253
|
+
|
|
12254
|
+
#refresh() {
|
|
12255
|
+
if (this.#hasSelectedNode) {
|
|
12256
|
+
this.#watchPresentation(this.#currentNodeKey);
|
|
12257
|
+
this.#describeNode(this.#currentNodeKey);
|
|
12258
|
+
this.#updatePosition();
|
|
12259
|
+
}
|
|
12260
|
+
}
|
|
12261
|
+
|
|
12262
|
+
// Failed previews change presentation without a Lexical update, and a node
|
|
12263
|
+
// can replace its figure without changing its key.
|
|
12264
|
+
#watchPresentation(nodeKey) {
|
|
12265
|
+
const element = this.#editor.getElementByKey(nodeKey);
|
|
12266
|
+
if (element !== this.#observedElement) {
|
|
12267
|
+
this.#presentationObserver.disconnect();
|
|
12268
|
+
this.#observedElement = element;
|
|
12269
|
+
if (element) this.#presentationObserver.observe(element, { attributes: true, attributeFilter: [ "class" ] });
|
|
12270
|
+
}
|
|
12271
|
+
}
|
|
12272
|
+
|
|
12273
|
+
#describeNode(nodeKey) {
|
|
12274
|
+
const element = this.#editor.getElementByKey(nodeKey);
|
|
12275
|
+
|
|
12276
|
+
this.#editor.getEditorState().read(() => {
|
|
12277
|
+
const node = $getNodeByKey(nodeKey);
|
|
12278
|
+
this.dataset.nodeType = node.getType();
|
|
12279
|
+
this.dataset.presentation = this.#presentationOf(node, element);
|
|
12280
|
+
|
|
12281
|
+
if (node.contentType) {
|
|
12282
|
+
this.dataset.contentType = node.contentType;
|
|
12283
|
+
} else {
|
|
12284
|
+
delete this.dataset.contentType;
|
|
12285
|
+
}
|
|
11501
12286
|
});
|
|
11502
12287
|
}
|
|
12288
|
+
|
|
12289
|
+
#presentationOf(node, element) {
|
|
12290
|
+
if ($isImageGalleryNode(node.getParent())) {
|
|
12291
|
+
return "gallery"
|
|
12292
|
+
} else if (node.isInline() && this.#rendersInline(element)) {
|
|
12293
|
+
return "inline"
|
|
12294
|
+
} else if (element?.classList.contains("attachment--preview")) {
|
|
12295
|
+
return "preview"
|
|
12296
|
+
} else {
|
|
12297
|
+
return "block"
|
|
12298
|
+
}
|
|
12299
|
+
}
|
|
12300
|
+
|
|
12301
|
+
#rendersInline(element) {
|
|
12302
|
+
return element != null && getComputedStyle(element).display.startsWith("inline")
|
|
12303
|
+
}
|
|
12304
|
+
|
|
12305
|
+
#updatePosition() {
|
|
12306
|
+
const figureElement = this.#editor.getElementByKey(this.#currentNodeKey);
|
|
12307
|
+
if (figureElement) {
|
|
12308
|
+
const rect = figureElement.getBoundingClientRect();
|
|
12309
|
+
const editorRect = this.#editorElement.getBoundingClientRect();
|
|
12310
|
+
this.style.setProperty("--lexxy-anchor-top", `${rect.top - editorRect.top}px`);
|
|
12311
|
+
this.style.setProperty("--lexxy-anchor-left", `${rect.left - editorRect.left}px`);
|
|
12312
|
+
this.style.setProperty("--lexxy-anchor-width", `${rect.width}px`);
|
|
12313
|
+
this.style.setProperty("--lexxy-anchor-height", `${rect.height}px`);
|
|
12314
|
+
this.#flipWhenOverflowing(editorRect);
|
|
12315
|
+
}
|
|
12316
|
+
}
|
|
12317
|
+
|
|
12318
|
+
#flipWhenOverflowing(editorRect) {
|
|
12319
|
+
this.removeAttribute("data-overflow");
|
|
12320
|
+
if (this.getBoundingClientRect().right > editorRect.right) {
|
|
12321
|
+
this.setAttribute("data-overflow", "");
|
|
12322
|
+
}
|
|
12323
|
+
}
|
|
12324
|
+
|
|
12325
|
+
#hide() {
|
|
12326
|
+
if (this.#hasSelectedNode) {
|
|
12327
|
+
this.#currentNodeKey = null;
|
|
12328
|
+
this.hidden = true;
|
|
12329
|
+
this.#reflowObserver.disconnect();
|
|
12330
|
+
this.#presentationObserver.disconnect();
|
|
12331
|
+
this.#observedElement = null;
|
|
12332
|
+
}
|
|
12333
|
+
}
|
|
12334
|
+
|
|
12335
|
+
// Scrolling inside the editor moves the figure without resizing anything.
|
|
12336
|
+
#followScrolling() {
|
|
12337
|
+
this.#listeners.track(registerEventListener(this.#editorElement, "scroll", () => this.#updatePosition(), { capture: true, passive: true }));
|
|
12338
|
+
}
|
|
12339
|
+
|
|
12340
|
+
#registerKeyboardShortcut() {
|
|
12341
|
+
this.#listeners.track(this.#editor.registerCommand(
|
|
12342
|
+
KEY_DOWN_COMMAND,
|
|
12343
|
+
this.#focusToolbarOnAltF10,
|
|
12344
|
+
COMMAND_PRIORITY_HIGH
|
|
12345
|
+
));
|
|
12346
|
+
}
|
|
12347
|
+
|
|
12348
|
+
#focusToolbarOnAltF10 = (event) => {
|
|
12349
|
+
if (this.#hasSelectedNode && event.altKey && event.key === "F10") {
|
|
12350
|
+
event.preventDefault();
|
|
12351
|
+
this.#buttons[0]?.focus();
|
|
12352
|
+
return true
|
|
12353
|
+
} else {
|
|
12354
|
+
return false
|
|
12355
|
+
}
|
|
12356
|
+
}
|
|
11503
12357
|
}
|
|
11504
12358
|
|
|
11505
12359
|
class TableController {
|
|
@@ -11904,7 +12758,7 @@ class TableTools extends HTMLElement {
|
|
|
11904
12758
|
#listeners = new ListenerBin()
|
|
11905
12759
|
|
|
11906
12760
|
connectedCallback() {
|
|
11907
|
-
this.tableController = new TableController(this
|
|
12761
|
+
this.tableController = new TableController(this.editorElement);
|
|
11908
12762
|
this.classList.add("lexxy-floating-controls");
|
|
11909
12763
|
this.role = "toolbar";
|
|
11910
12764
|
|
|
@@ -11925,16 +12779,34 @@ class TableTools extends HTMLElement {
|
|
|
11925
12779
|
this.tableController = null;
|
|
11926
12780
|
}
|
|
11927
12781
|
|
|
11928
|
-
get
|
|
11929
|
-
return this
|
|
12782
|
+
get editorElement() {
|
|
12783
|
+
return this.closest("lexxy-editor")
|
|
11930
12784
|
}
|
|
11931
12785
|
|
|
11932
|
-
get
|
|
11933
|
-
return this.
|
|
12786
|
+
get editor() {
|
|
12787
|
+
return this.editorElement.editor
|
|
12788
|
+
}
|
|
12789
|
+
|
|
12790
|
+
getEditorElement() {
|
|
12791
|
+
return this.editorElement
|
|
12792
|
+
}
|
|
12793
|
+
|
|
12794
|
+
closeDropdowns({ except } = {}) {
|
|
12795
|
+
this.#moreMenus.forEach(menu => {
|
|
12796
|
+
if (menu !== except) menu.close({ focusEditor: false });
|
|
12797
|
+
});
|
|
12798
|
+
}
|
|
12799
|
+
|
|
12800
|
+
get #moreMenus() {
|
|
12801
|
+
return this.querySelectorAll("lexxy-toolbar-dropdown")
|
|
12802
|
+
}
|
|
12803
|
+
|
|
12804
|
+
get #hasOpenMoreMenu() {
|
|
12805
|
+
return Array.from(this.#moreMenus).some(menu => menu.isOpen)
|
|
11934
12806
|
}
|
|
11935
12807
|
|
|
11936
12808
|
get #tableToolsButtons() {
|
|
11937
|
-
return Array.from(this.querySelectorAll("button
|
|
12809
|
+
return Array.from(this.querySelectorAll("button")).filter(button => !button.closest("[data-dropdown-panel]"))
|
|
11938
12810
|
}
|
|
11939
12811
|
|
|
11940
12812
|
#setUpButtons() {
|
|
@@ -11953,23 +12825,37 @@ class TableTools extends HTMLElement {
|
|
|
11953
12825
|
const plusButton = this.#createButton(`Add ${childType}`, { action: "insert", childType, direction: "after" }, "+");
|
|
11954
12826
|
const minusButton = this.#createButton(`Remove ${childType}`, { action: "delete", childType }, "−");
|
|
11955
12827
|
|
|
11956
|
-
const dropdown = createElement("details", { className: "lexxy-table-control__more-menu" });
|
|
11957
|
-
dropdown.setAttribute("name", "lexxy-dropdown");
|
|
11958
|
-
dropdown.tabIndex = -1;
|
|
11959
|
-
|
|
11960
|
-
const count = createElement("summary", {}, `_ ${childType}s`);
|
|
11961
|
-
setCountProperty(count);
|
|
11962
|
-
dropdown.appendChild(count);
|
|
11963
|
-
|
|
11964
|
-
dropdown.appendChild(moreMenu);
|
|
11965
|
-
|
|
11966
12828
|
container.appendChild(minusButton);
|
|
11967
|
-
container.appendChild(
|
|
12829
|
+
container.appendChild(this.#createMoreMenu(childType, setCountProperty, moreMenu));
|
|
11968
12830
|
container.appendChild(plusButton);
|
|
11969
12831
|
|
|
11970
12832
|
return container
|
|
11971
12833
|
}
|
|
11972
12834
|
|
|
12835
|
+
#createMoreMenu(childType, setCountProperty, menu) {
|
|
12836
|
+
const dropdown = createElement("lexxy-toolbar-dropdown", { className: "lexxy-table-control__more-menu" });
|
|
12837
|
+
|
|
12838
|
+
const trigger = createElement("button", {
|
|
12839
|
+
type: "button",
|
|
12840
|
+
className: "lexxy-table-control__more-menu-trigger",
|
|
12841
|
+
dataset: { dropdownTrigger: "" },
|
|
12842
|
+
"aria-haspopup": "menu",
|
|
12843
|
+
"aria-expanded": "false"
|
|
12844
|
+
}, `_ ${childType}s`);
|
|
12845
|
+
trigger.tabIndex = -1;
|
|
12846
|
+
setCountProperty(trigger);
|
|
12847
|
+
|
|
12848
|
+
menu.dataset.dropdownPanel = "";
|
|
12849
|
+
menu.role = "menu";
|
|
12850
|
+
menu.setAttribute("aria-label", `${childType} options`);
|
|
12851
|
+
menu.hidden = true;
|
|
12852
|
+
|
|
12853
|
+
dropdown.appendChild(trigger);
|
|
12854
|
+
dropdown.appendChild(menu);
|
|
12855
|
+
|
|
12856
|
+
return dropdown
|
|
12857
|
+
}
|
|
12858
|
+
|
|
11973
12859
|
#createRowButtonsContainer() {
|
|
11974
12860
|
return this.#createButtonsContainer(
|
|
11975
12861
|
"row",
|
|
@@ -11988,15 +12874,18 @@ class TableTools extends HTMLElement {
|
|
|
11988
12874
|
|
|
11989
12875
|
#createMoreMenuSection(childType) {
|
|
11990
12876
|
const section = createElement("div", { className: "lexxy-floating-controls__group lexxy-table-control__more-menu-details" });
|
|
11991
|
-
const addBeforeButton = this.#createButton(`Add ${childType} before`, { action: "insert", childType, direction: "before" });
|
|
11992
|
-
const addAfterButton = this.#createButton(`Add ${childType} after`, { action: "insert", childType, direction: "after" });
|
|
11993
|
-
const toggleStyleButton = this.#createButton(`Toggle ${childType} style`, { action: "toggle", childType });
|
|
11994
|
-
const deleteButton = this.#createButton(`Remove ${childType}`, { action: "delete", childType });
|
|
11995
12877
|
|
|
11996
|
-
|
|
11997
|
-
|
|
11998
|
-
|
|
11999
|
-
|
|
12878
|
+
const items = [
|
|
12879
|
+
this.#createButton(`Add ${childType} before`, { action: "insert", childType, direction: "before" }),
|
|
12880
|
+
this.#createButton(`Add ${childType} after`, { action: "insert", childType, direction: "after" }),
|
|
12881
|
+
this.#createButton(`Toggle ${childType} style`, { action: "toggle", childType }),
|
|
12882
|
+
this.#createButton(`Remove ${childType}`, { action: "delete", childType })
|
|
12883
|
+
];
|
|
12884
|
+
|
|
12885
|
+
items.forEach(item => {
|
|
12886
|
+
item.role = "menuitem";
|
|
12887
|
+
section.appendChild(item);
|
|
12888
|
+
});
|
|
12000
12889
|
|
|
12001
12890
|
return section
|
|
12002
12891
|
}
|
|
@@ -12037,7 +12926,7 @@ class TableTools extends HTMLElement {
|
|
|
12037
12926
|
}
|
|
12038
12927
|
|
|
12039
12928
|
#registerKeyboardShortcuts() {
|
|
12040
|
-
this.#listeners.track(this
|
|
12929
|
+
this.#listeners.track(this.editor.registerCommand(KEY_DOWN_COMMAND, this.#focusToolbarOnAltF10, COMMAND_PRIORITY_HIGH));
|
|
12041
12930
|
}
|
|
12042
12931
|
|
|
12043
12932
|
#focusToolbarOnAltF10 = (event) => {
|
|
@@ -12068,9 +12957,9 @@ class TableTools extends HTMLElement {
|
|
|
12068
12957
|
const cell = this.tableController.currentCell;
|
|
12069
12958
|
if (!cell) return
|
|
12070
12959
|
|
|
12071
|
-
this
|
|
12960
|
+
this.editor.update(() => {
|
|
12072
12961
|
cell.select();
|
|
12073
|
-
this
|
|
12962
|
+
this.editor.focus();
|
|
12074
12963
|
});
|
|
12075
12964
|
|
|
12076
12965
|
this.#update();
|
|
@@ -12107,7 +12996,7 @@ class TableTools extends HTMLElement {
|
|
|
12107
12996
|
if (!cellsToHighlight) return
|
|
12108
12997
|
|
|
12109
12998
|
cellsToHighlight.forEach(cell => {
|
|
12110
|
-
const cellElement = this
|
|
12999
|
+
const cellElement = this.editor.getElementByKey(cell.getKey());
|
|
12111
13000
|
if (!cellElement) return
|
|
12112
13001
|
|
|
12113
13002
|
cellElement.classList.toggle(theme.tableCellHighlight, true);
|
|
@@ -12116,8 +13005,8 @@ class TableTools extends HTMLElement {
|
|
|
12116
13005
|
}
|
|
12117
13006
|
|
|
12118
13007
|
#monitorForTableSelection() {
|
|
12119
|
-
this.#listeners.track(this
|
|
12120
|
-
const tableNode = this
|
|
13008
|
+
this.#listeners.track(this.editor.registerUpdateListener(() => {
|
|
13009
|
+
const tableNode = this.editor.getRootElement() && this.tableController.updateSelectedTable();
|
|
12121
13010
|
|
|
12122
13011
|
if (tableNode) {
|
|
12123
13012
|
this.#show();
|
|
@@ -12128,8 +13017,12 @@ class TableTools extends HTMLElement {
|
|
|
12128
13017
|
}
|
|
12129
13018
|
|
|
12130
13019
|
#executeTableCommand(command) {
|
|
13020
|
+
const fromMoreMenu = this.#hasOpenMoreMenu;
|
|
13021
|
+
|
|
12131
13022
|
this.tableController.executeTableCommand(command);
|
|
12132
13023
|
this.#update();
|
|
13024
|
+
|
|
13025
|
+
if (fromMoreMenu) this.editor.focus();
|
|
12133
13026
|
}
|
|
12134
13027
|
|
|
12135
13028
|
#show() {
|
|
@@ -12153,18 +13046,18 @@ class TableTools extends HTMLElement {
|
|
|
12153
13046
|
}
|
|
12154
13047
|
|
|
12155
13048
|
#closeMoreMenu() {
|
|
12156
|
-
this.
|
|
13049
|
+
this.closeDropdowns();
|
|
12157
13050
|
}
|
|
12158
13051
|
|
|
12159
13052
|
#updateButtonsPosition() {
|
|
12160
13053
|
const tableNode = this.tableController.currentTableNode;
|
|
12161
13054
|
if (!tableNode) return
|
|
12162
13055
|
|
|
12163
|
-
const tableElement = this
|
|
13056
|
+
const tableElement = this.editor.getElementByKey(tableNode.getKey());
|
|
12164
13057
|
if (!tableElement) return
|
|
12165
13058
|
|
|
12166
13059
|
const tableRect = tableElement.getBoundingClientRect();
|
|
12167
|
-
const editorRect = this
|
|
13060
|
+
const editorRect = this.editorElement.getBoundingClientRect();
|
|
12168
13061
|
|
|
12169
13062
|
const relativeTop = tableRect.top - editorRect.top;
|
|
12170
13063
|
const relativeCenter = (tableRect.left + tableRect.right) / 2 - editorRect.left;
|
|
@@ -12176,7 +13069,7 @@ class TableTools extends HTMLElement {
|
|
|
12176
13069
|
const tableNode = this.tableController.currentTableNode;
|
|
12177
13070
|
if (!tableNode) return
|
|
12178
13071
|
|
|
12179
|
-
const tableElement = $getElementForTableNode(this
|
|
13072
|
+
const tableElement = $getElementForTableNode(this.editor, tableNode);
|
|
12180
13073
|
if (!tableElement) return
|
|
12181
13074
|
|
|
12182
13075
|
const rowCount = tableElement.rows;
|
|
@@ -12190,18 +13083,18 @@ class TableTools extends HTMLElement {
|
|
|
12190
13083
|
const cell = this.tableController.currentCell;
|
|
12191
13084
|
if (!cell) return
|
|
12192
13085
|
|
|
12193
|
-
const cellElement = this
|
|
12194
|
-
if (!cellElement) return
|
|
13086
|
+
const cellElement = this.editor.getElementByKey(cell.getKey());
|
|
13087
|
+
if (!cellElement?.contains(document.getSelection()?.anchorNode)) return
|
|
12195
13088
|
|
|
12196
13089
|
cellElement.classList.add(theme.tableCellFocus);
|
|
12197
13090
|
}
|
|
12198
13091
|
|
|
12199
13092
|
#clearCellStyles() {
|
|
12200
|
-
this
|
|
13093
|
+
this.editorElement.querySelectorAll(`.${theme.tableCellFocus}`)?.forEach(cell => {
|
|
12201
13094
|
cell.classList.remove(theme.tableCellFocus);
|
|
12202
13095
|
});
|
|
12203
13096
|
|
|
12204
|
-
this
|
|
13097
|
+
this.editorElement.querySelectorAll(`.${theme.tableCellHighlight}`)?.forEach(cell => {
|
|
12205
13098
|
cell.classList.remove(theme.tableCellHighlight);
|
|
12206
13099
|
cell.removeAttribute("data-action");
|
|
12207
13100
|
cell.removeAttribute("data-child-type");
|
|
@@ -12233,7 +13126,8 @@ function defineElements() {
|
|
|
12233
13126
|
// Prompt must be registered AFTER Editor
|
|
12234
13127
|
"lexxy-prompt": LexicalPromptElement,
|
|
12235
13128
|
"lexxy-code-language-picker": CodeLanguagePicker,
|
|
12236
|
-
"lexxy-
|
|
13129
|
+
"lexxy-live-region": LiveRegion,
|
|
13130
|
+
"lexxy-attachment-toolbar": AttachmentToolbar,
|
|
12237
13131
|
"lexxy-table-tools": TableTools
|
|
12238
13132
|
};
|
|
12239
13133
|
|