@37signals/lexxy 0.9.31 → 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 +1818 -659
- package/dist/lexxy_helpers.esm.js +61 -23
- 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,18 +1,18 @@
|
|
|
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';
|
|
14
14
|
import { HistoryExtension } from '@lexical/history';
|
|
15
|
-
import { $isCodeNode, CodeHighlightNode,
|
|
15
|
+
import { $isCodeNode, CodeHighlightNode, $isCodeHighlightNode, $createCodeNode, $createCodeHighlightNode, CodeNode, registerCodeHighlighting, PrismTokenizer, normalizeCodeLang, CODE_LANGUAGE_FRIENDLY_NAME_MAP } from '@lexical/code';
|
|
16
16
|
import { TRANSFORMERS, registerMarkdownShortcuts } from '@lexical/markdown';
|
|
17
17
|
import { INSERT_TABLE_COMMAND, $getTableCellNodeFromLexicalNode, TableCellNode, TableNode, TableRowNode, setScrollableTablesActive, registerTablePlugin, registerTableSelectionObserver, TableCellHeaderStates, $insertTableRowAtSelection, $insertTableColumnAtSelection, $deleteTableRowAtSelection, $deleteTableColumnAtSelection, $findTableNode, $getTableRowIndexFromTableCellNode, $getTableColumnIndexFromTableCellNode, $findCellNode, $getElementForTableNode } from '@lexical/table';
|
|
18
18
|
import { Marked } from 'marked';
|
|
@@ -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;
|
|
@@ -1908,12 +2111,11 @@ class CustomActionTextAttachmentNode extends DecoratorNode {
|
|
|
1908
2111
|
return Lexxy.global.get("attachmentTagName")
|
|
1909
2112
|
}
|
|
1910
2113
|
|
|
1911
|
-
constructor({
|
|
2114
|
+
constructor({ sgid, contentType, innerHtml, plainText } = {}, key) {
|
|
1912
2115
|
super(key);
|
|
1913
2116
|
|
|
1914
2117
|
const contentTypeNamespace = Lexxy.global.get("attachmentContentTypeNamespace");
|
|
1915
2118
|
|
|
1916
|
-
this.tagName = tagName || CustomActionTextAttachmentNode.TAG_NAME;
|
|
1917
2119
|
this.sgid = sgid;
|
|
1918
2120
|
this.contentType = contentType || `application/vnd.${contentTypeNamespace}.unknown`;
|
|
1919
2121
|
this.innerHtml = innerHtml;
|
|
@@ -1921,7 +2123,7 @@ class CustomActionTextAttachmentNode extends DecoratorNode {
|
|
|
1921
2123
|
}
|
|
1922
2124
|
|
|
1923
2125
|
createDOM(_config, editor) {
|
|
1924
|
-
const figure = createElement(
|
|
2126
|
+
const figure = createElement(CustomActionTextAttachmentNode.TAG_NAME, { "content-type": this.contentType, "data-lexxy-decorator": true, draggable: true });
|
|
1925
2127
|
figure.dataset.lexicalNodeKey = this.__key;
|
|
1926
2128
|
|
|
1927
2129
|
// Resolved from the editor so this content is sanitized with its own
|
|
@@ -1940,13 +2142,35 @@ class CustomActionTextAttachmentNode extends DecoratorNode {
|
|
|
1940
2142
|
// is cosmetic: it is a nested attachment's rendering inside this one, not the
|
|
1941
2143
|
// attribute anything re-imports from.
|
|
1942
2144
|
figure.insertAdjacentHTML("beforeend", EditorSanitizer.for(editor).sanitize(this.innerHtml, { safeForXml: true }));
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
figure
|
|
2145
|
+
this.#markImagesAsDecorative(figure);
|
|
2146
|
+
this.#tagLabelImage(figure);
|
|
2147
|
+
this.#tagLabelMirrors(figure);
|
|
1946
2148
|
|
|
1947
2149
|
return figure
|
|
1948
2150
|
}
|
|
1949
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
|
+
|
|
1950
2174
|
updateDOM() {
|
|
1951
2175
|
return false
|
|
1952
2176
|
}
|
|
@@ -1964,7 +2188,7 @@ class CustomActionTextAttachmentNode extends DecoratorNode {
|
|
|
1964
2188
|
}
|
|
1965
2189
|
|
|
1966
2190
|
exportDOM() {
|
|
1967
|
-
const attachment = createElement(
|
|
2191
|
+
const attachment = createElement(CustomActionTextAttachmentNode.TAG_NAME, {
|
|
1968
2192
|
sgid: this.sgid,
|
|
1969
2193
|
content: this.innerHtml,
|
|
1970
2194
|
"content-type": this.contentType
|
|
@@ -1977,7 +2201,6 @@ class CustomActionTextAttachmentNode extends DecoratorNode {
|
|
|
1977
2201
|
return {
|
|
1978
2202
|
type: "custom_action_text_attachment",
|
|
1979
2203
|
version: 1,
|
|
1980
|
-
tagName: this.tagName,
|
|
1981
2204
|
sgid: this.sgid,
|
|
1982
2205
|
contentType: this.contentType,
|
|
1983
2206
|
innerHtml: this.innerHtml,
|
|
@@ -1988,6 +2211,34 @@ class CustomActionTextAttachmentNode extends DecoratorNode {
|
|
|
1988
2211
|
decorate() {
|
|
1989
2212
|
return null
|
|
1990
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
|
+
}
|
|
1991
2242
|
}
|
|
1992
2243
|
|
|
1993
2244
|
function $isCustomActionTextAttachmentNode(node) {
|
|
@@ -2091,6 +2342,35 @@ function $createNodeSelectionWith(...nodes) {
|
|
|
2091
2342
|
return selection
|
|
2092
2343
|
}
|
|
2093
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
|
+
|
|
2094
2374
|
function $isShadowRoot(node) {
|
|
2095
2375
|
return $isElementNode(node) && $isRootOrShadowRoot(node) && !$isRootNode(node)
|
|
2096
2376
|
}
|
|
@@ -2116,6 +2396,10 @@ function getListType(node) {
|
|
|
2116
2396
|
return list?.getListType() ?? null
|
|
2117
2397
|
}
|
|
2118
2398
|
|
|
2399
|
+
function announceFromEditor(editor, message) {
|
|
2400
|
+
editor.getRootElement()?.closest("lexxy-editor")?.announce(message);
|
|
2401
|
+
}
|
|
2402
|
+
|
|
2119
2403
|
function isEditorFocused(editor) {
|
|
2120
2404
|
const rootElement = editor.getRootElement();
|
|
2121
2405
|
return rootElement !== null && rootElement.contains(document.activeElement)
|
|
@@ -2724,10 +3008,9 @@ class ActionTextAttachmentNode extends DecoratorNode {
|
|
|
2724
3008
|
return Lexxy.global.get("attachmentTagName")
|
|
2725
3009
|
}
|
|
2726
3010
|
|
|
2727
|
-
constructor({
|
|
3011
|
+
constructor({ sgid, src, previewSrc, previewable, previewStatusUrl, pendingPreview, altText, caption, contentType, fileName, fileSize, width, height, uploadError } = {}, key) {
|
|
2728
3012
|
super(key);
|
|
2729
3013
|
|
|
2730
|
-
this.tagName = tagName || ActionTextAttachmentNode.TAG_NAME;
|
|
2731
3014
|
this.sgid = sgid;
|
|
2732
3015
|
this.src = src;
|
|
2733
3016
|
this.previewSrc = previewSrc;
|
|
@@ -2769,14 +3052,27 @@ class ActionTextAttachmentNode extends DecoratorNode {
|
|
|
2769
3052
|
updateDOM(prevNode, dom) {
|
|
2770
3053
|
if (this.uploadError !== prevNode.uploadError) return true
|
|
2771
3054
|
|
|
2772
|
-
const
|
|
2773
|
-
if (
|
|
2774
|
-
|
|
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;
|
|
2775
3067
|
}
|
|
2776
3068
|
|
|
2777
3069
|
return false
|
|
2778
3070
|
}
|
|
2779
3071
|
|
|
3072
|
+
get captionLabel() {
|
|
3073
|
+
return this.caption || this.fileName || ""
|
|
3074
|
+
}
|
|
3075
|
+
|
|
2780
3076
|
getTextContent() {
|
|
2781
3077
|
return `[${this.caption || this.fileName}]\n\n`
|
|
2782
3078
|
}
|
|
@@ -2786,7 +3082,7 @@ class ActionTextAttachmentNode extends DecoratorNode {
|
|
|
2786
3082
|
}
|
|
2787
3083
|
|
|
2788
3084
|
exportDOM() {
|
|
2789
|
-
const attachment = createElement(
|
|
3085
|
+
const attachment = createElement(ActionTextAttachmentNode.TAG_NAME, {
|
|
2790
3086
|
sgid: this.sgid,
|
|
2791
3087
|
previewable: this.previewable || null,
|
|
2792
3088
|
url: this.src,
|
|
@@ -2807,7 +3103,6 @@ class ActionTextAttachmentNode extends DecoratorNode {
|
|
|
2807
3103
|
return {
|
|
2808
3104
|
type: "action_text_attachment",
|
|
2809
3105
|
version: 1,
|
|
2810
|
-
tagName: this.tagName,
|
|
2811
3106
|
sgid: this.sgid,
|
|
2812
3107
|
src: this.src,
|
|
2813
3108
|
previewable: this.previewable,
|
|
@@ -2839,9 +3134,6 @@ class ActionTextAttachmentNode extends DecoratorNode {
|
|
|
2839
3134
|
figure.draggable = true;
|
|
2840
3135
|
figure.dataset.lexicalNodeKey = this.__key;
|
|
2841
3136
|
|
|
2842
|
-
const deleteButton = createElement("lexxy-node-delete-button");
|
|
2843
|
-
figure.appendChild(deleteButton);
|
|
2844
|
-
|
|
2845
3137
|
return figure
|
|
2846
3138
|
}
|
|
2847
3139
|
|
|
@@ -2857,6 +3149,18 @@ class ActionTextAttachmentNode extends DecoratorNode {
|
|
|
2857
3149
|
return this.contentType.startsWith("video/")
|
|
2858
3150
|
}
|
|
2859
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
|
+
|
|
2860
3164
|
#createDOMForPendingPreview() {
|
|
2861
3165
|
const figure = this.createAttachmentFigure(false);
|
|
2862
3166
|
figure.appendChild(this.#createDOMForFile());
|
|
@@ -3047,55 +3351,18 @@ class ActionTextAttachmentNode extends DecoratorNode {
|
|
|
3047
3351
|
}
|
|
3048
3352
|
|
|
3049
3353
|
#createEditableCaption() {
|
|
3050
|
-
const caption = createElement("figcaption", { className: "attachment__caption" });
|
|
3051
|
-
const
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
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();
|
|
3055
3362
|
});
|
|
3056
|
-
|
|
3057
|
-
input.addEventListener("focusin", () => input.placeholder = "Add caption...");
|
|
3058
|
-
input.addEventListener("blur", (event) => this.#handleCaptionInputBlurred(event));
|
|
3059
|
-
input.addEventListener("keydown", (event) => this.#handleCaptionInputKeydown(event));
|
|
3060
|
-
input.addEventListener("copy", (event) => event.stopPropagation());
|
|
3061
|
-
input.addEventListener("cut", (event) => event.stopPropagation());
|
|
3062
|
-
input.addEventListener("paste", (event) => event.stopPropagation());
|
|
3063
|
-
|
|
3064
|
-
caption.appendChild(input);
|
|
3065
|
-
|
|
3066
3363
|
return caption
|
|
3067
3364
|
}
|
|
3068
|
-
|
|
3069
|
-
#handleCaptionInputBlurred(event) {
|
|
3070
|
-
this.#updateCaptionValueFromInput(event.target);
|
|
3071
|
-
}
|
|
3072
|
-
|
|
3073
|
-
#updateCaptionValueFromInput(input) {
|
|
3074
|
-
input.placeholder = this.fileName;
|
|
3075
|
-
this.editor.update(() => {
|
|
3076
|
-
this.getWritable().caption = input.value;
|
|
3077
|
-
});
|
|
3078
|
-
}
|
|
3079
|
-
|
|
3080
|
-
#handleCaptionInputKeydown(event) {
|
|
3081
|
-
if (event.key === "Enter") {
|
|
3082
|
-
event.preventDefault();
|
|
3083
|
-
event.target.blur();
|
|
3084
|
-
|
|
3085
|
-
this.editor.update(() => {
|
|
3086
|
-
// Place the cursor after the current image
|
|
3087
|
-
this.selectNext(0, 0);
|
|
3088
|
-
}, {
|
|
3089
|
-
tag: HISTORY_MERGE_TAG
|
|
3090
|
-
});
|
|
3091
|
-
}
|
|
3092
|
-
|
|
3093
|
-
// Stop all keydown events from bubbling to the Lexical root element.
|
|
3094
|
-
// The caption textarea is outside Lexical's content model and should
|
|
3095
|
-
// handle its own keyboard events natively (Ctrl+A, Ctrl+C, Ctrl+X, etc.).
|
|
3096
|
-
event.stopPropagation();
|
|
3097
|
-
}
|
|
3098
|
-
}
|
|
3365
|
+
}
|
|
3099
3366
|
|
|
3100
3367
|
function $createActionTextAttachmentNode(...args) {
|
|
3101
3368
|
return new ActionTextAttachmentNode(...args)
|
|
@@ -3214,14 +3481,11 @@ class HorizontalDividerNode extends DecoratorNode {
|
|
|
3214
3481
|
super(key);
|
|
3215
3482
|
}
|
|
3216
3483
|
|
|
3484
|
+
// The hr already provides separator semantics; a figure boundary would be redundant.
|
|
3217
3485
|
createDOM() {
|
|
3218
|
-
const figure = createElement("figure", { className: "horizontal-divider" });
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
figure.appendChild(hr);
|
|
3222
|
-
|
|
3223
|
-
const deleteButton = createElement("lexxy-node-delete-button");
|
|
3224
|
-
figure.appendChild(deleteButton);
|
|
3486
|
+
const figure = createElement("figure", { className: "horizontal-divider", role: "presentation" });
|
|
3487
|
+
figure.appendChild(createElement("hr"));
|
|
3488
|
+
figure.dataset.lexicalNodeKey = this.__key;
|
|
3225
3489
|
|
|
3226
3490
|
return figure
|
|
3227
3491
|
}
|
|
@@ -3238,6 +3502,10 @@ class HorizontalDividerNode extends DecoratorNode {
|
|
|
3238
3502
|
return false
|
|
3239
3503
|
}
|
|
3240
3504
|
|
|
3505
|
+
get label() {
|
|
3506
|
+
return "Horizontal divider"
|
|
3507
|
+
}
|
|
3508
|
+
|
|
3241
3509
|
exportDOM() {
|
|
3242
3510
|
const hr = createElement("hr");
|
|
3243
3511
|
return { element: hr }
|
|
@@ -3503,161 +3771,366 @@ var theme = {
|
|
|
3503
3771
|
}
|
|
3504
3772
|
};
|
|
3505
3773
|
|
|
3506
|
-
class
|
|
3507
|
-
|
|
3774
|
+
class ImageGalleryNode extends ElementNode {
|
|
3775
|
+
$config() {
|
|
3776
|
+
return this.config("image_gallery", {
|
|
3777
|
+
extends: ElementNode,
|
|
3778
|
+
})
|
|
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
|
-
|
|
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
|
+
}
|
|
3803
|
+
}
|
|
3515
3804
|
}
|
|
3516
3805
|
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3806
|
+
static canCollapseWith(node) {
|
|
3807
|
+
return $isImageGalleryNode(node) || this.isValidChild(node)
|
|
3808
|
+
}
|
|
3809
|
+
|
|
3810
|
+
static isValidChild(node) {
|
|
3811
|
+
return $isActionTextAttachmentNode(node) && node.isPreviewableImage
|
|
3812
|
+
}
|
|
3813
|
+
|
|
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
|
+
}
|
|
3820
|
+
|
|
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
|
|
3522
3856
|
}
|
|
3857
|
+
|
|
3858
|
+
const newNode = $createImageGalleryNode();
|
|
3859
|
+
this.insertAfter(newNode, restoreSelection);
|
|
3860
|
+
return newNode
|
|
3523
3861
|
}
|
|
3524
3862
|
|
|
3525
|
-
|
|
3526
|
-
this
|
|
3863
|
+
getImageAttachments() {
|
|
3864
|
+
const children = this.getChildren();
|
|
3865
|
+
return children.filter($isActionTextAttachmentNode)
|
|
3527
3866
|
}
|
|
3528
|
-
}
|
|
3529
3867
|
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
//
|
|
3536
|
-
// Without this, `document.body.appendChild(...)` / `element.remove()` calls
|
|
3537
|
-
// forced the browser to re-evaluate every ancestor-dependent selector (`:has()`,
|
|
3538
|
-
// descendant combinators, universal sibling rules) across the document on each
|
|
3539
|
-
// invocation — a 13,000+ element style recalc per call on a typical Basecamp
|
|
3540
|
-
// page.
|
|
3868
|
+
exportDOM() {
|
|
3869
|
+
const div = document.createElement("div");
|
|
3870
|
+
div.className = this.#galleryClassNames;
|
|
3871
|
+
return { element: div }
|
|
3872
|
+
}
|
|
3541
3873
|
|
|
3542
|
-
|
|
3874
|
+
collapseWith(node, backwards) {
|
|
3875
|
+
if (!ImageGalleryNode.canCollapseWith(node)) return false
|
|
3543
3876
|
|
|
3544
|
-
|
|
3545
|
-
|
|
3877
|
+
if (backwards) {
|
|
3878
|
+
$insertFirst(this, node);
|
|
3879
|
+
} else {
|
|
3880
|
+
this.append(node);
|
|
3881
|
+
}
|
|
3546
3882
|
|
|
3547
|
-
|
|
3548
|
-
resolverRoot.setAttribute("aria-hidden", "true");
|
|
3549
|
-
resolverRoot.setAttribute("data-lexxy-style-resolver", "");
|
|
3550
|
-
// `contain: strict` (size, layout, paint, style) isolates everything.
|
|
3551
|
-
// The root itself paints nothing (visibility hidden), has zero
|
|
3552
|
-
// geometric impact (position fixed, intrinsic size via contain), and
|
|
3553
|
-
// never leaks style invalidation to its ancestors.
|
|
3554
|
-
resolverRoot.style.cssText = "contain: strict; position: fixed; top: 0; left: 0; visibility: hidden; pointer-events: none; width: 0; height: 0;";
|
|
3555
|
-
document.body.appendChild(resolverRoot);
|
|
3556
|
-
return resolverRoot
|
|
3557
|
-
}
|
|
3883
|
+
$unwrapAndFilterDescendants(this, ImageGalleryNode.isValidChild);
|
|
3558
3884
|
|
|
3559
|
-
|
|
3560
|
-
|
|
3885
|
+
return true
|
|
3886
|
+
}
|
|
3561
3887
|
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3888
|
+
unwrapEmptyNode() {
|
|
3889
|
+
if (this.isEmpty()) {
|
|
3890
|
+
const paragraph = $createParagraphNode();
|
|
3891
|
+
return this.replace(paragraph)
|
|
3892
|
+
}
|
|
3566
3893
|
}
|
|
3567
|
-
}
|
|
3568
3894
|
|
|
3569
|
-
|
|
3570
|
-
|
|
3895
|
+
replaceWithSingularChild() {
|
|
3896
|
+
if (this.#hasSingularChild) {
|
|
3897
|
+
const child = this.getFirstChild();
|
|
3898
|
+
return this.replace($makeSafeForRoot(child))
|
|
3899
|
+
}
|
|
3900
|
+
}
|
|
3571
3901
|
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3902
|
+
splitAroundInvalidChild() {
|
|
3903
|
+
for (const child of $firstToLastIterator(this)) {
|
|
3904
|
+
if (ImageGalleryNode.isValidChild(child)) continue
|
|
3905
|
+
|
|
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
|
|
3577
3915
|
}
|
|
3578
3916
|
}
|
|
3579
3917
|
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3918
|
+
splitAtIndex(index) {
|
|
3919
|
+
return $splitNode(this, index)
|
|
3920
|
+
}
|
|
3583
3921
|
|
|
3584
|
-
|
|
3922
|
+
get #hasSingularChild() {
|
|
3923
|
+
return this.getChildrenSize() === 1
|
|
3924
|
+
}
|
|
3925
|
+
|
|
3926
|
+
get #galleryClassNames() {
|
|
3927
|
+
return `attachment-gallery attachment-gallery--${this.getChildrenSize()}`
|
|
3928
|
+
}
|
|
3585
3929
|
}
|
|
3586
3930
|
|
|
3587
|
-
function
|
|
3588
|
-
|
|
3589
|
-
return !!(styles.color || styles["background-color"])
|
|
3931
|
+
function $createImageGalleryNode() {
|
|
3932
|
+
return new ImageGalleryNode()
|
|
3590
3933
|
}
|
|
3591
3934
|
|
|
3592
|
-
function
|
|
3593
|
-
return
|
|
3594
|
-
return canonicalizer.applyCanonicalization(css)
|
|
3595
|
-
}, styles)
|
|
3935
|
+
function $isImageGalleryNode(node) {
|
|
3936
|
+
return node instanceof ImageGalleryNode
|
|
3596
3937
|
}
|
|
3597
3938
|
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
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
|
+
);
|
|
3603
3966
|
}
|
|
3604
3967
|
|
|
3605
|
-
|
|
3606
|
-
|
|
3968
|
+
dispose() {
|
|
3969
|
+
this.#listeners.dispose();
|
|
3970
|
+
this.#close();
|
|
3971
|
+
this.#input.remove();
|
|
3972
|
+
}
|
|
3607
3973
|
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
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
|
|
3611
3983
|
}
|
|
3984
|
+
}
|
|
3612
3985
|
|
|
3613
|
-
|
|
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
|
+
}
|
|
3614
3996
|
}
|
|
3615
3997
|
|
|
3616
|
-
|
|
3617
|
-
|
|
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;
|
|
3618
4007
|
}
|
|
3619
4008
|
|
|
3620
|
-
|
|
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();
|
|
4031
|
+
}
|
|
3621
4032
|
|
|
3622
|
-
|
|
3623
|
-
|
|
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`;
|
|
4044
|
+
}
|
|
3624
4045
|
}
|
|
3625
4046
|
|
|
3626
|
-
#
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
4047
|
+
#checkAttachment() {
|
|
4048
|
+
if (this.#nodeKey && this.#editor.getElementByKey(this.#nodeKey)?.querySelector("figcaption") !== this.#caption) {
|
|
4049
|
+
this.#close();
|
|
4050
|
+
} else {
|
|
4051
|
+
this.#updatePosition();
|
|
4052
|
+
}
|
|
4053
|
+
}
|
|
4054
|
+
|
|
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 });
|
|
4081
|
+
}
|
|
4082
|
+
}
|
|
4083
|
+
|
|
4084
|
+
class UploadRequests {
|
|
4085
|
+
#requestsByKey = new Map()
|
|
4086
|
+
|
|
4087
|
+
track(key, request) {
|
|
4088
|
+
this.#requestsByKey.set(key, request);
|
|
4089
|
+
}
|
|
4090
|
+
|
|
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();
|
|
3630
4100
|
}
|
|
3631
|
-
return index === -1 ? null : this._allowedValues[index]
|
|
3632
4101
|
}
|
|
3633
4102
|
|
|
3634
|
-
|
|
3635
|
-
|
|
4103
|
+
clear() {
|
|
4104
|
+
this.#requestsByKey.clear();
|
|
3636
4105
|
}
|
|
3637
4106
|
}
|
|
3638
4107
|
|
|
3639
|
-
//
|
|
3640
|
-
//
|
|
3641
|
-
//
|
|
3642
|
-
// the
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
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;
|
|
3646
4115
|
|
|
3647
|
-
const
|
|
3648
|
-
const
|
|
3649
|
-
|
|
3650
|
-
return element
|
|
3651
|
-
});
|
|
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);
|
|
3652
4119
|
|
|
3653
|
-
|
|
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
|
+
}
|
|
3654
4128
|
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
4129
|
+
if (cursor < text.length) {
|
|
4130
|
+
segments.push({ start: cursor, end: text.length, range: null });
|
|
4131
|
+
}
|
|
3658
4132
|
|
|
3659
|
-
|
|
3660
|
-
return computed
|
|
4133
|
+
return segments
|
|
3661
4134
|
}
|
|
3662
4135
|
|
|
3663
4136
|
const TOGGLE_HIGHLIGHT_COMMAND = createCommand();
|
|
@@ -3830,26 +4303,9 @@ function extractHighlightStyleFromElement(element) {
|
|
|
3830
4303
|
return css.length > 0 ? css : null
|
|
3831
4304
|
}
|
|
3832
4305
|
|
|
3833
|
-
//
|
|
3834
|
-
//
|
|
3835
|
-
//
|
|
3836
|
-
// recovers the block's highlight ranges — staged during HTML import, or read
|
|
3837
|
-
// from the children the fresh tokens are about to replace — and reapplies
|
|
3838
|
-
// them to the fresh tokens before the retokenizer splices them in.
|
|
3839
|
-
function buildHighlightPreservingTokenizer(editor) {
|
|
3840
|
-
return {
|
|
3841
|
-
defaultLanguage: PrismTokenizer.defaultLanguage,
|
|
3842
|
-
tokenize(code, language) {
|
|
3843
|
-
return PrismTokenizer.tokenize(code, language)
|
|
3844
|
-
},
|
|
3845
|
-
$tokenize(codeNode, language) {
|
|
3846
|
-
const tokens = PrismTokenizer.$tokenize(codeNode, language);
|
|
3847
|
-
const highlights = $takeHighlightRanges(editor, codeNode);
|
|
3848
|
-
return $applyHighlightRangesToTokens(tokens, highlights)
|
|
3849
|
-
}
|
|
3850
|
-
}
|
|
3851
|
-
}
|
|
3852
|
-
|
|
4306
|
+
// Recover the highlight ranges to reapply after a retokenization: the ranges
|
|
4307
|
+
// staged during HTML import, or the ones read from the children the fresh
|
|
4308
|
+
// tokens are about to replace.
|
|
3853
4309
|
function $takeHighlightRanges(editor, codeNode) {
|
|
3854
4310
|
const pending = $getPendingHighlights(editor);
|
|
3855
4311
|
const key = codeNode.getKey();
|
|
@@ -3891,52 +4347,25 @@ function $applyHighlightRangesToTokens(tokens, highlights) {
|
|
|
3891
4347
|
// attached to the tree yet, so we create CodeHighlightNode replacements.
|
|
3892
4348
|
function $splitTokenAtHighlightBoundaries(token, tokenStart, highlights) {
|
|
3893
4349
|
const text = token.getTextContent();
|
|
3894
|
-
const segments =
|
|
4350
|
+
const segments = segmentTextByRanges(text, tokenStart, highlights);
|
|
3895
4351
|
|
|
3896
4352
|
if (segments.length === 1) {
|
|
3897
4353
|
const [ segment ] = segments;
|
|
3898
|
-
if (segment.
|
|
3899
|
-
$applyHighlightStyleToToken(token, segment.style);
|
|
4354
|
+
if (segment.range) {
|
|
4355
|
+
$applyHighlightStyleToToken(token, segment.range.style);
|
|
3900
4356
|
}
|
|
3901
4357
|
return [ token ]
|
|
3902
4358
|
} else {
|
|
3903
4359
|
return segments.map((segment) => {
|
|
3904
4360
|
const segmentToken = $createCodeHighlightNode(text.slice(segment.start, segment.end), token.getHighlightType());
|
|
3905
|
-
if (segment.
|
|
3906
|
-
$applyHighlightStyleToToken(segmentToken, segment.style);
|
|
4361
|
+
if (segment.range) {
|
|
4362
|
+
$applyHighlightStyleToToken(segmentToken, segment.range.style);
|
|
3907
4363
|
}
|
|
3908
4364
|
return segmentToken
|
|
3909
4365
|
})
|
|
3910
4366
|
}
|
|
3911
4367
|
}
|
|
3912
4368
|
|
|
3913
|
-
// Partition a token's text into consecutive { start, end, style } segments,
|
|
3914
|
-
// where offsets are relative to the token and style is null for the stretches
|
|
3915
|
-
// no highlight covers.
|
|
3916
|
-
function segmentTextByHighlights(text, tokenStart, highlights) {
|
|
3917
|
-
const segments = [];
|
|
3918
|
-
let cursor = 0;
|
|
3919
|
-
|
|
3920
|
-
for (const { start, end, style } of highlights) {
|
|
3921
|
-
const from = Math.max(start - tokenStart, cursor);
|
|
3922
|
-
const to = Math.min(end - tokenStart, text.length);
|
|
3923
|
-
|
|
3924
|
-
if (from < to) {
|
|
3925
|
-
if (from > cursor) {
|
|
3926
|
-
segments.push({ start: cursor, end: from, style: null });
|
|
3927
|
-
}
|
|
3928
|
-
segments.push({ start: from, end: to, style });
|
|
3929
|
-
cursor = to;
|
|
3930
|
-
}
|
|
3931
|
-
}
|
|
3932
|
-
|
|
3933
|
-
if (cursor < text.length) {
|
|
3934
|
-
segments.push({ start: cursor, end: text.length, style: null });
|
|
3935
|
-
}
|
|
3936
|
-
|
|
3937
|
-
return segments
|
|
3938
|
-
}
|
|
3939
|
-
|
|
3940
4369
|
function $applyHighlightStyleToToken(token, style) {
|
|
3941
4370
|
token.setStyle(style);
|
|
3942
4371
|
$setCodeHighlightFormat(token, true);
|
|
@@ -3946,17 +4375,24 @@ function $buildChildRanges(codeNode) {
|
|
|
3946
4375
|
const childRanges = [];
|
|
3947
4376
|
let charOffset = 0;
|
|
3948
4377
|
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
4378
|
+
function walk(node) {
|
|
4379
|
+
for (const child of node.getChildren()) {
|
|
4380
|
+
if ($isElementNode(child)) {
|
|
4381
|
+
// e.g. a LinkNode awaiting its first retokenization
|
|
4382
|
+
walk(child);
|
|
4383
|
+
} else if ($isCodeHighlightNode(child) || $isTextNode(child)) {
|
|
4384
|
+
const text = child.getTextContent();
|
|
4385
|
+
childRanges.push({ node: child, start: charOffset, end: charOffset + text.length });
|
|
4386
|
+
charOffset += text.length;
|
|
4387
|
+
} else {
|
|
4388
|
+
// LineBreakNode - counts as 1 character (\n)
|
|
4389
|
+
charOffset += 1;
|
|
4390
|
+
}
|
|
3957
4391
|
}
|
|
3958
4392
|
}
|
|
3959
4393
|
|
|
4394
|
+
walk(codeNode);
|
|
4395
|
+
|
|
3960
4396
|
return childRanges
|
|
3961
4397
|
}
|
|
3962
4398
|
|
|
@@ -3979,11 +4415,15 @@ function $extractHighlightRangesFromCodeNode(codeNode) {
|
|
|
3979
4415
|
|
|
3980
4416
|
function buildCanonicalizers(config) {
|
|
3981
4417
|
return [
|
|
3982
|
-
new StyleCanonicalizer("color", [ ...config.buttons.color, ...config.permit.color ]),
|
|
3983
|
-
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"] ])
|
|
3984
4420
|
]
|
|
3985
4421
|
}
|
|
3986
4422
|
|
|
4423
|
+
function colorValues(buttons) {
|
|
4424
|
+
return normalizeColorButtons(buttons).map((button) => button.value)
|
|
4425
|
+
}
|
|
4426
|
+
|
|
3987
4427
|
function $toggleSelectionStyles(editor, styles) {
|
|
3988
4428
|
const selection = $getSelection();
|
|
3989
4429
|
if (!$isRangeSelection(selection)) return
|
|
@@ -4922,29 +5362,25 @@ class Selection {
|
|
|
4922
5362
|
}, COMMAND_PRIORITY_LOW),
|
|
4923
5363
|
|
|
4924
5364
|
this.editor.registerCommand(SELECTION_CHANGE_COMMAND, () => {
|
|
4925
|
-
this.#
|
|
5365
|
+
this.#preserveFocusOutsideEditor();
|
|
4926
5366
|
return false
|
|
4927
5367
|
}, COMMAND_PRIORITY_CRITICAL)
|
|
4928
5368
|
);
|
|
4929
5369
|
}
|
|
4930
5370
|
|
|
4931
|
-
//
|
|
4932
|
-
//
|
|
4933
|
-
|
|
4934
|
-
|
|
4935
|
-
// tag the update so Lexical leaves the DOM selection (and with it, focus) alone.
|
|
4936
|
-
#preserveFocusOfOtherFields() {
|
|
4937
|
-
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) {
|
|
4938
5375
|
$addUpdateTag(SKIP_DOM_SELECTION_TAG);
|
|
4939
5376
|
}
|
|
4940
5377
|
}
|
|
4941
5378
|
|
|
4942
|
-
get #
|
|
5379
|
+
get #isAnotherElementFocused() {
|
|
4943
5380
|
const rootElement = this.editor.getRootElement();
|
|
4944
5381
|
const activeElement = rootElement?.ownerDocument.activeElement;
|
|
4945
5382
|
|
|
4946
|
-
return activeElement && !rootElement.contains(activeElement)
|
|
4947
|
-
(activeElement.tagName === "INPUT" || activeElement.tagName === "TEXTAREA" || activeElement.isContentEditable)
|
|
5383
|
+
return activeElement && activeElement !== rootElement.ownerDocument.body && !rootElement.contains(activeElement)
|
|
4948
5384
|
}
|
|
4949
5385
|
|
|
4950
5386
|
#listenForNodeSelections() {
|
|
@@ -5076,10 +5512,17 @@ class Selection {
|
|
|
5076
5512
|
|| this.#selectInLexical(this.topLevelNodeAfterCursor)
|
|
5077
5513
|
}
|
|
5078
5514
|
|
|
5515
|
+
// hasNodeSelection reads the committed state, while $getSelection() resolves
|
|
5516
|
+
// against the pending one — so the selection here can be missing, no longer a
|
|
5517
|
+
// NodeSelection, or reference a node that no longer exists (empty editor,
|
|
5518
|
+
// just-deleted node, decorator boundary). Only invoke the callback when a
|
|
5519
|
+
// node actually resolves; returning false lets the callers fall back —
|
|
5520
|
+
// #selectInLexical for plain arrows, native pass-through for shift-arrows.
|
|
5079
5521
|
#withCurrentNodeSelectionNode(fn) {
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5522
|
+
const selection = this.hasNodeSelection ? $getSelection() : null;
|
|
5523
|
+
const currentNode = $isNodeSelection(selection) ? selection.getNodes()[0] : undefined;
|
|
5524
|
+
|
|
5525
|
+
return currentNode ? fn(currentNode) : false
|
|
5083
5526
|
}
|
|
5084
5527
|
|
|
5085
5528
|
#rangeSelectDecorator(node, direction = "forward") {
|
|
@@ -5534,178 +5977,6 @@ class EditorConfiguration {
|
|
|
5534
5977
|
}
|
|
5535
5978
|
}
|
|
5536
5979
|
|
|
5537
|
-
class ImageGalleryNode extends ElementNode {
|
|
5538
|
-
$config() {
|
|
5539
|
-
return this.config("image_gallery", {
|
|
5540
|
-
extends: ElementNode,
|
|
5541
|
-
})
|
|
5542
|
-
}
|
|
5543
|
-
|
|
5544
|
-
static transform() {
|
|
5545
|
-
return (gallery) => {
|
|
5546
|
-
gallery.unwrapEmptyNode()
|
|
5547
|
-
|| gallery.replaceWithSingularChild()
|
|
5548
|
-
|| gallery.splitAroundInvalidChild();
|
|
5549
|
-
}
|
|
5550
|
-
}
|
|
5551
|
-
|
|
5552
|
-
static importDOM() {
|
|
5553
|
-
return {
|
|
5554
|
-
div: (element) => {
|
|
5555
|
-
if (!this.#isGalleryElement(element)) return null
|
|
5556
|
-
|
|
5557
|
-
return {
|
|
5558
|
-
conversion: () => {
|
|
5559
|
-
return {
|
|
5560
|
-
node: $createImageGalleryNode()
|
|
5561
|
-
}
|
|
5562
|
-
},
|
|
5563
|
-
priority: 2
|
|
5564
|
-
}
|
|
5565
|
-
}
|
|
5566
|
-
}
|
|
5567
|
-
}
|
|
5568
|
-
|
|
5569
|
-
static canCollapseWith(node) {
|
|
5570
|
-
return $isImageGalleryNode(node) || this.isValidChild(node)
|
|
5571
|
-
}
|
|
5572
|
-
|
|
5573
|
-
static isValidChild(node) {
|
|
5574
|
-
return $isActionTextAttachmentNode(node) && node.isPreviewableImage
|
|
5575
|
-
}
|
|
5576
|
-
|
|
5577
|
-
static #isGalleryElement(element) {
|
|
5578
|
-
const attachmentChildren = element.querySelectorAll(`:scope > :is(${this.#attachmentTags.join()})`);
|
|
5579
|
-
return element.textContent.trim() === ""
|
|
5580
|
-
&& attachmentChildren.length > 0
|
|
5581
|
-
&& element.children.length === attachmentChildren.length
|
|
5582
|
-
}
|
|
5583
|
-
|
|
5584
|
-
static get #attachmentTags() {
|
|
5585
|
-
return Object.keys(ActionTextAttachmentNode.importDOM())
|
|
5586
|
-
}
|
|
5587
|
-
|
|
5588
|
-
createDOM() {
|
|
5589
|
-
const div = document.createElement("div");
|
|
5590
|
-
div.className = this.#galleryClassNames;
|
|
5591
|
-
return div
|
|
5592
|
-
}
|
|
5593
|
-
|
|
5594
|
-
updateDOM(_prevNode, dom) {
|
|
5595
|
-
dom.className = this.#galleryClassNames;
|
|
5596
|
-
return false
|
|
5597
|
-
}
|
|
5598
|
-
|
|
5599
|
-
canBeEmpty() {
|
|
5600
|
-
// Return `true` to conform to `$isBlock(node)`
|
|
5601
|
-
// We clean-up empty galleries with a transform
|
|
5602
|
-
return true
|
|
5603
|
-
}
|
|
5604
|
-
|
|
5605
|
-
collapseAtStart(_selection) {
|
|
5606
|
-
return true
|
|
5607
|
-
}
|
|
5608
|
-
|
|
5609
|
-
insertNewAfter(selection, restoreSelection) {
|
|
5610
|
-
const selectionBeforeLastChild = selection.anchor.getNode().is(this) && selection.anchor.offset == this.getChildrenSize() - 1;
|
|
5611
|
-
if (selectionBeforeLastChild) {
|
|
5612
|
-
const paragraph = $createParagraphNode();
|
|
5613
|
-
this.insertAfter(paragraph, false);
|
|
5614
|
-
paragraph.insertAfter(this.getLastChild(), false);
|
|
5615
|
-
paragraph.selectEnd();
|
|
5616
|
-
|
|
5617
|
-
// return null as selection has been managed
|
|
5618
|
-
return null
|
|
5619
|
-
}
|
|
5620
|
-
|
|
5621
|
-
const newNode = $createImageGalleryNode();
|
|
5622
|
-
this.insertAfter(newNode, restoreSelection);
|
|
5623
|
-
return newNode
|
|
5624
|
-
}
|
|
5625
|
-
|
|
5626
|
-
getImageAttachments() {
|
|
5627
|
-
const children = this.getChildren();
|
|
5628
|
-
return children.filter($isActionTextAttachmentNode)
|
|
5629
|
-
}
|
|
5630
|
-
|
|
5631
|
-
exportDOM() {
|
|
5632
|
-
const div = document.createElement("div");
|
|
5633
|
-
div.className = this.#galleryClassNames;
|
|
5634
|
-
return { element: div }
|
|
5635
|
-
}
|
|
5636
|
-
|
|
5637
|
-
collapseWith(node, backwards) {
|
|
5638
|
-
if (!ImageGalleryNode.canCollapseWith(node)) return false
|
|
5639
|
-
|
|
5640
|
-
if (backwards) {
|
|
5641
|
-
$insertFirst(this, node);
|
|
5642
|
-
} else {
|
|
5643
|
-
this.append(node);
|
|
5644
|
-
}
|
|
5645
|
-
|
|
5646
|
-
$unwrapAndFilterDescendants(this, ImageGalleryNode.isValidChild);
|
|
5647
|
-
|
|
5648
|
-
return true
|
|
5649
|
-
}
|
|
5650
|
-
|
|
5651
|
-
unwrapEmptyNode() {
|
|
5652
|
-
if (this.isEmpty()) {
|
|
5653
|
-
const paragraph = $createParagraphNode();
|
|
5654
|
-
return this.replace(paragraph)
|
|
5655
|
-
}
|
|
5656
|
-
}
|
|
5657
|
-
|
|
5658
|
-
replaceWithSingularChild() {
|
|
5659
|
-
if (this.#hasSingularChild) {
|
|
5660
|
-
const child = this.getFirstChild();
|
|
5661
|
-
return this.replace($makeSafeForRoot(child))
|
|
5662
|
-
}
|
|
5663
|
-
}
|
|
5664
|
-
|
|
5665
|
-
splitAroundInvalidChild() {
|
|
5666
|
-
for (const child of $firstToLastIterator(this)) {
|
|
5667
|
-
if (ImageGalleryNode.isValidChild(child)) continue
|
|
5668
|
-
|
|
5669
|
-
const poppedNode = $makeSafeForRoot(child);
|
|
5670
|
-
const [ topGallery, secondGallery ] = this.splitAtIndex(poppedNode.getIndexWithinParent());
|
|
5671
|
-
topGallery.insertAfter(poppedNode);
|
|
5672
|
-
poppedNode.selectEnd();
|
|
5673
|
-
|
|
5674
|
-
// remove an empty gallery rather than let it unwrap to a paragraph
|
|
5675
|
-
if (secondGallery.isEmpty()) secondGallery.remove();
|
|
5676
|
-
|
|
5677
|
-
break
|
|
5678
|
-
}
|
|
5679
|
-
}
|
|
5680
|
-
|
|
5681
|
-
splitAtIndex(index) {
|
|
5682
|
-
return $splitNode(this, index)
|
|
5683
|
-
}
|
|
5684
|
-
|
|
5685
|
-
get #hasSingularChild() {
|
|
5686
|
-
return this.getChildrenSize() === 1
|
|
5687
|
-
}
|
|
5688
|
-
|
|
5689
|
-
get #galleryClassNames() {
|
|
5690
|
-
return `attachment-gallery attachment-gallery--${this.getChildrenSize()}`
|
|
5691
|
-
}
|
|
5692
|
-
}
|
|
5693
|
-
|
|
5694
|
-
function $createImageGalleryNode() {
|
|
5695
|
-
return new ImageGalleryNode()
|
|
5696
|
-
}
|
|
5697
|
-
|
|
5698
|
-
function $isImageGalleryNode(node) {
|
|
5699
|
-
return node instanceof ImageGalleryNode
|
|
5700
|
-
}
|
|
5701
|
-
|
|
5702
|
-
function $findOrCreateGalleryForImage(node) {
|
|
5703
|
-
if (!ImageGalleryNode.canCollapseWith(node)) return null
|
|
5704
|
-
|
|
5705
|
-
const existingGallery = $getNearestNodeOfType(node, ImageGalleryNode);
|
|
5706
|
-
return existingGallery ?? $wrapNodeInElement(node, $createImageGalleryNode)
|
|
5707
|
-
}
|
|
5708
|
-
|
|
5709
5980
|
class Uploader {
|
|
5710
5981
|
#files
|
|
5711
5982
|
|
|
@@ -6266,15 +6537,18 @@ class ListItemNodeInserter extends BaseNodeInserter {
|
|
|
6266
6537
|
// very corruption we are avoiding. The block must land at the list's level.
|
|
6267
6538
|
const anchorNode = this.selection.anchor.getNode();
|
|
6268
6539
|
const outerList = this.#outermostList(anchorNode);
|
|
6269
|
-
const topItem = this.#topLevelItemFor(anchorNode, outerList);
|
|
6270
6540
|
|
|
6271
|
-
//
|
|
6272
|
-
//
|
|
6273
|
-
//
|
|
6274
|
-
|
|
6275
|
-
|
|
6276
|
-
|
|
6277
|
-
|
|
6541
|
+
// removeText() can relocate the anchor before we read it back — clean out
|
|
6542
|
+
// of the list when the removed range spanned it. With no list left to
|
|
6543
|
+
// split, default insertion applies.
|
|
6544
|
+
if (!outerList) {
|
|
6545
|
+
this.selection.insertNodes(nodes);
|
|
6546
|
+
return
|
|
6547
|
+
}
|
|
6548
|
+
|
|
6549
|
+
const topItem = this.#topLevelItemFor(anchorNode, outerList);
|
|
6550
|
+
const [ listBefore, listAfter ] = $splitNode(outerList, this.#splitIndexFor(topItem));
|
|
6551
|
+
if (topItem && $isBlankNode(topItem)) { topItem.remove(); }
|
|
6278
6552
|
|
|
6279
6553
|
let anchor = listBefore ?? listAfter;
|
|
6280
6554
|
for (const node of nodes) {
|
|
@@ -6287,6 +6561,18 @@ class ListItemNodeInserter extends BaseNodeInserter {
|
|
|
6287
6561
|
nodes.at(-1).selectNext();
|
|
6288
6562
|
}
|
|
6289
6563
|
|
|
6564
|
+
// A blank top-level bullet is just the insertion point (e.g. the user pressed
|
|
6565
|
+
// Enter to leave the list); break out of it entirely. A bullet with content —
|
|
6566
|
+
// including one wrapping a nested list — splits so its content stays in the
|
|
6567
|
+
// list. With no top-level bullet at all — removeText() relocated the anchor
|
|
6568
|
+
// onto the list node itself — the anchor offset is the boundary between items.
|
|
6569
|
+
#splitIndexFor(topItem) {
|
|
6570
|
+
if (!topItem) return this.selection.anchor.offset
|
|
6571
|
+
|
|
6572
|
+
const splitAfterItem = $isBlankNode(topItem) ? topItem.getPreviousSibling() : topItem;
|
|
6573
|
+
return splitAfterItem ? splitAfterItem.getIndexWithinParent() + 1 : 0
|
|
6574
|
+
}
|
|
6575
|
+
|
|
6290
6576
|
#outermostList(node) {
|
|
6291
6577
|
return [ node, ...node.getParents() ].reverse().find($isListNode)
|
|
6292
6578
|
}
|
|
@@ -7386,11 +7672,93 @@ class Contents {
|
|
|
7386
7672
|
}
|
|
7387
7673
|
}
|
|
7388
7674
|
|
|
7675
|
+
function parsePastedMarkdown(text) {
|
|
7676
|
+
return pasteMarked.parse(text)
|
|
7677
|
+
}
|
|
7678
|
+
|
|
7679
|
+
// A marked instance scoped to the paste flow. A fresh Marked starts from
|
|
7680
|
+
// marked's stock defaults — gfm stays on and tables still parse — and is
|
|
7681
|
+
// isolated from the global `marked` singleton, so a host bundle sharing the
|
|
7682
|
+
// deduped module can't leak `marked.use(...)` customizations into pasting.
|
|
7683
|
+
// The additions are breaks: true, matching the render pass Clipboard always
|
|
7684
|
+
// used, the `html` renderer, and the dropped `code` tokenizer.
|
|
7685
|
+
//
|
|
7389
7686
|
// Markdown reads any line indented by a tab or four spaces as a code block. Pasted
|
|
7390
7687
|
// plain text is full of incidental indentation — outlines, notes, logs — and nobody
|
|
7391
7688
|
// indents a note meaning "this is code"; they fence it. Keep fences, drop the
|
|
7392
7689
|
// indentation rule.
|
|
7393
|
-
|
|
7690
|
+
//
|
|
7691
|
+
// Following CommonMark, marked tokenizes a bare "<tag>" in prose as raw inline
|
|
7692
|
+
// (or block) HTML. That is intentional for recognized tags — pasted plain text
|
|
7693
|
+
// carrying <span style>, <mark>, <b>… is styled on import — but for an
|
|
7694
|
+
// *unrecognized* element the Lexical importer has no converter and silently
|
|
7695
|
+
// unwraps it, dropping the tag. WEBVTT speaker cues such as
|
|
7696
|
+
// "<v Nabila Abdel Nabi>", where the name lives in what the HTML parser reads
|
|
7697
|
+
// as attributes, thus vanish entirely.
|
|
7698
|
+
//
|
|
7699
|
+
// Classifying at marked's html renderer rides its public token grammar instead
|
|
7700
|
+
// of shadowing it: marked routes code (`code`/`codespan`), autolinks and
|
|
7701
|
+
// [text](<dest>) links to *other* token types that never reach this renderer,
|
|
7702
|
+
// so their literal "<...>" content is preserved for free — no pre-escaping,
|
|
7703
|
+
// code-region collection, or lexer-option syncing required. This renderer fires
|
|
7704
|
+
// only for genuine raw-HTML tokens, and marked inserts its return value verbatim
|
|
7705
|
+
// (it does not re-encode), so we escape unsupported tags to literal text here.
|
|
7706
|
+
const pasteMarked = new Marked({
|
|
7707
|
+
breaks: true,
|
|
7708
|
+
renderer: { html: renderPastedHtmlToken }
|
|
7709
|
+
}).use({ tokenizer: { code: () => undefined } });
|
|
7710
|
+
|
|
7711
|
+
// Matches a single HTML tag lexeme: an optional "/", a tag name, then attribute
|
|
7712
|
+
// characters up to the closing ">". Quoted attribute values may contain ">" —
|
|
7713
|
+
// the HTML tokenizer consumes them as part of the value, and marked's tag
|
|
7714
|
+
// grammar matches them into the same raw token — so the attribute run matches
|
|
7715
|
+
// quoted strings whole. Otherwise a lexeme like <v title="a > b"> would end at
|
|
7716
|
+
// the inner ">", escaping only the prefix and leaving the tail for DOMParser
|
|
7717
|
+
// to decode.
|
|
7718
|
+
const HTML_TAG_LEXEME = /<(\/?)([a-zA-Z][a-zA-Z0-9-]*)((?:"[^"]*"|'[^']*'|[^'">])*)>/g;
|
|
7719
|
+
|
|
7720
|
+
// Escape unknown tags *within* the raw-HTML token, keeping known tags intact.
|
|
7721
|
+
// Lexeme-level (not whole-token) escaping matters because marked can emit a
|
|
7722
|
+
// single coarse block-HTML token whose first tag is known but which nests an
|
|
7723
|
+
// unknown one — e.g. "<div><v Name> Hello</div>" arrives as one token. We keep
|
|
7724
|
+
// the <div> so it still renders, and escape the nested <v Name> so it survives
|
|
7725
|
+
// as literal text rather than being unwrapped and dropped by the importer.
|
|
7726
|
+
function renderPastedHtmlToken(token) {
|
|
7727
|
+
return token.text.replace(HTML_TAG_LEXEME, (lexeme, _slash, name) => {
|
|
7728
|
+
return isSupportedHtmlElement(name) ? lexeme : escapeHtml(lexeme)
|
|
7729
|
+
})
|
|
7730
|
+
}
|
|
7731
|
+
|
|
7732
|
+
// The renderer's output is final, so escape the full lexeme — "&", "<" and ">" —
|
|
7733
|
+
// for an exact round-trip (e.g. "<v A B>" → "<v A&nbsp;B>").
|
|
7734
|
+
function escapeHtml(text) {
|
|
7735
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">")
|
|
7736
|
+
}
|
|
7737
|
+
|
|
7738
|
+
// "Supported" — safe to pass through for the importer to handle — is decided from
|
|
7739
|
+
// the element taxonomy, not the importer's converter registry. The importer both
|
|
7740
|
+
// converts tags it has a converter for (span→mark, b→bold, tr/td→table cells) and
|
|
7741
|
+
// transparently traverses standard structural wrappers it has *no* converter for
|
|
7742
|
+
// (thead, tbody, tfoot, colgroup, caption), keeping their children. A converter
|
|
7743
|
+
// registry (editor._htmlConversions) lists only the former, so classifying by it
|
|
7744
|
+
// escaped those wrappers to literal text and corrupted pasted tables — foster
|
|
7745
|
+
// parenting hoisted "<thead>…" out as a stray paragraph. The taxonomy covers
|
|
7746
|
+
// both: a standard HTML element is either converted or losslessly unwrapped.
|
|
7747
|
+
//
|
|
7748
|
+
// Two exclusions escape the tag to literal text instead:
|
|
7749
|
+
// - Invented tags (WEBVTT's "<v Nabila Abdel Nabi>", "<foo>"): document
|
|
7750
|
+
// .createElement returns HTMLUnknownElement. The importer would unwrap them,
|
|
7751
|
+
// dropping the tag — and for <v Name> the name lives in what parses as
|
|
7752
|
+
// attributes, so it vanishes entirely.
|
|
7753
|
+
// - Custom elements (turbo-frame, bc-attachment, action-text-attachment): a
|
|
7754
|
+
// hyphenated name is a custom element (HTML spec; no standard element has a
|
|
7755
|
+
// hyphen). They're excluded even when the importer registers a converter, so
|
|
7756
|
+
// a plain-text paste can never materialize an attachment/widget. Legitimate
|
|
7757
|
+
// attachments arrive through the rich-HTML paste path, not here.
|
|
7758
|
+
function isSupportedHtmlElement(name) {
|
|
7759
|
+
const tag = name.toLowerCase();
|
|
7760
|
+
return !(document.createElement(tag) instanceof HTMLUnknownElement) && !tag.includes("-")
|
|
7761
|
+
}
|
|
7394
7762
|
|
|
7395
7763
|
class Clipboard {
|
|
7396
7764
|
#listeners = new ListenerBin()
|
|
@@ -7578,7 +7946,7 @@ class Clipboard {
|
|
|
7578
7946
|
}
|
|
7579
7947
|
|
|
7580
7948
|
#pasteMarkdown(text) {
|
|
7581
|
-
const html =
|
|
7949
|
+
const html = parsePastedMarkdown(text);
|
|
7582
7950
|
const doc = parseHtml(html);
|
|
7583
7951
|
|
|
7584
7952
|
if (this.#isPlainTextWithoutMarkdown(doc)) {
|
|
@@ -7777,6 +8145,134 @@ class BrowserAdapter {
|
|
|
7777
8145
|
}
|
|
7778
8146
|
}
|
|
7779
8147
|
|
|
8148
|
+
// The code retokenizer only knows CodeHighlightNode, LineBreakNode and
|
|
8149
|
+
// TabNode, so a LinkNode inside a code block — Trix-authored documents allow
|
|
8150
|
+
// links in code — is destroyed on the first retokenization. Links survive as
|
|
8151
|
+
// state on the tokens instead: this state records the link attributes on each
|
|
8152
|
+
// CodeHighlightNode the link covers, the extraction below reads it (or a
|
|
8153
|
+
// still-untokenized LinkNode) back into ranges before every retokenization,
|
|
8154
|
+
// and the tokenizer reapplies it to the fresh tokens.
|
|
8155
|
+
const codeLinkState = createState("codeLink", {
|
|
8156
|
+
parse: (value) => (value && typeof value.url === "string" ? value : null)
|
|
8157
|
+
});
|
|
8158
|
+
|
|
8159
|
+
function $getCodeLink(node) {
|
|
8160
|
+
return $getState(node, codeLinkState)
|
|
8161
|
+
}
|
|
8162
|
+
|
|
8163
|
+
function $setCodeLink(node, link) {
|
|
8164
|
+
$setState(node, codeLinkState, link);
|
|
8165
|
+
}
|
|
8166
|
+
|
|
8167
|
+
// Build the list of { start, end, link } ranges covering every link in a code
|
|
8168
|
+
// block, with offsets into the block's text content. Links appear either as
|
|
8169
|
+
// LinkNode descendants (imported or just created, before their first
|
|
8170
|
+
// retokenization) or as link state on tokens (after it). The walk recurses
|
|
8171
|
+
// through nested elements because `<pre><code>` imports of multi-line content
|
|
8172
|
+
// briefly produce a CodeNode nested inside another before the outer one's
|
|
8173
|
+
// retokenization flattens them.
|
|
8174
|
+
function $extractLinkRangesFromCodeNode(codeNode) {
|
|
8175
|
+
const ranges = [];
|
|
8176
|
+
let offset = 0;
|
|
8177
|
+
|
|
8178
|
+
function walk(node) {
|
|
8179
|
+
for (const child of node.getChildren()) {
|
|
8180
|
+
if ($isLinkNode(child)) {
|
|
8181
|
+
const size = child.getTextContent().length;
|
|
8182
|
+
appendRange(ranges, { start: offset, end: offset + size, link: linkAttributesFrom(child) });
|
|
8183
|
+
offset += size;
|
|
8184
|
+
} else if ($isElementNode(child)) {
|
|
8185
|
+
walk(child);
|
|
8186
|
+
} else {
|
|
8187
|
+
const size = child.getTextContent().length;
|
|
8188
|
+
if ($isTextNode(child)) {
|
|
8189
|
+
const link = $getCodeLink(child);
|
|
8190
|
+
if (link) {
|
|
8191
|
+
appendRange(ranges, { start: offset, end: offset + size, link });
|
|
8192
|
+
}
|
|
8193
|
+
}
|
|
8194
|
+
offset += size;
|
|
8195
|
+
}
|
|
8196
|
+
}
|
|
8197
|
+
}
|
|
8198
|
+
|
|
8199
|
+
walk(codeNode);
|
|
8200
|
+
|
|
8201
|
+
return ranges
|
|
8202
|
+
}
|
|
8203
|
+
|
|
8204
|
+
function $applyLinkRangesToTokens(tokens, ranges) {
|
|
8205
|
+
if (ranges.length === 0) return tokens
|
|
8206
|
+
|
|
8207
|
+
const linkedTokens = [];
|
|
8208
|
+
let offset = 0;
|
|
8209
|
+
|
|
8210
|
+
for (const token of tokens) {
|
|
8211
|
+
if ($isCodeHighlightNode(token)) {
|
|
8212
|
+
linkedTokens.push(...$splitTokenAtLinkBoundaries(token, offset, ranges));
|
|
8213
|
+
} else {
|
|
8214
|
+
linkedTokens.push(token);
|
|
8215
|
+
}
|
|
8216
|
+
offset += token.getTextContentSize();
|
|
8217
|
+
}
|
|
8218
|
+
|
|
8219
|
+
return linkedTokens
|
|
8220
|
+
}
|
|
8221
|
+
|
|
8222
|
+
function $splitTokenAtLinkBoundaries(token, tokenStart, ranges) {
|
|
8223
|
+
const text = token.getTextContent();
|
|
8224
|
+
const segments = segmentTextByRanges(text, tokenStart, ranges);
|
|
8225
|
+
|
|
8226
|
+
if (segments.length === 1) {
|
|
8227
|
+
const [ segment ] = segments;
|
|
8228
|
+
if (segment.range) {
|
|
8229
|
+
$setCodeLink(token, segment.range.link);
|
|
8230
|
+
}
|
|
8231
|
+
return [ token ]
|
|
8232
|
+
} else {
|
|
8233
|
+
return segments.map((segment) => {
|
|
8234
|
+
const segmentToken = $cloneTokenSlice(token, text.slice(segment.start, segment.end));
|
|
8235
|
+
if (segment.range) {
|
|
8236
|
+
$setCodeLink(segmentToken, segment.range.link);
|
|
8237
|
+
}
|
|
8238
|
+
return segmentToken
|
|
8239
|
+
})
|
|
8240
|
+
}
|
|
8241
|
+
}
|
|
8242
|
+
|
|
8243
|
+
function $cloneTokenSlice(token, text) {
|
|
8244
|
+
const segmentToken = $createCodeHighlightNode(text, token.getHighlightType());
|
|
8245
|
+
segmentToken.setStyle(token.getStyle());
|
|
8246
|
+
// CodeHighlightNode.setFormat is a no-op, so carry the format over directly
|
|
8247
|
+
segmentToken.getWritable().__format = token.getFormat();
|
|
8248
|
+
return segmentToken
|
|
8249
|
+
}
|
|
8250
|
+
|
|
8251
|
+
// Consecutive children carrying the same link merge into a single range, so a
|
|
8252
|
+
// link split across tokens by an earlier retokenization keeps covering fresh
|
|
8253
|
+
// tokens as one contiguous stretch however they retokenize.
|
|
8254
|
+
function appendRange(ranges, range) {
|
|
8255
|
+
const previous = ranges[ranges.length - 1];
|
|
8256
|
+
|
|
8257
|
+
if (previous && previous.end === range.start && sameLink(previous.link, range.link)) {
|
|
8258
|
+
previous.end = range.end;
|
|
8259
|
+
} else {
|
|
8260
|
+
ranges.push(range);
|
|
8261
|
+
}
|
|
8262
|
+
}
|
|
8263
|
+
|
|
8264
|
+
function sameLink(a, b) {
|
|
8265
|
+
return a.url === b.url && a.target === b.target && a.rel === b.rel && a.title === b.title
|
|
8266
|
+
}
|
|
8267
|
+
|
|
8268
|
+
function linkAttributesFrom(linkNode) {
|
|
8269
|
+
const link = { url: linkNode.getURL() };
|
|
8270
|
+
if (linkNode.getTarget()) link.target = linkNode.getTarget();
|
|
8271
|
+
if (linkNode.getRel()) link.rel = linkNode.getRel();
|
|
8272
|
+
if (linkNode.getTitle()) link.title = linkNode.getTitle();
|
|
8273
|
+
return link
|
|
8274
|
+
}
|
|
8275
|
+
|
|
7780
8276
|
// Custom TextNode exportDOM that avoids redundant wrapping.
|
|
7781
8277
|
//
|
|
7782
8278
|
// Lexical's built-in TextNode.exportDOM() calls createDOM() which produces semantic tags
|
|
@@ -7791,6 +8287,30 @@ class BrowserAdapter {
|
|
|
7791
8287
|
// Any <span> elements produced by createDOM() are unwrapped, since they only carry
|
|
7792
8288
|
// editor classes that aren't meaningful in exported HTML.
|
|
7793
8289
|
|
|
8290
|
+
// A CodeHighlightNode exports like any other text node, plus an <a> wrapper
|
|
8291
|
+
// when it carries link state — that's how a link survives inside a code
|
|
8292
|
+
// block, whose retokenizer would destroy an actual LinkNode child.
|
|
8293
|
+
function exportCodeHighlightNodeDOM(editor, codeHighlightNode) {
|
|
8294
|
+
const output = exportTextNodeDOM(editor, codeHighlightNode);
|
|
8295
|
+
const link = $getCodeLink(codeHighlightNode);
|
|
8296
|
+
|
|
8297
|
+
if (link) {
|
|
8298
|
+
return { element: wrapWithAnchor(output.element, link) }
|
|
8299
|
+
} else {
|
|
8300
|
+
return output
|
|
8301
|
+
}
|
|
8302
|
+
}
|
|
8303
|
+
|
|
8304
|
+
function wrapWithAnchor(element, link) {
|
|
8305
|
+
const anchor = document.createElement("a");
|
|
8306
|
+
anchor.setAttribute("href", link.url);
|
|
8307
|
+
if (link.target) anchor.setAttribute("target", link.target);
|
|
8308
|
+
if (link.rel) anchor.setAttribute("rel", link.rel);
|
|
8309
|
+
if (link.title) anchor.setAttribute("title", link.title);
|
|
8310
|
+
anchor.appendChild(element);
|
|
8311
|
+
return anchor
|
|
8312
|
+
}
|
|
8313
|
+
|
|
7794
8314
|
function exportTextNodeDOM(editor, textNode) {
|
|
7795
8315
|
const element = textNode.createDOM(editor._config, editor);
|
|
7796
8316
|
element.style.whiteSpace = "pre-wrap";
|
|
@@ -8022,12 +8542,34 @@ class CodeHighlightingExtension extends LexxyExtension {
|
|
|
8022
8542
|
return defineExtension({
|
|
8023
8543
|
name: "lexxy/code-highlighting",
|
|
8024
8544
|
register(editor) {
|
|
8025
|
-
return registerCodeHighlighting(editor,
|
|
8545
|
+
return registerCodeHighlighting(editor, buildMarkupPreservingTokenizer(editor))
|
|
8026
8546
|
}
|
|
8027
8547
|
})
|
|
8028
8548
|
}
|
|
8029
8549
|
}
|
|
8030
8550
|
|
|
8551
|
+
// The code retokenizer replaces a code block's children with freshly created
|
|
8552
|
+
// tokens that carry no styles or links, which would drop color highlights and
|
|
8553
|
+
// hyperlinks on every edit. This tokenizer wraps the stock Prism tokenizer to
|
|
8554
|
+
// restore both: it recovers the block's highlight and link ranges — staged
|
|
8555
|
+
// during HTML import, or read from the children the fresh tokens are about to
|
|
8556
|
+
// replace — and reapplies them to the fresh tokens before the retokenizer
|
|
8557
|
+
// splices them in.
|
|
8558
|
+
function buildMarkupPreservingTokenizer(editor) {
|
|
8559
|
+
return {
|
|
8560
|
+
defaultLanguage: PrismTokenizer.defaultLanguage,
|
|
8561
|
+
tokenize(code, language) {
|
|
8562
|
+
return PrismTokenizer.tokenize(code, language)
|
|
8563
|
+
},
|
|
8564
|
+
$tokenize(codeNode, language) {
|
|
8565
|
+
const linkRanges = $extractLinkRangesFromCodeNode(codeNode);
|
|
8566
|
+
const highlightRanges = $takeHighlightRanges(editor, codeNode);
|
|
8567
|
+
const tokens = PrismTokenizer.$tokenize(codeNode, language);
|
|
8568
|
+
return $applyLinkRangesToTokens($applyHighlightRangesToTokens(tokens, highlightRanges), linkRanges)
|
|
8569
|
+
}
|
|
8570
|
+
}
|
|
8571
|
+
}
|
|
8572
|
+
|
|
8031
8573
|
const TRIX_LANGUAGE_ATTR = "language";
|
|
8032
8574
|
|
|
8033
8575
|
class TrixContentExtension extends LexxyExtension {
|
|
@@ -8523,75 +9065,387 @@ class AttachmentDragAndDrop {
|
|
|
8523
9065
|
if (position === "before") {
|
|
8524
9066
|
targetNode.insertBefore(draggedNode);
|
|
8525
9067
|
} else {
|
|
8526
|
-
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"
|
|
8527
9340
|
}
|
|
8528
9341
|
}
|
|
8529
9342
|
|
|
8530
|
-
#
|
|
8531
|
-
|
|
8532
|
-
|
|
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
|
+
}
|
|
8533
9351
|
|
|
8534
|
-
|
|
8535
|
-
|
|
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
|
|
8536
9358
|
|
|
8537
|
-
|
|
8538
|
-
|
|
8539
|
-
|
|
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
|
+
}
|
|
8540
9371
|
|
|
8541
|
-
|
|
9372
|
+
destroy() {
|
|
9373
|
+
this.#listeners.dispose();
|
|
9374
|
+
this.#restoreLabel();
|
|
9375
|
+
}
|
|
8542
9376
|
|
|
8543
|
-
|
|
9377
|
+
#updateLabel = ({ editorState }) => {
|
|
9378
|
+
if (document.activeElement === this.#editor.getRootElement()) {
|
|
9379
|
+
editorState.read(() => {
|
|
9380
|
+
const selection = $getSelection();
|
|
9381
|
+
const attachment = customAttachmentSelectedBy(selection) || customAttachmentBesideAnchor(selection);
|
|
8544
9382
|
|
|
8545
|
-
|
|
8546
|
-
|
|
8547
|
-
|
|
8548
|
-
|
|
9383
|
+
if (attachment) {
|
|
9384
|
+
this.#exposeLabelOn(attachment);
|
|
9385
|
+
} else {
|
|
9386
|
+
this.#restoreLabel();
|
|
9387
|
+
}
|
|
9388
|
+
});
|
|
8549
9389
|
} else {
|
|
8550
|
-
|
|
8551
|
-
listAfter.insertBefore(draggedNode);
|
|
9390
|
+
this.#restoreLabel();
|
|
8552
9391
|
}
|
|
8553
9392
|
}
|
|
8554
9393
|
|
|
8555
|
-
#
|
|
8556
|
-
const
|
|
8557
|
-
if (
|
|
8558
|
-
|
|
8559
|
-
|
|
8560
|
-
|
|
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
|
+
}
|
|
8561
9402
|
|
|
8562
|
-
|
|
9403
|
+
#restoreLabel() {
|
|
9404
|
+
if (this.#active) {
|
|
9405
|
+
const { attachment, figure } = this.#active;
|
|
9406
|
+
attachment.restoreLabel(figure);
|
|
9407
|
+
this.#active = null;
|
|
9408
|
+
}
|
|
9409
|
+
}
|
|
9410
|
+
}
|
|
8563
9411
|
|
|
8564
|
-
|
|
8565
|
-
|
|
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]
|
|
8566
9417
|
} else {
|
|
8567
|
-
|
|
9418
|
+
return null
|
|
8568
9419
|
}
|
|
9420
|
+
} else {
|
|
9421
|
+
return null
|
|
8569
9422
|
}
|
|
9423
|
+
}
|
|
8570
9424
|
|
|
8571
|
-
|
|
8572
|
-
|
|
8573
|
-
|
|
8574
|
-
|
|
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 = [];
|
|
8575
9432
|
|
|
8576
|
-
if (
|
|
8577
|
-
const
|
|
8578
|
-
if (
|
|
8579
|
-
|
|
8580
|
-
|
|
8581
|
-
}
|
|
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 });
|
|
8582
9440
|
}
|
|
8583
9441
|
|
|
8584
|
-
|
|
8585
|
-
|
|
8586
|
-
|
|
8587
|
-
cancelAnimationFrame(this.#rafId);
|
|
8588
|
-
this.#rafId = null;
|
|
8589
|
-
}
|
|
9442
|
+
const nearest = candidates
|
|
9443
|
+
.filter(({ node }) => $isCustomActionTextAttachmentNode(node))
|
|
9444
|
+
.sort((a, b) => a.distance - b.distance)[0];
|
|
8590
9445
|
|
|
8591
|
-
|
|
8592
|
-
|
|
8593
|
-
|
|
8594
|
-
}
|
|
9446
|
+
return nearest?.node ?? null
|
|
9447
|
+
} else {
|
|
9448
|
+
return null
|
|
8595
9449
|
}
|
|
8596
9450
|
}
|
|
8597
9451
|
|
|
@@ -8621,12 +9475,19 @@ class AttachmentsExtension extends LexxyExtension {
|
|
|
8621
9475
|
],
|
|
8622
9476
|
register: (editor) => {
|
|
8623
9477
|
const dragAndDrop = new AttachmentDragAndDrop(editor);
|
|
9478
|
+
const keyboardMove = new AttachmentKeyboardMove(editor);
|
|
9479
|
+
const fakeSelection = new AttachmentFakeSelection(editor);
|
|
9480
|
+
const customAttachmentLabels = new CustomAttachmentLabels(editor);
|
|
8624
9481
|
|
|
8625
9482
|
return mergeRegister(
|
|
8626
9483
|
editor.registerNodeTransform(ActionTextAttachmentNode, $extractAttachmentFromParagraph),
|
|
8627
9484
|
editor.registerCommand(DELETE_CHARACTER_COMMAND, $collapseIntoGallery, COMMAND_PRIORITY_NORMAL),
|
|
9485
|
+
editor.registerCommand(KEY_TAB_COMMAND, $focusCaptionFromSelectedAttachment, COMMAND_PRIORITY_HIGH),
|
|
8628
9486
|
editor.registerMutationListener(ActionTextAttachmentUploadNode, this.#handleUploadMutations.bind(this)),
|
|
8629
|
-
() => dragAndDrop.destroy()
|
|
9487
|
+
() => dragAndDrop.destroy(),
|
|
9488
|
+
() => keyboardMove.destroy(),
|
|
9489
|
+
() => fakeSelection.destroy(),
|
|
9490
|
+
() => customAttachmentLabels.destroy()
|
|
8630
9491
|
)
|
|
8631
9492
|
}
|
|
8632
9493
|
})
|
|
@@ -8724,6 +9585,16 @@ function $collapseAtGalleryEdge(anchor, backwards) {
|
|
|
8724
9585
|
}
|
|
8725
9586
|
}
|
|
8726
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
|
+
|
|
8727
9598
|
// Manual selection handling to prevent Lexical merging the gallery with a <p> and unwrapping it
|
|
8728
9599
|
function $moveSelectionBeforeGallery(anchor) {
|
|
8729
9600
|
const previousNode = anchor.getNode().getPreviousSibling();
|
|
@@ -9455,6 +10326,7 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
9455
10326
|
#validity = new Map()
|
|
9456
10327
|
#validationTextArea = document.createElement("textarea")
|
|
9457
10328
|
#uploadRequests
|
|
10329
|
+
#liveRegion
|
|
9458
10330
|
|
|
9459
10331
|
constructor() {
|
|
9460
10332
|
super();
|
|
@@ -9485,6 +10357,10 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
9485
10357
|
this.clipboard = new Clipboard(this);
|
|
9486
10358
|
this.#disposables.push(this.clipboard);
|
|
9487
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
|
+
|
|
9488
10364
|
this.adapter = new BrowserAdapter();
|
|
9489
10365
|
this.#uploadRequests = new UploadRequests();
|
|
9490
10366
|
|
|
@@ -9630,6 +10506,16 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
9630
10506
|
return dispatch(this, "lexxy:file-accept", { file }, true)
|
|
9631
10507
|
}
|
|
9632
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
|
+
|
|
9633
10519
|
$generateNodesFromDOM(doc, { editor = this.editor } = {}) {
|
|
9634
10520
|
let nodes = $generateNodesFromDOM(editor, doc);
|
|
9635
10521
|
if ($hasUpdateTag(PASTE_TAG)) nodes = $convertInlineImageDataURIs(nodes, this);
|
|
@@ -9814,7 +10700,7 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
9814
10700
|
theme: theme,
|
|
9815
10701
|
nodes: this.#lexicalNodes,
|
|
9816
10702
|
html: {
|
|
9817
|
-
export: new Map([ [ TextNode, exportTextNodeDOM ], [ CodeHighlightNode,
|
|
10703
|
+
export: new Map([ [ TextNode, exportTextNodeDOM ], [ CodeHighlightNode, exportCodeHighlightNodeDOM ] ])
|
|
9818
10704
|
},
|
|
9819
10705
|
$initialEditorState: (editor) => {
|
|
9820
10706
|
this.#configureSanitizer(editor);
|
|
@@ -9995,6 +10881,9 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
9995
10881
|
);
|
|
9996
10882
|
this.#registerTableComponents();
|
|
9997
10883
|
this.#registerCodeLanguagePicker();
|
|
10884
|
+
if (this.supportsAttachments) {
|
|
10885
|
+
this.#registerAttachmentToolbar();
|
|
10886
|
+
}
|
|
9998
10887
|
if (this.supportsMarkdown) {
|
|
9999
10888
|
const transformers = [ ...TRANSFORMERS, HORIZONTAL_DIVIDER ];
|
|
10000
10889
|
registered.push(
|
|
@@ -10023,6 +10912,15 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
10023
10912
|
this.#disposables.push(codeLanguagePicker);
|
|
10024
10913
|
}
|
|
10025
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
|
+
|
|
10026
10924
|
#handleEnter() {
|
|
10027
10925
|
// We can't prevent these externally using regular keydown because Lexical handles it first.
|
|
10028
10926
|
this.#listeners.track(this.editor.registerCommand(
|
|
@@ -10231,21 +11129,22 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
10231
11129
|
// — triggering at most one forced reflow. The previous implementation interleaved
|
|
10232
11130
|
// setProperty/getComputedStyle/removeProperty on the same element, forcing a style
|
|
10233
11131
|
// recalc on every iteration during editor initialization.
|
|
10234
|
-
#resolveColors(property,
|
|
11132
|
+
#resolveColors(property, buttons) {
|
|
10235
11133
|
const container = document.createElement("span");
|
|
10236
11134
|
container.style.display = "none";
|
|
10237
11135
|
|
|
10238
|
-
const resolvers =
|
|
11136
|
+
const resolvers = normalizeColorButtons(buttons).map(({ value, label }) => {
|
|
10239
11137
|
const element = document.createElement("span");
|
|
10240
|
-
element.style.setProperty(property,
|
|
11138
|
+
element.style.setProperty(property, value);
|
|
10241
11139
|
container.appendChild(element);
|
|
10242
|
-
return { element, name:
|
|
11140
|
+
return { element, name: value, label }
|
|
10243
11141
|
});
|
|
10244
11142
|
|
|
10245
11143
|
styleResolverRoot().appendChild(container);
|
|
10246
11144
|
|
|
10247
|
-
const resolved = resolvers.map(({ element, name }) => ({
|
|
11145
|
+
const resolved = resolvers.map(({ element, name, label }) => ({
|
|
10248
11146
|
name,
|
|
11147
|
+
label,
|
|
10249
11148
|
value: window.getComputedStyle(element).getPropertyValue(property)
|
|
10250
11149
|
}));
|
|
10251
11150
|
|
|
@@ -10309,6 +11208,39 @@ function $getReadableTextContent(node) {
|
|
|
10309
11208
|
return node.getTextContent()
|
|
10310
11209
|
}
|
|
10311
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
|
+
|
|
10312
11244
|
class BaseSource {
|
|
10313
11245
|
// Template method to override
|
|
10314
11246
|
async buildListItems(filter = "") {
|
|
@@ -11195,46 +12127,233 @@ const DELETE_ICON = `<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg"
|
|
|
11195
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"/>
|
|
11196
12128
|
</svg>`;
|
|
11197
12129
|
|
|
11198
|
-
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
|
+
|
|
11199
12138
|
connectedCallback() {
|
|
11200
|
-
this.editorElement = this.closest("lexxy-editor");
|
|
11201
|
-
this.editor = this.editorElement.editor;
|
|
11202
12139
|
this.classList.add("lexxy-floating-controls");
|
|
12140
|
+
this.role = "toolbar";
|
|
12141
|
+
this.hidden = true;
|
|
11203
12142
|
|
|
11204
|
-
if (
|
|
11205
|
-
this.#
|
|
12143
|
+
if (this.#editor) {
|
|
12144
|
+
this.#setUpButtons();
|
|
12145
|
+
this.#monitorSelection();
|
|
12146
|
+
this.#followScrolling();
|
|
12147
|
+
this.#registerKeyboardShortcut();
|
|
11206
12148
|
}
|
|
11207
12149
|
}
|
|
11208
12150
|
|
|
11209
12151
|
disconnectedCallback() {
|
|
11210
|
-
this.
|
|
11211
|
-
|
|
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")
|
|
11212
12167
|
}
|
|
11213
12168
|
|
|
11214
|
-
#
|
|
12169
|
+
#setUpButtons() {
|
|
12170
|
+
this.innerHTML = "";
|
|
12171
|
+
|
|
11215
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
|
+
}
|
|
11216
12182
|
|
|
11217
|
-
|
|
11218
|
-
|
|
12183
|
+
#createRemoveButton() {
|
|
12184
|
+
const button = createElement("button", {
|
|
11219
12185
|
type: "button",
|
|
11220
|
-
|
|
12186
|
+
className: "lexxy-attachment-toolbar__remove",
|
|
12187
|
+
ariaLabel: "Remove",
|
|
12188
|
+
tabIndex: -1
|
|
11221
12189
|
});
|
|
11222
|
-
|
|
11223
|
-
this.
|
|
12190
|
+
button.innerHTML = DELETE_ICON;
|
|
12191
|
+
this.#listeners.track(registerEventListener(button, "click", () => this.#removeSelectedNode()));
|
|
12192
|
+
return button
|
|
12193
|
+
}
|
|
11224
12194
|
|
|
11225
|
-
|
|
11226
|
-
|
|
11227
|
-
|
|
12195
|
+
#removeSelectedNode() {
|
|
12196
|
+
if (this.#hasSelectedNode) {
|
|
12197
|
+
this.#editor.update(() => $getNodeByKey(this.#currentNodeKey)?.remove());
|
|
12198
|
+
this.#editor.focus();
|
|
12199
|
+
}
|
|
12200
|
+
}
|
|
11228
12201
|
|
|
11229
|
-
|
|
12202
|
+
get #hasSelectedNode() {
|
|
12203
|
+
return this.#currentNodeKey !== null
|
|
11230
12204
|
}
|
|
11231
12205
|
|
|
11232
|
-
#
|
|
11233
|
-
|
|
11234
|
-
|
|
11235
|
-
|
|
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
|
+
}
|
|
11236
12286
|
});
|
|
11237
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
|
+
}
|
|
11238
12357
|
}
|
|
11239
12358
|
|
|
11240
12359
|
class TableController {
|
|
@@ -11639,7 +12758,7 @@ class TableTools extends HTMLElement {
|
|
|
11639
12758
|
#listeners = new ListenerBin()
|
|
11640
12759
|
|
|
11641
12760
|
connectedCallback() {
|
|
11642
|
-
this.tableController = new TableController(this
|
|
12761
|
+
this.tableController = new TableController(this.editorElement);
|
|
11643
12762
|
this.classList.add("lexxy-floating-controls");
|
|
11644
12763
|
this.role = "toolbar";
|
|
11645
12764
|
|
|
@@ -11660,16 +12779,34 @@ class TableTools extends HTMLElement {
|
|
|
11660
12779
|
this.tableController = null;
|
|
11661
12780
|
}
|
|
11662
12781
|
|
|
11663
|
-
get
|
|
11664
|
-
return this
|
|
12782
|
+
get editorElement() {
|
|
12783
|
+
return this.closest("lexxy-editor")
|
|
11665
12784
|
}
|
|
11666
12785
|
|
|
11667
|
-
get
|
|
11668
|
-
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)
|
|
11669
12806
|
}
|
|
11670
12807
|
|
|
11671
12808
|
get #tableToolsButtons() {
|
|
11672
|
-
return Array.from(this.querySelectorAll("button
|
|
12809
|
+
return Array.from(this.querySelectorAll("button")).filter(button => !button.closest("[data-dropdown-panel]"))
|
|
11673
12810
|
}
|
|
11674
12811
|
|
|
11675
12812
|
#setUpButtons() {
|
|
@@ -11688,23 +12825,37 @@ class TableTools extends HTMLElement {
|
|
|
11688
12825
|
const plusButton = this.#createButton(`Add ${childType}`, { action: "insert", childType, direction: "after" }, "+");
|
|
11689
12826
|
const minusButton = this.#createButton(`Remove ${childType}`, { action: "delete", childType }, "−");
|
|
11690
12827
|
|
|
11691
|
-
const dropdown = createElement("details", { className: "lexxy-table-control__more-menu" });
|
|
11692
|
-
dropdown.setAttribute("name", "lexxy-dropdown");
|
|
11693
|
-
dropdown.tabIndex = -1;
|
|
11694
|
-
|
|
11695
|
-
const count = createElement("summary", {}, `_ ${childType}s`);
|
|
11696
|
-
setCountProperty(count);
|
|
11697
|
-
dropdown.appendChild(count);
|
|
11698
|
-
|
|
11699
|
-
dropdown.appendChild(moreMenu);
|
|
11700
|
-
|
|
11701
12828
|
container.appendChild(minusButton);
|
|
11702
|
-
container.appendChild(
|
|
12829
|
+
container.appendChild(this.#createMoreMenu(childType, setCountProperty, moreMenu));
|
|
11703
12830
|
container.appendChild(plusButton);
|
|
11704
12831
|
|
|
11705
12832
|
return container
|
|
11706
12833
|
}
|
|
11707
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
|
+
|
|
11708
12859
|
#createRowButtonsContainer() {
|
|
11709
12860
|
return this.#createButtonsContainer(
|
|
11710
12861
|
"row",
|
|
@@ -11723,15 +12874,18 @@ class TableTools extends HTMLElement {
|
|
|
11723
12874
|
|
|
11724
12875
|
#createMoreMenuSection(childType) {
|
|
11725
12876
|
const section = createElement("div", { className: "lexxy-floating-controls__group lexxy-table-control__more-menu-details" });
|
|
11726
|
-
const addBeforeButton = this.#createButton(`Add ${childType} before`, { action: "insert", childType, direction: "before" });
|
|
11727
|
-
const addAfterButton = this.#createButton(`Add ${childType} after`, { action: "insert", childType, direction: "after" });
|
|
11728
|
-
const toggleStyleButton = this.#createButton(`Toggle ${childType} style`, { action: "toggle", childType });
|
|
11729
|
-
const deleteButton = this.#createButton(`Remove ${childType}`, { action: "delete", childType });
|
|
11730
12877
|
|
|
11731
|
-
|
|
11732
|
-
|
|
11733
|
-
|
|
11734
|
-
|
|
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
|
+
});
|
|
11735
12889
|
|
|
11736
12890
|
return section
|
|
11737
12891
|
}
|
|
@@ -11772,7 +12926,7 @@ class TableTools extends HTMLElement {
|
|
|
11772
12926
|
}
|
|
11773
12927
|
|
|
11774
12928
|
#registerKeyboardShortcuts() {
|
|
11775
|
-
this.#listeners.track(this
|
|
12929
|
+
this.#listeners.track(this.editor.registerCommand(KEY_DOWN_COMMAND, this.#focusToolbarOnAltF10, COMMAND_PRIORITY_HIGH));
|
|
11776
12930
|
}
|
|
11777
12931
|
|
|
11778
12932
|
#focusToolbarOnAltF10 = (event) => {
|
|
@@ -11803,9 +12957,9 @@ class TableTools extends HTMLElement {
|
|
|
11803
12957
|
const cell = this.tableController.currentCell;
|
|
11804
12958
|
if (!cell) return
|
|
11805
12959
|
|
|
11806
|
-
this
|
|
12960
|
+
this.editor.update(() => {
|
|
11807
12961
|
cell.select();
|
|
11808
|
-
this
|
|
12962
|
+
this.editor.focus();
|
|
11809
12963
|
});
|
|
11810
12964
|
|
|
11811
12965
|
this.#update();
|
|
@@ -11842,7 +12996,7 @@ class TableTools extends HTMLElement {
|
|
|
11842
12996
|
if (!cellsToHighlight) return
|
|
11843
12997
|
|
|
11844
12998
|
cellsToHighlight.forEach(cell => {
|
|
11845
|
-
const cellElement = this
|
|
12999
|
+
const cellElement = this.editor.getElementByKey(cell.getKey());
|
|
11846
13000
|
if (!cellElement) return
|
|
11847
13001
|
|
|
11848
13002
|
cellElement.classList.toggle(theme.tableCellHighlight, true);
|
|
@@ -11851,8 +13005,8 @@ class TableTools extends HTMLElement {
|
|
|
11851
13005
|
}
|
|
11852
13006
|
|
|
11853
13007
|
#monitorForTableSelection() {
|
|
11854
|
-
this.#listeners.track(this
|
|
11855
|
-
const tableNode = this
|
|
13008
|
+
this.#listeners.track(this.editor.registerUpdateListener(() => {
|
|
13009
|
+
const tableNode = this.editor.getRootElement() && this.tableController.updateSelectedTable();
|
|
11856
13010
|
|
|
11857
13011
|
if (tableNode) {
|
|
11858
13012
|
this.#show();
|
|
@@ -11863,8 +13017,12 @@ class TableTools extends HTMLElement {
|
|
|
11863
13017
|
}
|
|
11864
13018
|
|
|
11865
13019
|
#executeTableCommand(command) {
|
|
13020
|
+
const fromMoreMenu = this.#hasOpenMoreMenu;
|
|
13021
|
+
|
|
11866
13022
|
this.tableController.executeTableCommand(command);
|
|
11867
13023
|
this.#update();
|
|
13024
|
+
|
|
13025
|
+
if (fromMoreMenu) this.editor.focus();
|
|
11868
13026
|
}
|
|
11869
13027
|
|
|
11870
13028
|
#show() {
|
|
@@ -11888,18 +13046,18 @@ class TableTools extends HTMLElement {
|
|
|
11888
13046
|
}
|
|
11889
13047
|
|
|
11890
13048
|
#closeMoreMenu() {
|
|
11891
|
-
this.
|
|
13049
|
+
this.closeDropdowns();
|
|
11892
13050
|
}
|
|
11893
13051
|
|
|
11894
13052
|
#updateButtonsPosition() {
|
|
11895
13053
|
const tableNode = this.tableController.currentTableNode;
|
|
11896
13054
|
if (!tableNode) return
|
|
11897
13055
|
|
|
11898
|
-
const tableElement = this
|
|
13056
|
+
const tableElement = this.editor.getElementByKey(tableNode.getKey());
|
|
11899
13057
|
if (!tableElement) return
|
|
11900
13058
|
|
|
11901
13059
|
const tableRect = tableElement.getBoundingClientRect();
|
|
11902
|
-
const editorRect = this
|
|
13060
|
+
const editorRect = this.editorElement.getBoundingClientRect();
|
|
11903
13061
|
|
|
11904
13062
|
const relativeTop = tableRect.top - editorRect.top;
|
|
11905
13063
|
const relativeCenter = (tableRect.left + tableRect.right) / 2 - editorRect.left;
|
|
@@ -11911,7 +13069,7 @@ class TableTools extends HTMLElement {
|
|
|
11911
13069
|
const tableNode = this.tableController.currentTableNode;
|
|
11912
13070
|
if (!tableNode) return
|
|
11913
13071
|
|
|
11914
|
-
const tableElement = $getElementForTableNode(this
|
|
13072
|
+
const tableElement = $getElementForTableNode(this.editor, tableNode);
|
|
11915
13073
|
if (!tableElement) return
|
|
11916
13074
|
|
|
11917
13075
|
const rowCount = tableElement.rows;
|
|
@@ -11925,18 +13083,18 @@ class TableTools extends HTMLElement {
|
|
|
11925
13083
|
const cell = this.tableController.currentCell;
|
|
11926
13084
|
if (!cell) return
|
|
11927
13085
|
|
|
11928
|
-
const cellElement = this
|
|
11929
|
-
if (!cellElement) return
|
|
13086
|
+
const cellElement = this.editor.getElementByKey(cell.getKey());
|
|
13087
|
+
if (!cellElement?.contains(document.getSelection()?.anchorNode)) return
|
|
11930
13088
|
|
|
11931
13089
|
cellElement.classList.add(theme.tableCellFocus);
|
|
11932
13090
|
}
|
|
11933
13091
|
|
|
11934
13092
|
#clearCellStyles() {
|
|
11935
|
-
this
|
|
13093
|
+
this.editorElement.querySelectorAll(`.${theme.tableCellFocus}`)?.forEach(cell => {
|
|
11936
13094
|
cell.classList.remove(theme.tableCellFocus);
|
|
11937
13095
|
});
|
|
11938
13096
|
|
|
11939
|
-
this
|
|
13097
|
+
this.editorElement.querySelectorAll(`.${theme.tableCellHighlight}`)?.forEach(cell => {
|
|
11940
13098
|
cell.classList.remove(theme.tableCellHighlight);
|
|
11941
13099
|
cell.removeAttribute("data-action");
|
|
11942
13100
|
cell.removeAttribute("data-child-type");
|
|
@@ -11968,7 +13126,8 @@ function defineElements() {
|
|
|
11968
13126
|
// Prompt must be registered AFTER Editor
|
|
11969
13127
|
"lexxy-prompt": LexicalPromptElement,
|
|
11970
13128
|
"lexxy-code-language-picker": CodeLanguagePicker,
|
|
11971
|
-
"lexxy-
|
|
13129
|
+
"lexxy-live-region": LiveRegion,
|
|
13130
|
+
"lexxy-attachment-toolbar": AttachmentToolbar,
|
|
11972
13131
|
"lexxy-table-tools": TableTools
|
|
11973
13132
|
};
|
|
11974
13133
|
|