@37signals/lexxy 0.9.9-beta.preview6 → 0.9.11-beta
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 +220 -28
- package/dist/lexxy_helpers.esm.js +3 -63
- package/package.json +1 -1
package/dist/lexxy.esm.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { isActiveAndVisible, extractPlainTextFromHtml, createElement, createAttachmentFigure, isPreviewableImage, dispatch, parseHtml, addBlockSpacing, generateDomId } from './lexxy_helpers.esm.js';
|
|
2
1
|
export { highlightCode } from './lexxy_helpers.esm.js';
|
|
3
2
|
import DOMPurify from 'dompurify';
|
|
4
3
|
import { getStyleObjectFromCSS, getCSSFromStyleObject, $ensureForwardRangeSelection, $isAtNodeEnd, $getSelectionStyleValueForProperty, $patchStyleText, $setBlocksType, $forEachSelectedTextNode } from '@lexical/selection';
|
|
5
|
-
import { SKIP_DOM_SELECTION_TAG, CAN_UNDO_COMMAND, COMMAND_PRIORITY_LOW, CAN_REDO_COMMAND, $getSelection, $isRangeSelection, DecoratorNode, $createTextNode, $isElementNode, $isRootOrShadowRoot, $isRootNode, $createNodeSelection, $isDecoratorNode, $isLineBreakNode, $isTextNode, $isParagraphNode, $splitNode, $getSiblingCaret, LineBreakNode, $createParagraphNode, TextNode, createCommand, defineExtension, COMMAND_PRIORITY_EDITOR, $getEditor, $getNodeByKey, HISTORY_MERGE_TAG, SKIP_SCROLL_INTO_VIEW_TAG, $cloneWithProperties, $getNearestRootOrShadowRoot, $createRangeSelection, $setSelection, createState, COMMAND_PRIORITY_NORMAL, $getState, $setState, $hasUpdateTag, PASTE_TAG, FORMAT_TEXT_COMMAND, UNDO_COMMAND, REDO_COMMAND,
|
|
4
|
+
import { SKIP_DOM_SELECTION_TAG, CAN_UNDO_COMMAND, COMMAND_PRIORITY_LOW, CAN_REDO_COMMAND, $getSelection, $isRangeSelection, DecoratorNode, $createTextNode, $isElementNode, $isRootOrShadowRoot, $isRootNode, $createNodeSelection, $isDecoratorNode, $isLineBreakNode, $isTextNode, $isParagraphNode, $splitNode, $getSiblingCaret, LineBreakNode, $createParagraphNode, $getCommonAncestor, $findMatchingParent, TextNode, createCommand, defineExtension, COMMAND_PRIORITY_EDITOR, $getEditor, $getNodeByKey, HISTORY_MERGE_TAG, SKIP_SCROLL_INTO_VIEW_TAG, $cloneWithProperties, $getNearestRootOrShadowRoot, $createRangeSelection, $setSelection, createState, COMMAND_PRIORITY_NORMAL, $getState, $setState, $hasUpdateTag, PASTE_TAG, FORMAT_TEXT_COMMAND, UNDO_COMMAND, REDO_COMMAND, KEY_ARROW_RIGHT_COMMAND, KEY_TAB_COMMAND, OUTDENT_CONTENT_COMMAND, INDENT_CONTENT_COMMAND, $isNodeSelection, $getRoot, KEY_ARROW_LEFT_COMMAND, KEY_ARROW_UP_COMMAND, KEY_ARROW_DOWN_COMMAND, DELETE_CHARACTER_COMMAND, SELECTION_CHANGE_COMMAND, CLICK_COMMAND, isDOMNode, $getNearestNodeFromDOMNode, $addUpdateTag, ElementNode, $getChildCaretAtIndex, $createLineBreakNode, PASTE_COMMAND, SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, ParagraphNode, RootNode, COMMAND_PRIORITY_HIGH, DRAGSTART_COMMAND, DROP_COMMAND, INSERT_PARAGRAPH_COMMAND, mergeRegister as mergeRegister$1, CLEAR_HISTORY_COMMAND, $onUpdate, KEY_ENTER_COMMAND, COMMAND_PRIORITY_CRITICAL, KEY_SPACE_COMMAND, KEY_BACKSPACE_COMMAND, KEY_DOWN_COMMAND } from 'lexical';
|
|
6
5
|
import { buildEditorFromExtensions } from '@lexical/extension';
|
|
7
6
|
import { ListNode, ListItemNode, $getListDepth, INSERT_UNORDERED_LIST_COMMAND, INSERT_ORDERED_LIST_COMMAND, $isListItemNode, $isListNode, registerList } from '@lexical/list';
|
|
8
7
|
import { LinkNode, $createAutoLinkNode, $toggleLink, $createLinkNode, $isLinkNode, AutoLinkNode } from '@lexical/link';
|
|
@@ -126,6 +125,64 @@ class ListenerBin {
|
|
|
126
125
|
}
|
|
127
126
|
}
|
|
128
127
|
|
|
128
|
+
function createElement(name, properties, content = "") {
|
|
129
|
+
const element = document.createElement(name);
|
|
130
|
+
for (const [ key, value ] of Object.entries(properties || {})) {
|
|
131
|
+
if (key in element) {
|
|
132
|
+
element[key] = value;
|
|
133
|
+
} else if (value !== null && value !== undefined) {
|
|
134
|
+
element.setAttribute(key, value);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (content) {
|
|
138
|
+
element.innerHTML = content;
|
|
139
|
+
}
|
|
140
|
+
return element
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function parseHtml(html) {
|
|
144
|
+
const parser = new DOMParser();
|
|
145
|
+
return parser.parseFromString(html, "text/html")
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function createAttachmentFigure(contentType, isPreviewable, fileName) {
|
|
149
|
+
const extension = fileName ? fileName.split(".").pop().toLowerCase() : "unknown";
|
|
150
|
+
return createElement("figure", {
|
|
151
|
+
className: `attachment attachment--${isPreviewable ? "preview" : "file"} attachment--${extension}`,
|
|
152
|
+
"data-content-type": contentType
|
|
153
|
+
})
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function isPreviewableImage(contentType) {
|
|
157
|
+
return contentType.startsWith("image/") && !contentType.includes("svg")
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function dispatch(element, eventName, detail = null, cancelable = false) {
|
|
161
|
+
return element.dispatchEvent(new CustomEvent(eventName, { bubbles: true, detail, cancelable }))
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function addBlockSpacing(doc) {
|
|
165
|
+
const blocks = doc.querySelectorAll("body > :not(h1, h2, h3, h4, h5, h6) + *");
|
|
166
|
+
for (const block of blocks) {
|
|
167
|
+
const spacer = doc.createElement("p");
|
|
168
|
+
spacer.appendChild(doc.createElement("br"));
|
|
169
|
+
block.before(spacer);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function generateDomId(prefix) {
|
|
174
|
+
const randomPart = Math.random().toString(36).slice(2, 10);
|
|
175
|
+
return `${prefix}-${randomPart}`
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function extractPlainTextFromHtml(innerHtml = "") {
|
|
179
|
+
return parseHtml(innerHtml).body.textContent.trim()
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function isActiveAndVisible(element) {
|
|
183
|
+
return element && !element.disabled && element.checkVisibility()
|
|
184
|
+
}
|
|
185
|
+
|
|
129
186
|
function handleRollingTabIndex(elements, event) {
|
|
130
187
|
const previousActiveElement = document.activeElement;
|
|
131
188
|
|
|
@@ -553,15 +610,23 @@ class LexicalToolbarElement extends HTMLElement {
|
|
|
553
610
|
#setButtonPressed(name, isPressed) {
|
|
554
611
|
const button = this.querySelector(`[name="${name}"]`);
|
|
555
612
|
if (button) {
|
|
556
|
-
|
|
613
|
+
const next = isPressed.toString();
|
|
614
|
+
if (button.getAttribute("aria-pressed") !== next) {
|
|
615
|
+
button.setAttribute("aria-pressed", next);
|
|
616
|
+
}
|
|
557
617
|
}
|
|
558
618
|
}
|
|
559
619
|
|
|
560
620
|
#setButtonDisabled(name, isDisabled) {
|
|
561
621
|
const button = this.querySelector(`[name="${name}"]`);
|
|
562
622
|
if (button) {
|
|
563
|
-
button.disabled
|
|
564
|
-
|
|
623
|
+
if (button.disabled !== isDisabled) {
|
|
624
|
+
button.disabled = isDisabled;
|
|
625
|
+
}
|
|
626
|
+
const next = isDisabled.toString();
|
|
627
|
+
if (button.getAttribute("aria-disabled") !== next) {
|
|
628
|
+
button.setAttribute("aria-disabled", next);
|
|
629
|
+
}
|
|
565
630
|
}
|
|
566
631
|
}
|
|
567
632
|
|
|
@@ -1078,6 +1143,18 @@ class LexxyExtension {
|
|
|
1078
1143
|
initializeToolbar(_lexxyToolbar) {
|
|
1079
1144
|
|
|
1080
1145
|
}
|
|
1146
|
+
|
|
1147
|
+
dispose() {
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
function $containsRangeSelection(node, selection = $getSelection()) {
|
|
1152
|
+
if ($isRangeSelection(selection)) {
|
|
1153
|
+
const { commonAncestor } = $getCommonAncestor(selection.focus.getNode(), selection.anchor.getNode());
|
|
1154
|
+
return $findMatchingParent(commonAncestor, parent => parent.is(node))
|
|
1155
|
+
} else {
|
|
1156
|
+
return false
|
|
1157
|
+
}
|
|
1081
1158
|
}
|
|
1082
1159
|
|
|
1083
1160
|
function $createNodeSelectionWith(...nodes) {
|
|
@@ -2813,17 +2890,12 @@ class CommandDispatcher {
|
|
|
2813
2890
|
this.editor = editorElement.editor;
|
|
2814
2891
|
this.selection = editorElement.selection;
|
|
2815
2892
|
this.contents = editorElement.contents;
|
|
2816
|
-
this.clipboard = editorElement.clipboard;
|
|
2817
2893
|
|
|
2818
2894
|
this.#registerCommands();
|
|
2819
2895
|
this.#registerKeyboardCommands();
|
|
2820
2896
|
this.#registerDragAndDropHandlers();
|
|
2821
2897
|
}
|
|
2822
2898
|
|
|
2823
|
-
dispatchPaste(event) {
|
|
2824
|
-
return this.clipboard.paste(event)
|
|
2825
|
-
}
|
|
2826
|
-
|
|
2827
2899
|
dispatchBold() {
|
|
2828
2900
|
this.editor.dispatchCommand(FORMAT_TEXT_COMMAND, "bold");
|
|
2829
2901
|
}
|
|
@@ -3050,8 +3122,6 @@ class CommandDispatcher {
|
|
|
3050
3122
|
const methodName = `dispatch${capitalize(command)}`;
|
|
3051
3123
|
this.#registerCommandHandler(command, 0, this[methodName].bind(this));
|
|
3052
3124
|
}
|
|
3053
|
-
|
|
3054
|
-
this.#registerCommandHandler(PASTE_COMMAND, COMMAND_PRIORITY_LOW, this.dispatchPaste.bind(this));
|
|
3055
3125
|
}
|
|
3056
3126
|
|
|
3057
3127
|
#registerCommandHandler(command, priority, handler) {
|
|
@@ -3412,6 +3482,11 @@ class Selection {
|
|
|
3412
3482
|
return $isActionTextAttachmentNode(firstNode) && firstNode.isPreviewableImage
|
|
3413
3483
|
}
|
|
3414
3484
|
|
|
3485
|
+
get isAtNodeStart() {
|
|
3486
|
+
const { anchorNode, offset } = this.#getCollapsedSelectionData();
|
|
3487
|
+
return anchorNode && offset === 0
|
|
3488
|
+
}
|
|
3489
|
+
|
|
3415
3490
|
get nodeAfterCursor() {
|
|
3416
3491
|
const { anchorNode, offset } = this.#getCollapsedSelectionData();
|
|
3417
3492
|
if (!anchorNode) return null
|
|
@@ -4586,8 +4661,9 @@ class Uploader {
|
|
|
4586
4661
|
return new UploaderKlass(editorElement, files)
|
|
4587
4662
|
}
|
|
4588
4663
|
|
|
4589
|
-
constructor(editorElement, files) {
|
|
4664
|
+
constructor(editorElement, files, options = {}) {
|
|
4590
4665
|
this.#files = files;
|
|
4666
|
+
this.options = options;
|
|
4591
4667
|
|
|
4592
4668
|
this.editorElement = editorElement;
|
|
4593
4669
|
this.contents = editorElement.contents;
|
|
@@ -4629,10 +4705,10 @@ class GalleryUploader extends Uploader {
|
|
|
4629
4705
|
#gallery
|
|
4630
4706
|
|
|
4631
4707
|
static handle(editorElement, files) {
|
|
4632
|
-
return this
|
|
4708
|
+
return this.isMultipleImageUpload(files) || this.gallerySelection(editorElement.selection)
|
|
4633
4709
|
}
|
|
4634
4710
|
|
|
4635
|
-
static
|
|
4711
|
+
static isMultipleImageUpload(files) {
|
|
4636
4712
|
let imageFileCount = 0;
|
|
4637
4713
|
for (const file of files) {
|
|
4638
4714
|
if (isPreviewableImage(file.type)) imageFileCount++;
|
|
@@ -4641,11 +4717,12 @@ class GalleryUploader extends Uploader {
|
|
|
4641
4717
|
return false
|
|
4642
4718
|
}
|
|
4643
4719
|
|
|
4644
|
-
static
|
|
4645
|
-
|
|
4720
|
+
static gallerySelection(selection) {
|
|
4721
|
+
return selection.isOnPreviewableImage || this.selectionIsAfterGalleryEdge(selection)
|
|
4722
|
+
}
|
|
4646
4723
|
|
|
4647
|
-
|
|
4648
|
-
return
|
|
4724
|
+
static selectionIsAfterGalleryEdge(selection) {
|
|
4725
|
+
return selection.isAtNodeStart && ImageGalleryNode.canCollapseWith(selection.nodeBeforeCursor)
|
|
4649
4726
|
}
|
|
4650
4727
|
|
|
4651
4728
|
$insertUploadNodes() {
|
|
@@ -4657,18 +4734,26 @@ class GalleryUploader extends Uploader {
|
|
|
4657
4734
|
#findOrCreateGallery() {
|
|
4658
4735
|
if (this.selection.isOnPreviewableImage) {
|
|
4659
4736
|
this.#gallery = $findOrCreateGalleryForImage(this.#selectedNode);
|
|
4737
|
+
} else if (this.#selectionIsAfterGalleryEdge) {
|
|
4738
|
+
this.#gallery = $findOrCreateGalleryForImage(this.selection.nodeBeforeCursor);
|
|
4660
4739
|
} else {
|
|
4661
4740
|
this.#gallery = $createImageGalleryNode();
|
|
4662
4741
|
this.contents.insertAtCursor(this.#gallery);
|
|
4663
4742
|
}
|
|
4664
4743
|
}
|
|
4665
4744
|
|
|
4745
|
+
get #selectionIsAfterGalleryEdge() {
|
|
4746
|
+
return this.constructor.selectionIsAfterGalleryEdge(this.selection)
|
|
4747
|
+
}
|
|
4748
|
+
|
|
4666
4749
|
get #selectedNode() {
|
|
4667
4750
|
const { node } = this.selection.selectedNodeWithOffset();
|
|
4668
4751
|
return node
|
|
4669
4752
|
}
|
|
4670
4753
|
|
|
4671
4754
|
get #galleryInsertPosition() {
|
|
4755
|
+
if (this.#selectionIsAfterGalleryEdge) return this.#gallery.getChildrenSize()
|
|
4756
|
+
|
|
4672
4757
|
const anchor = $getSelection()?.anchor;
|
|
4673
4758
|
const galleryHasElementSelection = anchor?.getNode().is(this.#gallery);
|
|
4674
4759
|
if (galleryHasElementSelection) return anchor.offset
|
|
@@ -5396,10 +5481,22 @@ class Contents {
|
|
|
5396
5481
|
}
|
|
5397
5482
|
|
|
5398
5483
|
class Clipboard {
|
|
5484
|
+
#listeners = new ListenerBin()
|
|
5485
|
+
|
|
5399
5486
|
constructor(editorElement) {
|
|
5400
5487
|
this.editorElement = editorElement;
|
|
5401
5488
|
this.editor = editorElement.editor;
|
|
5402
5489
|
this.contents = editorElement.contents;
|
|
5490
|
+
|
|
5491
|
+
this.#registerPasteCommands();
|
|
5492
|
+
}
|
|
5493
|
+
|
|
5494
|
+
dispose() {
|
|
5495
|
+
this.editorElement = null;
|
|
5496
|
+
this.editor = null;
|
|
5497
|
+
this.contents = null;
|
|
5498
|
+
|
|
5499
|
+
this.#listeners.dispose();
|
|
5403
5500
|
}
|
|
5404
5501
|
|
|
5405
5502
|
paste(event) {
|
|
@@ -5424,6 +5521,25 @@ class Clipboard {
|
|
|
5424
5521
|
return handled
|
|
5425
5522
|
}
|
|
5426
5523
|
|
|
5524
|
+
#registerPasteCommands() {
|
|
5525
|
+
this.#listeners.track(
|
|
5526
|
+
this.editor.registerCommand(PASTE_COMMAND, this.paste.bind(this), COMMAND_PRIORITY_NORMAL),
|
|
5527
|
+
this.editor.registerCommand(
|
|
5528
|
+
SELECTION_INSERT_CLIPBOARD_NODES_COMMAND,
|
|
5529
|
+
(payload) => this.#handleParsedClipboardNodes(payload),
|
|
5530
|
+
COMMAND_PRIORITY_NORMAL
|
|
5531
|
+
)
|
|
5532
|
+
);
|
|
5533
|
+
}
|
|
5534
|
+
|
|
5535
|
+
#handleParsedClipboardNodes({ nodes, selection }) {
|
|
5536
|
+
const url = $bareUrlFromSingleLink(nodes);
|
|
5537
|
+
if (!url) return false
|
|
5538
|
+
|
|
5539
|
+
this.#insertSingleLinkAt(selection, url);
|
|
5540
|
+
return true
|
|
5541
|
+
}
|
|
5542
|
+
|
|
5427
5543
|
#isPlainTextOrURLPasted(clipboardData) {
|
|
5428
5544
|
return this.#isOnlyPlainTextPasted(clipboardData) || this.#isOnlyURLPasted(clipboardData)
|
|
5429
5545
|
}
|
|
@@ -5486,6 +5602,24 @@ class Clipboard {
|
|
|
5486
5602
|
});
|
|
5487
5603
|
}
|
|
5488
5604
|
|
|
5605
|
+
#insertSingleLinkAt(selection, url) {
|
|
5606
|
+
if (!$isRangeSelection(selection)) return
|
|
5607
|
+
|
|
5608
|
+
if (!selection.isCollapsed()) {
|
|
5609
|
+
$toggleLink(null);
|
|
5610
|
+
$toggleLink(url);
|
|
5611
|
+
return
|
|
5612
|
+
}
|
|
5613
|
+
|
|
5614
|
+
const linkNode = $createLinkNode(url).append($createTextNode(url));
|
|
5615
|
+
selection.insertNodes([ linkNode ]);
|
|
5616
|
+
|
|
5617
|
+
// Defer the lexxy:insert-link event until after the active update commits;
|
|
5618
|
+
// listeners may run editor mutations of their own.
|
|
5619
|
+
const nodeKey = linkNode.getKey();
|
|
5620
|
+
Promise.resolve().then(() => this.#dispatchLinkInsertEvent(nodeKey, { url }));
|
|
5621
|
+
}
|
|
5622
|
+
|
|
5489
5623
|
#dispatchLinkInsertEvent(nodeKey, payload) {
|
|
5490
5624
|
const linkManipulationMethods = {
|
|
5491
5625
|
replaceLinkWith: (html, options) => this.contents.replaceNodeWithHTML(nodeKey, html, options),
|
|
@@ -5577,6 +5711,28 @@ class Clipboard {
|
|
|
5577
5711
|
}
|
|
5578
5712
|
}
|
|
5579
5713
|
|
|
5714
|
+
function $bareUrlFromSingleLink(nodes) {
|
|
5715
|
+
if (nodes.length !== 1) return null
|
|
5716
|
+
|
|
5717
|
+
const node = nodes[0];
|
|
5718
|
+
if ($isLinkNode(node)) return $bareUrlFromLink(node)
|
|
5719
|
+
|
|
5720
|
+
if ($isParagraphNode(node)) {
|
|
5721
|
+
const children = node.getChildren();
|
|
5722
|
+
if (children.length === 1 && $isLinkNode(children[0])) {
|
|
5723
|
+
return $bareUrlFromLink(children[0])
|
|
5724
|
+
}
|
|
5725
|
+
}
|
|
5726
|
+
|
|
5727
|
+
return null
|
|
5728
|
+
}
|
|
5729
|
+
|
|
5730
|
+
function $bareUrlFromLink(linkNode) {
|
|
5731
|
+
const url = linkNode.getURL();
|
|
5732
|
+
if (!url) return null
|
|
5733
|
+
return linkNode.getTextContent() === url ? url : null
|
|
5734
|
+
}
|
|
5735
|
+
|
|
5580
5736
|
class Extensions {
|
|
5581
5737
|
|
|
5582
5738
|
constructor(lexxyElement) {
|
|
@@ -5597,6 +5753,12 @@ class Extensions {
|
|
|
5597
5753
|
this.#addExtensionToolbarButtons(toolbar);
|
|
5598
5754
|
}
|
|
5599
5755
|
|
|
5756
|
+
dispose() {
|
|
5757
|
+
while (this.enabledExtensions.length) {
|
|
5758
|
+
this.enabledExtensions.pop().dispose();
|
|
5759
|
+
}
|
|
5760
|
+
}
|
|
5761
|
+
|
|
5600
5762
|
#clearPreviousExtensionToolbarButtons(toolbar) {
|
|
5601
5763
|
toolbar.querySelectorAll("[data-lexxy-extension]").forEach(el => el.remove());
|
|
5602
5764
|
}
|
|
@@ -6002,9 +6164,15 @@ class TablesExtension extends LexxyExtension {
|
|
|
6002
6164
|
setScrollableTablesActive(editor, true);
|
|
6003
6165
|
|
|
6004
6166
|
return mergeRegister(
|
|
6005
|
-
// Register Lexical table plugins
|
|
6006
6167
|
registerTablePlugin(editor),
|
|
6007
|
-
|
|
6168
|
+
|
|
6169
|
+
// Lexxy registers extensions before setRootElement(), but table
|
|
6170
|
+
// drag-selection needs a root before wiring its pointer handlers.
|
|
6171
|
+
editor.registerRootListener((rootElement) => {
|
|
6172
|
+
if (rootElement) {
|
|
6173
|
+
return registerTableSelectionObserver(editor, true)
|
|
6174
|
+
}
|
|
6175
|
+
}),
|
|
6008
6176
|
|
|
6009
6177
|
// Bug fix: Prevent hardcoded background color (Lexical #8089)
|
|
6010
6178
|
editor.registerNodeTransform(TableCellNode, (node) => {
|
|
@@ -6712,7 +6880,8 @@ class FormatEscapeExtension extends LexxyExtension {
|
|
|
6712
6880
|
KEY_ARROW_DOWN_COMMAND,
|
|
6713
6881
|
(event) => $handleArrowDownInCodeBlock(event),
|
|
6714
6882
|
COMMAND_PRIORITY_NORMAL
|
|
6715
|
-
)
|
|
6883
|
+
),
|
|
6884
|
+
editor.registerNodeTransform(QuoteNode, $ensureQuoteHasParagraphChild)
|
|
6716
6885
|
)
|
|
6717
6886
|
}
|
|
6718
6887
|
})
|
|
@@ -6765,6 +6934,13 @@ function $handleArrowDownInCodeBlock(event) {
|
|
|
6765
6934
|
return false
|
|
6766
6935
|
}
|
|
6767
6936
|
|
|
6937
|
+
function $ensureQuoteHasParagraphChild(quoteNode) {
|
|
6938
|
+
if (!quoteNode.isEmpty()) return
|
|
6939
|
+
|
|
6940
|
+
quoteNode.append($createParagraphNode());
|
|
6941
|
+
if ($containsRangeSelection(quoteNode)) quoteNode.getFirstChild().select();
|
|
6942
|
+
}
|
|
6943
|
+
|
|
6768
6944
|
class LinkOpenerExtension extends LexxyExtension {
|
|
6769
6945
|
get enabled() {
|
|
6770
6946
|
return this.editorElement.supportsRichText
|
|
@@ -6852,6 +7028,7 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
6852
7028
|
this.id ||= generateDomId("lexxy-editor");
|
|
6853
7029
|
this.config = new EditorConfiguration(this);
|
|
6854
7030
|
this.extensions = new Extensions(this);
|
|
7031
|
+
this.#disposables.push(this.extensions);
|
|
6855
7032
|
|
|
6856
7033
|
this.editor = this.#createEditor();
|
|
6857
7034
|
this.#disposables.push(this.editor);
|
|
@@ -6864,6 +7041,8 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
6864
7041
|
this.#disposables.push(this.selection);
|
|
6865
7042
|
|
|
6866
7043
|
this.clipboard = new Clipboard(this);
|
|
7044
|
+
this.#disposables.push(this.clipboard);
|
|
7045
|
+
|
|
6867
7046
|
this.adapter = new BrowserAdapter();
|
|
6868
7047
|
|
|
6869
7048
|
const commandDispatcher = CommandDispatcher.configureFor(this);
|
|
@@ -7828,7 +8007,10 @@ class HighlightDropdown extends ToolbarDropdown {
|
|
|
7828
8007
|
|
|
7829
8008
|
this.#colorButtons.forEach(button => {
|
|
7830
8009
|
const matchesSelection = button.dataset.value === textColor || button.dataset.value === backgroundColor;
|
|
7831
|
-
|
|
8010
|
+
const next = matchesSelection.toString();
|
|
8011
|
+
if (button.getAttribute("aria-pressed") !== next) {
|
|
8012
|
+
button.setAttribute("aria-pressed", next);
|
|
8013
|
+
}
|
|
7832
8014
|
});
|
|
7833
8015
|
|
|
7834
8016
|
const hasHighlight = textColor !== NO_STYLE || backgroundColor !== NO_STYLE;
|
|
@@ -8236,7 +8418,7 @@ class LexicalPromptElement extends HTMLElement {
|
|
|
8236
8418
|
}
|
|
8237
8419
|
|
|
8238
8420
|
#selectOption(listItem) {
|
|
8239
|
-
this.#
|
|
8421
|
+
this.#clearListItemSelection();
|
|
8240
8422
|
listItem.toggleAttribute("aria-selected", true);
|
|
8241
8423
|
listItem.scrollIntoView({ block: "nearest", behavior: "smooth" });
|
|
8242
8424
|
listItem.focus();
|
|
@@ -8246,18 +8428,28 @@ class LexicalPromptElement extends HTMLElement {
|
|
|
8246
8428
|
this.#editorElement.focus();
|
|
8247
8429
|
});
|
|
8248
8430
|
|
|
8249
|
-
this.#
|
|
8250
|
-
this.#
|
|
8251
|
-
this.#
|
|
8431
|
+
this.#setEditorAssociationAttribute("aria-controls", this.popoverElement.id);
|
|
8432
|
+
this.#setEditorAssociationAttribute("aria-activedescendant", listItem.id);
|
|
8433
|
+
this.#setEditorAssociationAttribute("aria-haspopup", "listbox");
|
|
8252
8434
|
}
|
|
8253
8435
|
|
|
8254
|
-
#
|
|
8436
|
+
#clearListItemSelection() {
|
|
8255
8437
|
this.#listItemElements.forEach((item) => { item.toggleAttribute("aria-selected", false); });
|
|
8438
|
+
}
|
|
8439
|
+
|
|
8440
|
+
#clearSelection() {
|
|
8441
|
+
this.#clearListItemSelection();
|
|
8256
8442
|
this.#editorContentElement.removeAttribute("aria-controls");
|
|
8257
8443
|
this.#editorContentElement.removeAttribute("aria-activedescendant");
|
|
8258
8444
|
this.#editorContentElement.removeAttribute("aria-haspopup");
|
|
8259
8445
|
}
|
|
8260
8446
|
|
|
8447
|
+
#setEditorAssociationAttribute(name, value) {
|
|
8448
|
+
if (this.#editorContentElement.getAttribute(name) !== value) {
|
|
8449
|
+
this.#editorContentElement.setAttribute(name, value);
|
|
8450
|
+
}
|
|
8451
|
+
}
|
|
8452
|
+
|
|
8261
8453
|
#positionPopover() {
|
|
8262
8454
|
const { x, y, fontSize } = this.#selection.cursorPosition;
|
|
8263
8455
|
const editorRect = this.#editorElement.getBoundingClientRect();
|
|
@@ -28,64 +28,6 @@ import 'prismjs/components/prism-kotlin';
|
|
|
28
28
|
window.Prism ||= {};
|
|
29
29
|
window.Prism.manual = true;
|
|
30
30
|
|
|
31
|
-
function createElement(name, properties, content = "") {
|
|
32
|
-
const element = document.createElement(name);
|
|
33
|
-
for (const [ key, value ] of Object.entries(properties || {})) {
|
|
34
|
-
if (key in element) {
|
|
35
|
-
element[key] = value;
|
|
36
|
-
} else if (value !== null && value !== undefined) {
|
|
37
|
-
element.setAttribute(key, value);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
if (content) {
|
|
41
|
-
element.innerHTML = content;
|
|
42
|
-
}
|
|
43
|
-
return element
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function parseHtml(html) {
|
|
47
|
-
const parser = new DOMParser();
|
|
48
|
-
return parser.parseFromString(html, "text/html")
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function createAttachmentFigure(contentType, isPreviewable, fileName) {
|
|
52
|
-
const extension = fileName ? fileName.split(".").pop().toLowerCase() : "unknown";
|
|
53
|
-
return createElement("figure", {
|
|
54
|
-
className: `attachment attachment--${isPreviewable ? "preview" : "file"} attachment--${extension}`,
|
|
55
|
-
"data-content-type": contentType
|
|
56
|
-
})
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function isPreviewableImage(contentType) {
|
|
60
|
-
return contentType.startsWith("image/") && !contentType.includes("svg")
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function dispatch(element, eventName, detail = null, cancelable = false) {
|
|
64
|
-
return element.dispatchEvent(new CustomEvent(eventName, { bubbles: true, detail, cancelable }))
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function addBlockSpacing(doc) {
|
|
68
|
-
const blocks = doc.querySelectorAll("body > :not(h1, h2, h3, h4, h5, h6) + *");
|
|
69
|
-
for (const block of blocks) {
|
|
70
|
-
const spacer = doc.createElement("p");
|
|
71
|
-
spacer.appendChild(doc.createElement("br"));
|
|
72
|
-
block.before(spacer);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function generateDomId(prefix) {
|
|
77
|
-
const randomPart = Math.random().toString(36).slice(2, 10);
|
|
78
|
-
return `${prefix}-${randomPart}`
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function extractPlainTextFromHtml(innerHtml = "") {
|
|
82
|
-
return parseHtml(innerHtml).body.textContent.trim()
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function isActiveAndVisible(element) {
|
|
86
|
-
return element && !element.disabled && element.checkVisibility()
|
|
87
|
-
}
|
|
88
|
-
|
|
89
31
|
function highlightCode() {
|
|
90
32
|
const elements = document.querySelectorAll("pre[data-language]");
|
|
91
33
|
|
|
@@ -108,13 +50,11 @@ function highlightElement(preElement) {
|
|
|
108
50
|
code = new DOMParser().parseFromString(code, "text/html").body.textContent || "";
|
|
109
51
|
|
|
110
52
|
const highlightedHtml = Prism.highlight(code, grammar, language);
|
|
111
|
-
|
|
53
|
+
preElement.innerHTML = highlightedHtml;
|
|
112
54
|
|
|
113
55
|
if (highlights.length > 0) {
|
|
114
|
-
applyHighlightRanges(
|
|
56
|
+
applyHighlightRanges(preElement, highlights);
|
|
115
57
|
}
|
|
116
|
-
|
|
117
|
-
preElement.replaceChildren(codeElement);
|
|
118
58
|
}
|
|
119
59
|
|
|
120
60
|
// Walk the DOM tree inside a <pre> element and build a list of
|
|
@@ -220,4 +160,4 @@ function collectTextNodes(root) {
|
|
|
220
160
|
return nodes
|
|
221
161
|
}
|
|
222
162
|
|
|
223
|
-
export {
|
|
163
|
+
export { highlightCode };
|