@37signals/lexxy 0.9.1-beta → 0.9.3-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 +567 -122
- package/package.json +1 -1
package/dist/lexxy.esm.js
CHANGED
|
@@ -10,10 +10,11 @@ import 'prismjs/components/prism-json';
|
|
|
10
10
|
import 'prismjs/components/prism-diff';
|
|
11
11
|
import DOMPurify from 'dompurify';
|
|
12
12
|
import { getStyleObjectFromCSS, getCSSFromStyleObject, $isAtNodeEnd, $getSelectionStyleValueForProperty, $patchStyleText, $setBlocksType } from '@lexical/selection';
|
|
13
|
-
import { SKIP_DOM_SELECTION_TAG, $getSelection, $isRangeSelection, $getNodeByKey, $isTextNode, $createRangeSelection, $setSelection, DecoratorNode, $createTextNode, $createNodeSelection, $isDecoratorNode, $isLineBreakNode, $isElementNode,
|
|
13
|
+
import { SKIP_DOM_SELECTION_TAG, $getSelection, $isRangeSelection, $getNodeByKey, $isTextNode, $createRangeSelection, $setSelection, DecoratorNode, $createTextNode, HISTORY_MERGE_TAG, SKIP_SCROLL_INTO_VIEW_TAG, $createNodeSelection, $isDecoratorNode, $isLineBreakNode, $isElementNode, $createParagraphNode, TextNode, createCommand, createState, defineExtension, COMMAND_PRIORITY_NORMAL, $getState, $setState, $hasUpdateTag, PASTE_TAG, FORMAT_TEXT_COMMAND, UNDO_COMMAND, REDO_COMMAND, PASTE_COMMAND, COMMAND_PRIORITY_LOW, KEY_ARROW_RIGHT_COMMAND, KEY_TAB_COMMAND, OUTDENT_CONTENT_COMMAND, INDENT_CONTENT_COMMAND, $getEditor, $getNearestRootOrShadowRoot, $isNodeSelection, $getRoot, mergeRegister as mergeRegister$1, KEY_ARROW_LEFT_COMMAND, KEY_ARROW_UP_COMMAND, KEY_ARROW_DOWN_COMMAND, DELETE_CHARACTER_COMMAND, SELECTION_CHANGE_COMMAND, CLICK_COMMAND, isDOMNode, $getNearestNodeFromDOMNode, $isRootOrShadowRoot, ElementNode, $splitNode, $isParagraphNode, $createLineBreakNode, $isRootNode, ParagraphNode, RootNode, COMMAND_PRIORITY_HIGH, DRAGSTART_COMMAND, DROP_COMMAND, INSERT_PARAGRAPH_COMMAND, CLEAR_HISTORY_COMMAND, $addUpdateTag, KEY_ENTER_COMMAND, COMMAND_PRIORITY_CRITICAL, KEY_SPACE_COMMAND, KEY_BACKSPACE_COMMAND, KEY_DOWN_COMMAND } from 'lexical';
|
|
14
14
|
import { buildEditorFromExtensions } from '@lexical/extension';
|
|
15
15
|
import { ListNode, INSERT_UNORDERED_LIST_COMMAND, INSERT_ORDERED_LIST_COMMAND, ListItemNode, $getListDepth, $isListItemNode, $isListNode, registerList } from '@lexical/list';
|
|
16
16
|
import { $createAutoLinkNode, $toggleLink, LinkNode, $createLinkNode, AutoLinkNode, $isLinkNode } from '@lexical/link';
|
|
17
|
+
import { $getNearestNodeOfType, $wrapNodeInElement, $lastToFirstIterator, mergeRegister, $insertFirst, $unwrapAndFilterDescendants, $firstToLastIterator, $descendantsMatching } from '@lexical/utils';
|
|
17
18
|
import { registerPlainText } from '@lexical/plain-text';
|
|
18
19
|
import { RichTextExtension, $isQuoteNode, $isHeadingNode, $createHeadingNode, $createQuoteNode, QuoteNode, HeadingNode, registerRichText } from '@lexical/rich-text';
|
|
19
20
|
import { $generateNodesFromDOM, $generateHtmlFromNodes } from '@lexical/html';
|
|
@@ -22,8 +23,7 @@ import { registerMarkdownShortcuts, TRANSFORMERS } from '@lexical/markdown';
|
|
|
22
23
|
import { createEmptyHistoryState, registerHistory } from '@lexical/history';
|
|
23
24
|
import { createElement, extractPlainTextFromHtml, createAttachmentFigure, isPreviewableImage, dispatch, parseHtml, addBlockSpacing, generateDomId } from './lexxy_helpers.esm.js';
|
|
24
25
|
export { highlightCode as highlightAll, highlightCode } from './lexxy_helpers.esm.js';
|
|
25
|
-
import { INSERT_TABLE_COMMAND, $getTableCellNodeFromLexicalNode, TableCellNode, TableNode, TableRowNode, registerTablePlugin, registerTableSelectionObserver,
|
|
26
|
-
import { $getNearestNodeOfType, $wrapNodeInElement, $lastToFirstIterator, mergeRegister, $insertFirst, $unwrapAndFilterDescendants, $firstToLastIterator, $descendantsMatching } from '@lexical/utils';
|
|
26
|
+
import { INSERT_TABLE_COMMAND, $getTableCellNodeFromLexicalNode, TableCellNode, TableNode, TableRowNode, setScrollableTablesActive, registerTablePlugin, registerTableSelectionObserver, TableCellHeaderStates, $insertTableRowAtSelection, $insertTableColumnAtSelection, $deleteTableRowAtSelection, $deleteTableColumnAtSelection, $findTableNode, $getTableRowIndexFromTableCellNode, $getTableColumnIndexFromTableCellNode, $findCellNode, $getElementForTableNode } from '@lexical/table';
|
|
27
27
|
import { marked } from 'marked';
|
|
28
28
|
import { $insertDataTransferForRichText } from '@lexical/clipboard';
|
|
29
29
|
|
|
@@ -382,9 +382,22 @@ class LexicalToolbarElement extends HTMLElement {
|
|
|
382
382
|
}
|
|
383
383
|
|
|
384
384
|
disconnectedCallback() {
|
|
385
|
+
this.dispose();
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
dispose() {
|
|
385
389
|
this.#uninstallResizeObserver();
|
|
390
|
+
this.#unbindButtons();
|
|
386
391
|
this.#unbindHotkeys();
|
|
387
392
|
this.#unbindFocusListeners();
|
|
393
|
+
this.unregisterSelectionListener?.();
|
|
394
|
+
this.unregisterHistoryListener?.();
|
|
395
|
+
|
|
396
|
+
this.editorElement = null;
|
|
397
|
+
this.editor = null;
|
|
398
|
+
this.selection = null;
|
|
399
|
+
|
|
400
|
+
this.#createEditorPromise();
|
|
388
401
|
}
|
|
389
402
|
|
|
390
403
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
@@ -428,10 +441,12 @@ class LexicalToolbarElement extends HTMLElement {
|
|
|
428
441
|
this.connectedCallback();
|
|
429
442
|
}
|
|
430
443
|
|
|
431
|
-
#createEditorPromise() {
|
|
444
|
+
async #createEditorPromise() {
|
|
432
445
|
this.editorPromise = new Promise((resolve) => {
|
|
433
446
|
this.resolveEditorPromise = resolve;
|
|
434
447
|
});
|
|
448
|
+
|
|
449
|
+
this.editorElement = await this.editorPromise;
|
|
435
450
|
}
|
|
436
451
|
|
|
437
452
|
#installResizeObserver() {
|
|
@@ -447,10 +462,14 @@ class LexicalToolbarElement extends HTMLElement {
|
|
|
447
462
|
}
|
|
448
463
|
|
|
449
464
|
#bindButtons() {
|
|
450
|
-
this.addEventListener("click", this.#handleButtonClicked
|
|
465
|
+
this.addEventListener("click", this.#handleButtonClicked);
|
|
451
466
|
}
|
|
452
467
|
|
|
453
|
-
#
|
|
468
|
+
#unbindButtons() {
|
|
469
|
+
this.removeEventListener("click", this.#handleButtonClicked);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
#handleButtonClicked = (event) => {
|
|
454
473
|
this.#handleTargetClicked(event, "[data-command]", this.#dispatchButtonCommand.bind(this));
|
|
455
474
|
}
|
|
456
475
|
|
|
@@ -510,8 +529,8 @@ class LexicalToolbarElement extends HTMLElement {
|
|
|
510
529
|
}
|
|
511
530
|
|
|
512
531
|
#unbindFocusListeners() {
|
|
513
|
-
this.editorElement
|
|
514
|
-
this.editorElement
|
|
532
|
+
this.editorElement?.removeEventListener("lexxy:focus", this.#handleEditorFocus);
|
|
533
|
+
this.editorElement?.removeEventListener("lexxy:blur", this.#handleEditorBlur);
|
|
515
534
|
this.removeEventListener("keydown", this.#handleKeydown);
|
|
516
535
|
}
|
|
517
536
|
|
|
@@ -535,7 +554,7 @@ class LexicalToolbarElement extends HTMLElement {
|
|
|
535
554
|
}
|
|
536
555
|
|
|
537
556
|
#monitorSelectionChanges() {
|
|
538
|
-
this.editor.registerUpdateListener(() => {
|
|
557
|
+
this.unregisterSelectionListener = this.editor.registerUpdateListener(() => {
|
|
539
558
|
this.editor.getEditorState().read(() => {
|
|
540
559
|
this.#updateButtonStates();
|
|
541
560
|
this.#closeDropdowns();
|
|
@@ -544,7 +563,7 @@ class LexicalToolbarElement extends HTMLElement {
|
|
|
544
563
|
}
|
|
545
564
|
|
|
546
565
|
#monitorHistoryChanges() {
|
|
547
|
-
this.editor.registerUpdateListener(() => {
|
|
566
|
+
this.unregisterHistoryListener = this.editor.registerUpdateListener(() => {
|
|
548
567
|
this.#updateUndoRedoButtonStates();
|
|
549
568
|
});
|
|
550
569
|
}
|
|
@@ -1390,6 +1409,24 @@ function isSelectionHighlighted(selection) {
|
|
|
1390
1409
|
}
|
|
1391
1410
|
}
|
|
1392
1411
|
|
|
1412
|
+
function getHighlightStyles(selection) {
|
|
1413
|
+
if (!$isRangeSelection(selection)) return null
|
|
1414
|
+
|
|
1415
|
+
let styles = getStyleObjectFromCSS(selection.style);
|
|
1416
|
+
if (!styles.color && !styles["background-color"]) {
|
|
1417
|
+
const anchorNode = selection.anchor.getNode();
|
|
1418
|
+
if ($isTextNode(anchorNode)) {
|
|
1419
|
+
styles = getStyleObjectFromCSS(anchorNode.getStyle());
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
const color = styles.color || null;
|
|
1424
|
+
const backgroundColor = styles["background-color"] || null;
|
|
1425
|
+
if (!color && !backgroundColor) return null
|
|
1426
|
+
|
|
1427
|
+
return { color, backgroundColor }
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1393
1430
|
function hasHighlightStyles(cssOrStyles) {
|
|
1394
1431
|
const styles = typeof cssOrStyles === "string" ? getStyleObjectFromCSS(cssOrStyles) : cssOrStyles;
|
|
1395
1432
|
return !!(styles.color || styles["background-color"])
|
|
@@ -1952,6 +1989,7 @@ const COMMANDS = [
|
|
|
1952
1989
|
"insertOrderedList",
|
|
1953
1990
|
"insertQuoteBlock",
|
|
1954
1991
|
"insertCodeBlock",
|
|
1992
|
+
"setCodeLanguage",
|
|
1955
1993
|
"insertHorizontalDivider",
|
|
1956
1994
|
"uploadImage",
|
|
1957
1995
|
"uploadFile",
|
|
@@ -1964,9 +2002,10 @@ const COMMANDS = [
|
|
|
1964
2002
|
|
|
1965
2003
|
class CommandDispatcher {
|
|
1966
2004
|
#selectionBeforeDrag = null
|
|
2005
|
+
#unregister = []
|
|
1967
2006
|
|
|
1968
2007
|
static configureFor(editorElement) {
|
|
1969
|
-
new CommandDispatcher(editorElement)
|
|
2008
|
+
return new CommandDispatcher(editorElement)
|
|
1970
2009
|
}
|
|
1971
2010
|
|
|
1972
2011
|
constructor(editorElement) {
|
|
@@ -2026,7 +2065,14 @@ class CommandDispatcher {
|
|
|
2026
2065
|
}
|
|
2027
2066
|
|
|
2028
2067
|
dispatchUnlink() {
|
|
2029
|
-
this
|
|
2068
|
+
this.editor.update(() => {
|
|
2069
|
+
// Let adapters signal whether unlink should target a frozen link key.
|
|
2070
|
+
if (this.editorElement.adapter.unlinkFrozenNode?.()) {
|
|
2071
|
+
return
|
|
2072
|
+
}
|
|
2073
|
+
|
|
2074
|
+
$toggleLink(null);
|
|
2075
|
+
});
|
|
2030
2076
|
}
|
|
2031
2077
|
|
|
2032
2078
|
dispatchInsertUnorderedList() {
|
|
@@ -2117,6 +2163,17 @@ class CommandDispatcher {
|
|
|
2117
2163
|
}
|
|
2118
2164
|
}
|
|
2119
2165
|
|
|
2166
|
+
dispatchSetCodeLanguage(language) {
|
|
2167
|
+
this.editor.update(() => {
|
|
2168
|
+
if (!this.selection.isInsideCodeBlock) return
|
|
2169
|
+
|
|
2170
|
+
const codeNode = this.selection.nearestNodeOfType(CodeNode);
|
|
2171
|
+
if (!codeNode) return
|
|
2172
|
+
|
|
2173
|
+
codeNode.setLanguage(language);
|
|
2174
|
+
});
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2120
2177
|
dispatchInsertHorizontalDivider() {
|
|
2121
2178
|
this.contents.insertAtCursorEnsuringLineBelow(new HorizontalDividerNode());
|
|
2122
2179
|
this.editor.focus();
|
|
@@ -2178,6 +2235,13 @@ class CommandDispatcher {
|
|
|
2178
2235
|
this.editor.dispatchCommand(REDO_COMMAND, undefined);
|
|
2179
2236
|
}
|
|
2180
2237
|
|
|
2238
|
+
dispose() {
|
|
2239
|
+
while (this.#unregister.length) {
|
|
2240
|
+
const unregister = this.#unregister.pop();
|
|
2241
|
+
unregister();
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2244
|
+
|
|
2181
2245
|
#registerCommands() {
|
|
2182
2246
|
for (const command of COMMANDS) {
|
|
2183
2247
|
const methodName = `dispatch${capitalize(command)}`;
|
|
@@ -2188,12 +2252,12 @@ class CommandDispatcher {
|
|
|
2188
2252
|
}
|
|
2189
2253
|
|
|
2190
2254
|
#registerCommandHandler(command, priority, handler) {
|
|
2191
|
-
this.editor.registerCommand(command, handler, priority);
|
|
2255
|
+
this.#unregister.push(this.editor.registerCommand(command, handler, priority));
|
|
2192
2256
|
}
|
|
2193
2257
|
|
|
2194
2258
|
#registerKeyboardCommands() {
|
|
2195
|
-
this
|
|
2196
|
-
this
|
|
2259
|
+
this.#registerCommandHandler(KEY_ARROW_RIGHT_COMMAND, COMMAND_PRIORITY_NORMAL, this.#handleArrowRightKey.bind(this));
|
|
2260
|
+
this.#registerCommandHandler(KEY_TAB_COMMAND, COMMAND_PRIORITY_NORMAL, this.#handleTabKey.bind(this));
|
|
2197
2261
|
}
|
|
2198
2262
|
|
|
2199
2263
|
#handleArrowRightKey(event) {
|
|
@@ -2308,16 +2372,6 @@ class CommandDispatcher {
|
|
|
2308
2372
|
return $isRangeSelection(selection) && selection.isCollapsed()
|
|
2309
2373
|
}
|
|
2310
2374
|
|
|
2311
|
-
// Not using TOGGLE_LINK_COMMAND because it's not handled unless you use React/LinkPlugin
|
|
2312
|
-
#toggleLink(url) {
|
|
2313
|
-
this.editor.update(() => {
|
|
2314
|
-
if (url === null) {
|
|
2315
|
-
$toggleLink(null);
|
|
2316
|
-
} else {
|
|
2317
|
-
$toggleLink(url);
|
|
2318
|
-
}
|
|
2319
|
-
});
|
|
2320
|
-
}
|
|
2321
2375
|
}
|
|
2322
2376
|
|
|
2323
2377
|
function capitalize(str) {
|
|
@@ -2544,8 +2598,8 @@ class ActionTextAttachmentNode extends DecoratorNode {
|
|
|
2544
2598
|
return null
|
|
2545
2599
|
}
|
|
2546
2600
|
|
|
2547
|
-
createAttachmentFigure() {
|
|
2548
|
-
const figure = createAttachmentFigure(this.contentType,
|
|
2601
|
+
createAttachmentFigure(previewable = this.isPreviewableAttachment) {
|
|
2602
|
+
const figure = createAttachmentFigure(this.contentType, previewable, this.fileName);
|
|
2549
2603
|
figure.draggable = true;
|
|
2550
2604
|
figure.dataset.lexicalNodeKey = this.__key;
|
|
2551
2605
|
|
|
@@ -2679,6 +2733,8 @@ function $isActionTextAttachmentNode(node) {
|
|
|
2679
2733
|
}
|
|
2680
2734
|
|
|
2681
2735
|
class Selection {
|
|
2736
|
+
#unregister = []
|
|
2737
|
+
|
|
2682
2738
|
constructor(editorElement) {
|
|
2683
2739
|
this.editorElement = editorElement;
|
|
2684
2740
|
this.editorContentElement = editorElement.editorContentElement;
|
|
@@ -2935,6 +2991,18 @@ class Selection {
|
|
|
2935
2991
|
return this.#findPreviousSiblingUp(anchorNode)
|
|
2936
2992
|
}
|
|
2937
2993
|
|
|
2994
|
+
dispose() {
|
|
2995
|
+
this.editorElement = null;
|
|
2996
|
+
this.editorContentElement = null;
|
|
2997
|
+
this.editor = null;
|
|
2998
|
+
this.previouslySelectedKeys = null;
|
|
2999
|
+
|
|
3000
|
+
while (this.#unregister.length) {
|
|
3001
|
+
const unregister = this.#unregister.pop();
|
|
3002
|
+
unregister();
|
|
3003
|
+
}
|
|
3004
|
+
}
|
|
3005
|
+
|
|
2938
3006
|
// When all inline code text is deleted, Lexical's selection retains the stale
|
|
2939
3007
|
// code format flag. Verify the flag is backed by actual code-formatted content:
|
|
2940
3008
|
// a code block ancestor or a text node that carries the code format.
|
|
@@ -2950,7 +3018,7 @@ class Selection {
|
|
|
2950
3018
|
// detects that stale state and clears it so newly typed text won't be
|
|
2951
3019
|
// code-formatted.
|
|
2952
3020
|
#clearStaleInlineCodeFormat() {
|
|
2953
|
-
this.editor.registerUpdateListener(({ editorState, tags }) => {
|
|
3021
|
+
this.#unregister.push(this.editor.registerUpdateListener(({ editorState, tags }) => {
|
|
2954
3022
|
if (tags.has("history-merge") || tags.has("skip-dom-selection")) return
|
|
2955
3023
|
|
|
2956
3024
|
let isStale = false;
|
|
@@ -2979,7 +3047,7 @@ class Selection {
|
|
|
2979
3047
|
});
|
|
2980
3048
|
}, 0);
|
|
2981
3049
|
}
|
|
2982
|
-
});
|
|
3050
|
+
}));
|
|
2983
3051
|
}
|
|
2984
3052
|
|
|
2985
3053
|
get #currentlySelectedKeys() {
|
|
@@ -2998,29 +3066,32 @@ class Selection {
|
|
|
2998
3066
|
}
|
|
2999
3067
|
|
|
3000
3068
|
#processSelectionChangeCommands() {
|
|
3001
|
-
this
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3069
|
+
this.#unregister.push(mergeRegister$1(
|
|
3070
|
+
this.editor.registerCommand(KEY_ARROW_LEFT_COMMAND, this.#selectPreviousNode.bind(this), COMMAND_PRIORITY_LOW),
|
|
3071
|
+
this.editor.registerCommand(KEY_ARROW_RIGHT_COMMAND, this.#selectNextNode.bind(this), COMMAND_PRIORITY_LOW),
|
|
3072
|
+
this.editor.registerCommand(KEY_ARROW_UP_COMMAND, this.#selectPreviousTopLevelNode.bind(this), COMMAND_PRIORITY_LOW),
|
|
3073
|
+
this.editor.registerCommand(KEY_ARROW_DOWN_COMMAND, this.#selectNextTopLevelNode.bind(this), COMMAND_PRIORITY_LOW),
|
|
3005
3074
|
|
|
3006
|
-
|
|
3075
|
+
this.editor.registerCommand(DELETE_CHARACTER_COMMAND, this.#selectDecoratorNodeBeforeDeletion.bind(this), COMMAND_PRIORITY_LOW),
|
|
3007
3076
|
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3077
|
+
this.editor.registerCommand(SELECTION_CHANGE_COMMAND, () => {
|
|
3078
|
+
this.current = $getSelection();
|
|
3079
|
+
}, COMMAND_PRIORITY_LOW)
|
|
3080
|
+
));
|
|
3011
3081
|
}
|
|
3012
3082
|
|
|
3013
3083
|
#listenForNodeSelections() {
|
|
3014
|
-
this.editor.registerCommand(CLICK_COMMAND, ({ target }) => {
|
|
3084
|
+
this.#unregister.push(this.editor.registerCommand(CLICK_COMMAND, ({ target }) => {
|
|
3015
3085
|
if (!isDOMNode(target)) return false
|
|
3016
3086
|
|
|
3017
3087
|
const targetNode = $getNearestNodeFromDOMNode(target);
|
|
3018
3088
|
return $isDecoratorNode(targetNode) && this.#selectInLexical(targetNode)
|
|
3019
|
-
}, COMMAND_PRIORITY_LOW);
|
|
3089
|
+
}, COMMAND_PRIORITY_LOW));
|
|
3020
3090
|
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3091
|
+
const moveNextLineHandler = () => this.#selectOrAppendNextLine();
|
|
3092
|
+
const rootElement = this.editor.getRootElement();
|
|
3093
|
+
rootElement.addEventListener("lexxy:internal:move-to-next-line", moveNextLineHandler);
|
|
3094
|
+
this.#unregister.push(() => rootElement.removeEventListener("lexxy:internal:move-to-next-line", moveNextLineHandler));
|
|
3024
3095
|
}
|
|
3025
3096
|
|
|
3026
3097
|
#containEditorFocus() {
|
|
@@ -3613,9 +3684,12 @@ class ActionTextAttachmentUploadNode extends ActionTextAttachmentNode {
|
|
|
3613
3684
|
// node is reloaded from saved state such as from history.
|
|
3614
3685
|
this.#startUploadIfNeeded();
|
|
3615
3686
|
|
|
3616
|
-
|
|
3687
|
+
// Bridge-managed uploads (uploadUrl is null) don't have file data to show
|
|
3688
|
+
// an image preview, so always show the file icon during upload.
|
|
3689
|
+
const canPreviewFile = this.isPreviewableAttachment && this.uploadUrl != null;
|
|
3690
|
+
const figure = this.createAttachmentFigure(canPreviewFile);
|
|
3617
3691
|
|
|
3618
|
-
if (
|
|
3692
|
+
if (canPreviewFile) {
|
|
3619
3693
|
const img = figure.appendChild(this.#createDOMForImage());
|
|
3620
3694
|
|
|
3621
3695
|
// load file locally to set dimensions and prevent vertical shifting
|
|
@@ -3715,6 +3789,7 @@ class ActionTextAttachmentUploadNode extends ActionTextAttachmentNode {
|
|
|
3715
3789
|
|
|
3716
3790
|
async #startUploadIfNeeded() {
|
|
3717
3791
|
if (this.#uploadStarted) return
|
|
3792
|
+
if (!this.uploadUrl) return // Bridge-managed upload — skip DirectUpload
|
|
3718
3793
|
|
|
3719
3794
|
this.#setUploadStarted();
|
|
3720
3795
|
|
|
@@ -3731,7 +3806,9 @@ class ActionTextAttachmentUploadNode extends ActionTextAttachmentNode {
|
|
|
3731
3806
|
this.#handleUploadError(error);
|
|
3732
3807
|
} else {
|
|
3733
3808
|
this.#dispatchEvent("lexxy:upload-end", { file: this.file, error: null });
|
|
3734
|
-
this
|
|
3809
|
+
this.editor.update(() => {
|
|
3810
|
+
this.showUploadedAttachment(blob);
|
|
3811
|
+
}, { tag: this.#backgroundUpdateTags });
|
|
3735
3812
|
}
|
|
3736
3813
|
});
|
|
3737
3814
|
}
|
|
@@ -3775,17 +3852,15 @@ class ActionTextAttachmentUploadNode extends ActionTextAttachmentNode {
|
|
|
3775
3852
|
}, { tag: this.#backgroundUpdateTags });
|
|
3776
3853
|
}
|
|
3777
3854
|
|
|
3778
|
-
|
|
3779
|
-
const
|
|
3855
|
+
showUploadedAttachment(blob) {
|
|
3856
|
+
const replacementNode = this.#toActionTextAttachmentNodeWith(blob);
|
|
3857
|
+
this.replace(replacementNode);
|
|
3780
3858
|
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3859
|
+
if ($isRootOrShadowRoot(replacementNode.getParent())) {
|
|
3860
|
+
replacementNode.selectNext();
|
|
3861
|
+
}
|
|
3784
3862
|
|
|
3785
|
-
|
|
3786
|
-
replacementNode.selectNext();
|
|
3787
|
-
}
|
|
3788
|
-
}, { tag: this.#backgroundUpdateTags });
|
|
3863
|
+
return replacementNode.getKey()
|
|
3789
3864
|
}
|
|
3790
3865
|
|
|
3791
3866
|
// Upload lifecycle methods (progress, completion, errors) run asynchronously and may
|
|
@@ -4155,7 +4230,11 @@ class Contents {
|
|
|
4155
4230
|
constructor(editorElement) {
|
|
4156
4231
|
this.editorElement = editorElement;
|
|
4157
4232
|
this.editor = editorElement.editor;
|
|
4233
|
+
}
|
|
4158
4234
|
|
|
4235
|
+
dispose() {
|
|
4236
|
+
this.editorElement = null;
|
|
4237
|
+
this.editor = null;
|
|
4159
4238
|
}
|
|
4160
4239
|
|
|
4161
4240
|
insertHtml(html, { tag } = {}) {
|
|
@@ -4383,6 +4462,53 @@ class Contents {
|
|
|
4383
4462
|
});
|
|
4384
4463
|
}
|
|
4385
4464
|
|
|
4465
|
+
insertPendingAttachment(file) {
|
|
4466
|
+
if (!this.editorElement.supportsAttachments) return null
|
|
4467
|
+
|
|
4468
|
+
let nodeKey = null;
|
|
4469
|
+
this.editor.update(() => {
|
|
4470
|
+
const uploadNode = new ActionTextAttachmentUploadNode({
|
|
4471
|
+
file,
|
|
4472
|
+
uploadUrl: null,
|
|
4473
|
+
blobUrlTemplate: this.editorElement.blobUrlTemplate,
|
|
4474
|
+
editor: this.editor
|
|
4475
|
+
});
|
|
4476
|
+
this.insertAtCursor(uploadNode);
|
|
4477
|
+
nodeKey = uploadNode.getKey();
|
|
4478
|
+
}, { tag: HISTORY_MERGE_TAG });
|
|
4479
|
+
|
|
4480
|
+
if (!nodeKey) return null
|
|
4481
|
+
|
|
4482
|
+
const editor = this.editor;
|
|
4483
|
+
return {
|
|
4484
|
+
setAttributes(blob) {
|
|
4485
|
+
editor.update(() => {
|
|
4486
|
+
const node = $getNodeByKey(nodeKey);
|
|
4487
|
+
if (!(node instanceof ActionTextAttachmentUploadNode)) return
|
|
4488
|
+
|
|
4489
|
+
const replacementNodeKey = node.showUploadedAttachment(blob);
|
|
4490
|
+
if (replacementNodeKey) {
|
|
4491
|
+
nodeKey = replacementNodeKey;
|
|
4492
|
+
}
|
|
4493
|
+
}, { tag: HISTORY_MERGE_TAG });
|
|
4494
|
+
},
|
|
4495
|
+
setUploadProgress(progress) {
|
|
4496
|
+
editor.update(() => {
|
|
4497
|
+
const node = $getNodeByKey(nodeKey);
|
|
4498
|
+
if (!(node instanceof ActionTextAttachmentUploadNode)) return
|
|
4499
|
+
|
|
4500
|
+
node.getWritable().progress = progress;
|
|
4501
|
+
}, { tag: HISTORY_MERGE_TAG });
|
|
4502
|
+
},
|
|
4503
|
+
remove() {
|
|
4504
|
+
editor.update(() => {
|
|
4505
|
+
const node = $getNodeByKey(nodeKey);
|
|
4506
|
+
if (node) node.remove();
|
|
4507
|
+
});
|
|
4508
|
+
}
|
|
4509
|
+
}
|
|
4510
|
+
}
|
|
4511
|
+
|
|
4386
4512
|
replaceNodeWithHTML(nodeKey, html, options = {}) {
|
|
4387
4513
|
this.editor.update(() => {
|
|
4388
4514
|
const node = $getNodeByKey(nodeKey);
|
|
@@ -4867,6 +4993,18 @@ class Extensions {
|
|
|
4867
4993
|
}
|
|
4868
4994
|
}
|
|
4869
4995
|
|
|
4996
|
+
class BrowserAdapter {
|
|
4997
|
+
frozenLinkKey = null
|
|
4998
|
+
|
|
4999
|
+
dispatchAttributesChange(attributes, linkHref, highlight, headingTag) {}
|
|
5000
|
+
dispatchEditorInitialized(detail) {}
|
|
5001
|
+
freeze() {}
|
|
5002
|
+
thaw() {}
|
|
5003
|
+
unlinkFrozenNode() {
|
|
5004
|
+
return false
|
|
5005
|
+
}
|
|
5006
|
+
}
|
|
5007
|
+
|
|
4870
5008
|
// Custom TextNode exportDOM that avoids redundant bold/italic wrapping.
|
|
4871
5009
|
//
|
|
4872
5010
|
// Lexical's built-in TextNode.exportDOM() calls createDOM() which produces semantic tags
|
|
@@ -5186,11 +5324,12 @@ class TablesExtension extends LexxyExtension {
|
|
|
5186
5324
|
TableRowNode
|
|
5187
5325
|
],
|
|
5188
5326
|
register(editor) {
|
|
5327
|
+
setScrollableTablesActive(editor, true);
|
|
5328
|
+
|
|
5189
5329
|
return mergeRegister(
|
|
5190
5330
|
// Register Lexical table plugins
|
|
5191
5331
|
registerTablePlugin(editor),
|
|
5192
5332
|
registerTableSelectionObserver(editor, true),
|
|
5193
|
-
setScrollableTablesActive(editor, true),
|
|
5194
5333
|
|
|
5195
5334
|
// Bug fix: Prevent hardcoded background color (Lexical #8089)
|
|
5196
5335
|
editor.registerNodeTransform(TableCellNode, (node) => {
|
|
@@ -5917,6 +6056,8 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
5917
6056
|
|
|
5918
6057
|
#initialValue = ""
|
|
5919
6058
|
#validationTextArea = document.createElement("textarea")
|
|
6059
|
+
#editorInitializedRafId = null
|
|
6060
|
+
#disposables = []
|
|
5920
6061
|
|
|
5921
6062
|
constructor() {
|
|
5922
6063
|
super();
|
|
@@ -5925,20 +6066,28 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
5925
6066
|
}
|
|
5926
6067
|
|
|
5927
6068
|
connectedCallback() {
|
|
5928
|
-
this.id
|
|
6069
|
+
this.id ||= generateDomId("lexxy-editor");
|
|
5929
6070
|
this.config = new EditorConfiguration(this);
|
|
5930
6071
|
this.extensions = new Extensions(this);
|
|
5931
6072
|
|
|
5932
6073
|
this.editor = this.#createEditor();
|
|
6074
|
+
this.#disposables.push(this.editor);
|
|
5933
6075
|
|
|
5934
6076
|
this.contents = new Contents(this);
|
|
6077
|
+
this.#disposables.push(this.contents);
|
|
6078
|
+
|
|
5935
6079
|
this.selection = new Selection(this);
|
|
6080
|
+
this.#disposables.push(this.selection);
|
|
6081
|
+
|
|
5936
6082
|
this.clipboard = new Clipboard(this);
|
|
6083
|
+
this.adapter = new BrowserAdapter();
|
|
6084
|
+
|
|
6085
|
+
const commandDispatcher = CommandDispatcher.configureFor(this);
|
|
6086
|
+
this.#disposables.push(commandDispatcher);
|
|
5937
6087
|
|
|
5938
|
-
CommandDispatcher.configureFor(this);
|
|
5939
6088
|
this.#initialize();
|
|
5940
6089
|
|
|
5941
|
-
|
|
6090
|
+
this.#scheduleEditorInitializedDispatch();
|
|
5942
6091
|
this.toggleAttribute("connected", true);
|
|
5943
6092
|
|
|
5944
6093
|
this.#handleAutofocus();
|
|
@@ -5947,6 +6096,7 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
5947
6096
|
}
|
|
5948
6097
|
|
|
5949
6098
|
disconnectedCallback() {
|
|
6099
|
+
this.#cancelEditorInitializedDispatch();
|
|
5950
6100
|
this.valueBeforeDisconnect = this.value;
|
|
5951
6101
|
this.#reset(); // Prevent hangs with Safari when morphing
|
|
5952
6102
|
}
|
|
@@ -5988,7 +6138,7 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
5988
6138
|
get toolbarElement() {
|
|
5989
6139
|
if (!this.#hasToolbar) return null
|
|
5990
6140
|
|
|
5991
|
-
this.toolbar
|
|
6141
|
+
this.toolbar ??= this.#findOrCreateDefaultToolbar();
|
|
5992
6142
|
return this.toolbar
|
|
5993
6143
|
}
|
|
5994
6144
|
|
|
@@ -6043,6 +6193,32 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
6043
6193
|
return this.config.get("richText")
|
|
6044
6194
|
}
|
|
6045
6195
|
|
|
6196
|
+
registerAdapter(adapter) {
|
|
6197
|
+
this.adapter = adapter;
|
|
6198
|
+
|
|
6199
|
+
if (!this.editor) return
|
|
6200
|
+
|
|
6201
|
+
this.#cancelEditorInitializedDispatch();
|
|
6202
|
+
this.#dispatchEditorInitialized();
|
|
6203
|
+
this.#dispatchAttributesChange();
|
|
6204
|
+
}
|
|
6205
|
+
|
|
6206
|
+
freezeSelection() {
|
|
6207
|
+
this.adapter.freeze();
|
|
6208
|
+
}
|
|
6209
|
+
|
|
6210
|
+
thawSelection() {
|
|
6211
|
+
this.adapter.thaw();
|
|
6212
|
+
}
|
|
6213
|
+
|
|
6214
|
+
dispatchAttributesChange() {
|
|
6215
|
+
this.#dispatchAttributesChange();
|
|
6216
|
+
}
|
|
6217
|
+
|
|
6218
|
+
dispatchEditorInitialized() {
|
|
6219
|
+
this.#dispatchEditorInitialized();
|
|
6220
|
+
}
|
|
6221
|
+
|
|
6046
6222
|
// TODO: Deprecate `single-line` attribute
|
|
6047
6223
|
get isSingleLineMode() {
|
|
6048
6224
|
return this.hasAttribute("single-line")
|
|
@@ -6124,6 +6300,7 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
6124
6300
|
|
|
6125
6301
|
#createEditor() {
|
|
6126
6302
|
this.editorContentElement ||= this.#createEditorContentElement();
|
|
6303
|
+
this.appendChild(this.editorContentElement);
|
|
6127
6304
|
|
|
6128
6305
|
const editor = buildEditorFromExtensions({
|
|
6129
6306
|
name: "lexxy/core",
|
|
@@ -6166,6 +6343,7 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
6166
6343
|
const editorContentElement = createElement("div", {
|
|
6167
6344
|
classList: "lexxy-editor__content",
|
|
6168
6345
|
contenteditable: true,
|
|
6346
|
+
autocapitalize: "none",
|
|
6169
6347
|
role: "textbox",
|
|
6170
6348
|
"aria-multiline": true,
|
|
6171
6349
|
"aria-label": this.#labelText,
|
|
@@ -6173,7 +6351,6 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
6173
6351
|
});
|
|
6174
6352
|
editorContentElement.id = `${this.id}-content`;
|
|
6175
6353
|
this.#ariaAttributes.forEach(attribute => editorContentElement.setAttribute(attribute.name, attribute.value));
|
|
6176
|
-
this.appendChild(editorContentElement);
|
|
6177
6354
|
|
|
6178
6355
|
if (this.getAttribute("tabindex")) {
|
|
6179
6356
|
editorContentElement.setAttribute("tabindex", this.getAttribute("tabindex"));
|
|
@@ -6228,6 +6405,7 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
6228
6405
|
this.#internalFormValue = this.value;
|
|
6229
6406
|
this.#toggleEmptyStatus();
|
|
6230
6407
|
this.#setValidity();
|
|
6408
|
+
this.#dispatchAttributesChange();
|
|
6231
6409
|
}));
|
|
6232
6410
|
}
|
|
6233
6411
|
|
|
@@ -6249,36 +6427,48 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
6249
6427
|
}
|
|
6250
6428
|
|
|
6251
6429
|
#registerComponents() {
|
|
6430
|
+
const registered = [];
|
|
6431
|
+
|
|
6252
6432
|
if (this.supportsRichText) {
|
|
6253
|
-
|
|
6254
|
-
|
|
6433
|
+
registered.push(
|
|
6434
|
+
registerRichText(this.editor),
|
|
6435
|
+
registerList(this.editor)
|
|
6436
|
+
);
|
|
6255
6437
|
this.#registerTableComponents();
|
|
6256
6438
|
this.#registerCodeHiglightingComponents();
|
|
6257
6439
|
if (this.supportsMarkdown) {
|
|
6258
|
-
|
|
6259
|
-
|
|
6440
|
+
registered.push(
|
|
6441
|
+
registerMarkdownShortcuts(this.editor, TRANSFORMERS),
|
|
6442
|
+
registerMarkdownLeadingTagHandler(this.editor, TRANSFORMERS)
|
|
6443
|
+
);
|
|
6260
6444
|
}
|
|
6261
6445
|
} else {
|
|
6262
|
-
registerPlainText(this.editor);
|
|
6446
|
+
registered.push(registerPlainText(this.editor));
|
|
6263
6447
|
}
|
|
6264
6448
|
this.historyState = createEmptyHistoryState();
|
|
6265
|
-
registerHistory(this.editor, this.historyState, 20);
|
|
6449
|
+
registered.push(registerHistory(this.editor, this.historyState, 20));
|
|
6450
|
+
|
|
6451
|
+
this.#addUnregisterHandler(mergeRegister$1(...registered));
|
|
6266
6452
|
}
|
|
6267
6453
|
|
|
6268
6454
|
#registerTableComponents() {
|
|
6269
|
-
|
|
6270
|
-
|
|
6455
|
+
let tableTools = this.querySelector("lexxy-table-tools");
|
|
6456
|
+
tableTools ??= createElement("lexxy-table-tools");
|
|
6457
|
+
this.append(tableTools);
|
|
6458
|
+
this.#disposables.push(tableTools);
|
|
6271
6459
|
}
|
|
6272
6460
|
|
|
6273
6461
|
#registerCodeHiglightingComponents() {
|
|
6274
6462
|
registerCodeHighlighting(this.editor);
|
|
6275
|
-
|
|
6276
|
-
|
|
6463
|
+
let codeLanguagePicker = this.querySelector("lexxy-code-language-picker");
|
|
6464
|
+
codeLanguagePicker ??= createElement("lexxy-code-language-picker");
|
|
6465
|
+
this.append(codeLanguagePicker);
|
|
6466
|
+
this.#disposables.push(codeLanguagePicker);
|
|
6277
6467
|
}
|
|
6278
6468
|
|
|
6279
6469
|
#handleEnter() {
|
|
6280
6470
|
// We can't prevent these externally using regular keydown because Lexical handles it first.
|
|
6281
|
-
this.editor.registerCommand(
|
|
6471
|
+
this.#addUnregisterHandler(this.editor.registerCommand(
|
|
6282
6472
|
KEY_ENTER_COMMAND,
|
|
6283
6473
|
(event) => {
|
|
6284
6474
|
// Prevent CTRL+ENTER
|
|
@@ -6296,16 +6486,22 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
6296
6486
|
return false
|
|
6297
6487
|
},
|
|
6298
6488
|
COMMAND_PRIORITY_NORMAL
|
|
6299
|
-
);
|
|
6489
|
+
));
|
|
6300
6490
|
}
|
|
6301
6491
|
|
|
6302
6492
|
#registerFocusEvents() {
|
|
6303
6493
|
this.addEventListener("focusin", this.#handleFocusIn);
|
|
6304
6494
|
this.addEventListener("focusout", this.#handleFocusOut);
|
|
6495
|
+
|
|
6496
|
+
this.#addUnregisterHandler(() => {
|
|
6497
|
+
this.removeEventListener("focusin", this.#handleFocusIn);
|
|
6498
|
+
this.removeEventListener("focusout", this.#handleFocusOut);
|
|
6499
|
+
});
|
|
6305
6500
|
}
|
|
6306
6501
|
|
|
6307
6502
|
#handleFocusIn(event) {
|
|
6308
6503
|
if (this.#elementInEditorOrToolbar(event.target) && !this.currentlyFocused) {
|
|
6504
|
+
this.#dispatchAttributesChange();
|
|
6309
6505
|
dispatch(this, "lexxy:focus");
|
|
6310
6506
|
this.currentlyFocused = true;
|
|
6311
6507
|
}
|
|
@@ -6344,6 +6540,10 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
6344
6540
|
#attachToolbar() {
|
|
6345
6541
|
if (this.#hasToolbar) {
|
|
6346
6542
|
this.toolbarElement.setEditor(this);
|
|
6543
|
+
if (typeof this.toolbarElement.dispose === "function") {
|
|
6544
|
+
this.#disposables.push(this.toolbarElement);
|
|
6545
|
+
}
|
|
6546
|
+
|
|
6347
6547
|
this.extensions.initializeToolbars();
|
|
6348
6548
|
}
|
|
6349
6549
|
}
|
|
@@ -6353,7 +6553,7 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
6353
6553
|
if (typeof toolbarConfig === "string") {
|
|
6354
6554
|
return document.getElementById(toolbarConfig)
|
|
6355
6555
|
} else {
|
|
6356
|
-
return this.#createDefaultToolbar()
|
|
6556
|
+
return this.querySelector("lexxy-toolbar") ?? this.#createDefaultToolbar()
|
|
6357
6557
|
}
|
|
6358
6558
|
}
|
|
6359
6559
|
|
|
@@ -6382,35 +6582,129 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
6382
6582
|
}
|
|
6383
6583
|
}
|
|
6384
6584
|
|
|
6385
|
-
#
|
|
6386
|
-
|
|
6585
|
+
#dispatchAttributesChange() {
|
|
6586
|
+
let attributes = null;
|
|
6587
|
+
let linkHref = null;
|
|
6588
|
+
let highlight = null;
|
|
6589
|
+
let headingTag = null;
|
|
6387
6590
|
|
|
6388
|
-
|
|
6389
|
-
|
|
6390
|
-
|
|
6391
|
-
}
|
|
6591
|
+
this.editor.getEditorState().read(() => {
|
|
6592
|
+
const selection = $getSelection();
|
|
6593
|
+
if (!$isRangeSelection(selection)) return
|
|
6392
6594
|
|
|
6393
|
-
|
|
6394
|
-
|
|
6595
|
+
const format = this.selection.getFormat();
|
|
6596
|
+
if (Object.keys(format).length === 0) return
|
|
6395
6597
|
|
|
6396
|
-
|
|
6397
|
-
|
|
6398
|
-
|
|
6399
|
-
|
|
6598
|
+
const anchorNode = selection.anchor.getNode();
|
|
6599
|
+
const linkNode = $getNearestNodeOfType(anchorNode, LinkNode);
|
|
6600
|
+
|
|
6601
|
+
attributes = {
|
|
6602
|
+
bold: { active: format.isBold, enabled: true },
|
|
6603
|
+
italic: { active: format.isItalic, enabled: true },
|
|
6604
|
+
strikethrough: { active: format.isStrikethrough, enabled: true },
|
|
6605
|
+
code: { active: format.isInCode, enabled: true },
|
|
6606
|
+
highlight: { active: format.isHighlight, enabled: true },
|
|
6607
|
+
link: { active: format.isInLink, enabled: true },
|
|
6608
|
+
quote: { active: format.isInQuote, enabled: true },
|
|
6609
|
+
heading: { active: format.isInHeading, enabled: true },
|
|
6610
|
+
"unordered-list": { active: format.isInList && format.listType === "bullet", enabled: true },
|
|
6611
|
+
"ordered-list": { active: format.isInList && format.listType === "number", enabled: true },
|
|
6612
|
+
undo: { active: false, enabled: this.historyState?.undoStack.length > 0 },
|
|
6613
|
+
redo: { active: false, enabled: this.historyState?.redoStack.length > 0 }
|
|
6614
|
+
};
|
|
6615
|
+
|
|
6616
|
+
linkHref = linkNode ? linkNode.getURL() : null;
|
|
6617
|
+
highlight = format.isHighlight ? getHighlightStyles(selection) : null;
|
|
6618
|
+
headingTag = format.headingTag ?? null;
|
|
6619
|
+
});
|
|
6400
6620
|
|
|
6401
|
-
if (
|
|
6402
|
-
this.
|
|
6403
|
-
this.codeLanguagePicker = null;
|
|
6621
|
+
if (attributes) {
|
|
6622
|
+
this.adapter.dispatchAttributesChange(attributes, linkHref, highlight, headingTag);
|
|
6404
6623
|
}
|
|
6624
|
+
}
|
|
6405
6625
|
|
|
6406
|
-
|
|
6407
|
-
|
|
6408
|
-
this.tableHandler = null;
|
|
6409
|
-
}
|
|
6626
|
+
#dispatchEditorInitialized() {
|
|
6627
|
+
if (!this.adapter) return
|
|
6410
6628
|
|
|
6411
|
-
this.
|
|
6629
|
+
this.adapter.dispatchEditorInitialized({
|
|
6630
|
+
highlightColors: this.#resolvedHighlightColors,
|
|
6631
|
+
headingFormats: this.#supportedHeadingFormats
|
|
6632
|
+
});
|
|
6633
|
+
}
|
|
6634
|
+
|
|
6635
|
+
#scheduleEditorInitializedDispatch() {
|
|
6636
|
+
this.#cancelEditorInitializedDispatch();
|
|
6637
|
+
this.#editorInitializedRafId = requestAnimationFrame(() => {
|
|
6638
|
+
this.#editorInitializedRafId = null;
|
|
6639
|
+
if (!this.isConnected || !this.adapter) return
|
|
6640
|
+
|
|
6641
|
+
dispatch(this, "lexxy:initialize");
|
|
6642
|
+
this.#dispatchEditorInitialized();
|
|
6643
|
+
});
|
|
6644
|
+
}
|
|
6645
|
+
|
|
6646
|
+
#cancelEditorInitializedDispatch() {
|
|
6647
|
+
if (this.#editorInitializedRafId == null) return
|
|
6648
|
+
|
|
6649
|
+
cancelAnimationFrame(this.#editorInitializedRafId);
|
|
6650
|
+
this.#editorInitializedRafId = null;
|
|
6651
|
+
}
|
|
6652
|
+
|
|
6653
|
+
get #resolvedHighlightColors() {
|
|
6654
|
+
const buttons = this.config.get("highlight.buttons");
|
|
6655
|
+
if (!buttons) return null
|
|
6656
|
+
|
|
6657
|
+
const colors = this.#resolveColors("color", buttons.color || []);
|
|
6658
|
+
const backgroundColors = this.#resolveColors("background-color", buttons["background-color"] || []);
|
|
6659
|
+
return { colors, backgroundColors }
|
|
6660
|
+
}
|
|
6661
|
+
|
|
6662
|
+
get #supportedHeadingFormats() {
|
|
6663
|
+
if (!this.supportsRichText) return []
|
|
6664
|
+
|
|
6665
|
+
return [
|
|
6666
|
+
{ label: "Normal", command: "setFormatParagraph", tag: null },
|
|
6667
|
+
{ label: "Large heading", command: "setFormatHeadingLarge", tag: "h2" },
|
|
6668
|
+
{ label: "Medium heading", command: "setFormatHeadingMedium", tag: "h3" },
|
|
6669
|
+
{ label: "Small heading", command: "setFormatHeadingSmall", tag: "h4" },
|
|
6670
|
+
]
|
|
6671
|
+
}
|
|
6672
|
+
|
|
6673
|
+
#resolveColors(property, cssValues) {
|
|
6674
|
+
const resolver = document.createElement("span");
|
|
6675
|
+
resolver.style.display = "none";
|
|
6676
|
+
this.appendChild(resolver);
|
|
6677
|
+
|
|
6678
|
+
const resolved = cssValues.map(cssValue => {
|
|
6679
|
+
resolver.style.setProperty(property, cssValue);
|
|
6680
|
+
const value = window.getComputedStyle(resolver).getPropertyValue(property);
|
|
6681
|
+
resolver.style.removeProperty(property);
|
|
6682
|
+
return { name: cssValue, value }
|
|
6683
|
+
});
|
|
6684
|
+
|
|
6685
|
+
resolver.remove();
|
|
6686
|
+
return resolved
|
|
6687
|
+
}
|
|
6688
|
+
|
|
6689
|
+
#reset() {
|
|
6690
|
+
this.#cancelEditorInitializedDispatch();
|
|
6691
|
+
this.#dispose();
|
|
6692
|
+
this.editorContentElement?.remove();
|
|
6693
|
+
this.editorContentElement = null;
|
|
6412
6694
|
|
|
6695
|
+
// Prevents issues with turbo morphing receiving an empty <lexxy-editor> which wipes
|
|
6696
|
+
// out the DOM for the tools, and the old toolbar reference will cause issues
|
|
6697
|
+
this.toolbar = null;
|
|
6698
|
+
}
|
|
6699
|
+
|
|
6700
|
+
#dispose() {
|
|
6701
|
+
this.#unregisterHandlers();
|
|
6702
|
+
this.adapter = null;
|
|
6413
6703
|
document.removeEventListener("turbo:before-cache", this.#handleTurboBeforeCache);
|
|
6704
|
+
|
|
6705
|
+
while (this.#disposables.length) {
|
|
6706
|
+
this.#disposables.pop().dispose();
|
|
6707
|
+
}
|
|
6414
6708
|
}
|
|
6415
6709
|
|
|
6416
6710
|
#reconnect() {
|
|
@@ -6451,14 +6745,15 @@ class ToolbarDropdown extends HTMLElement {
|
|
|
6451
6745
|
connectedCallback() {
|
|
6452
6746
|
this.container = this.closest("details");
|
|
6453
6747
|
|
|
6454
|
-
this.container.addEventListener("toggle", this.#handleToggle
|
|
6455
|
-
this.container.addEventListener("keydown", this.#handleKeyDown
|
|
6748
|
+
this.container.addEventListener("toggle", this.#handleToggle);
|
|
6749
|
+
this.container.addEventListener("keydown", this.#handleKeyDown);
|
|
6456
6750
|
|
|
6457
6751
|
this.#onToolbarEditor(this.initialize.bind(this));
|
|
6458
6752
|
}
|
|
6459
6753
|
|
|
6460
6754
|
disconnectedCallback() {
|
|
6461
|
-
this.container
|
|
6755
|
+
this.container?.removeEventListener("toggle", this.#handleToggle);
|
|
6756
|
+
this.container?.removeEventListener("keydown", this.#handleKeyDown);
|
|
6462
6757
|
}
|
|
6463
6758
|
|
|
6464
6759
|
get toolbar() {
|
|
@@ -6483,11 +6778,11 @@ class ToolbarDropdown extends HTMLElement {
|
|
|
6483
6778
|
}
|
|
6484
6779
|
|
|
6485
6780
|
async #onToolbarEditor(callback) {
|
|
6486
|
-
await this.toolbar.
|
|
6781
|
+
await this.toolbar.editorElement;
|
|
6487
6782
|
callback();
|
|
6488
6783
|
}
|
|
6489
6784
|
|
|
6490
|
-
#handleToggle() {
|
|
6785
|
+
#handleToggle = () => {
|
|
6491
6786
|
if (this.container.open) {
|
|
6492
6787
|
this.#handleOpen();
|
|
6493
6788
|
}
|
|
@@ -6498,7 +6793,7 @@ class ToolbarDropdown extends HTMLElement {
|
|
|
6498
6793
|
this.#resetTabIndexValues();
|
|
6499
6794
|
}
|
|
6500
6795
|
|
|
6501
|
-
#handleKeyDown(event) {
|
|
6796
|
+
#handleKeyDown = (event) => {
|
|
6502
6797
|
if (event.key === "Escape") {
|
|
6503
6798
|
event.stopPropagation();
|
|
6504
6799
|
this.close();
|
|
@@ -6526,27 +6821,30 @@ class LinkDropdown extends ToolbarDropdown {
|
|
|
6526
6821
|
super.connectedCallback();
|
|
6527
6822
|
this.input = this.querySelector("input");
|
|
6528
6823
|
|
|
6529
|
-
this.#
|
|
6824
|
+
this.container.addEventListener("toggle", this.#handleToggle);
|
|
6825
|
+
this.addEventListener("submit", this.#handleSubmit);
|
|
6826
|
+
this.querySelector("[value='unlink']").addEventListener("click", this.#handleUnlink);
|
|
6530
6827
|
}
|
|
6531
6828
|
|
|
6532
|
-
|
|
6533
|
-
this.container
|
|
6534
|
-
this.
|
|
6535
|
-
this.querySelector("[value='unlink']")
|
|
6829
|
+
disconnectedCallback() {
|
|
6830
|
+
this.container?.removeEventListener("toggle", this.#handleToggle);
|
|
6831
|
+
this.removeEventListener("submit", this.#handleSubmit);
|
|
6832
|
+
this.querySelector("[value='unlink']")?.removeEventListener("click", this.#handleUnlink);
|
|
6833
|
+
super.disconnectedCallback();
|
|
6536
6834
|
}
|
|
6537
6835
|
|
|
6538
|
-
#handleToggle({ newState }) {
|
|
6836
|
+
#handleToggle = ({ newState }) => {
|
|
6539
6837
|
this.input.value = this.#selectedLinkUrl;
|
|
6540
6838
|
this.input.required = newState === "open";
|
|
6541
6839
|
}
|
|
6542
6840
|
|
|
6543
|
-
#handleSubmit(event) {
|
|
6841
|
+
#handleSubmit = (event) => {
|
|
6544
6842
|
const command = event.submitter?.value;
|
|
6545
6843
|
this.editor.dispatchCommand(command, this.input.value);
|
|
6546
6844
|
this.close();
|
|
6547
6845
|
}
|
|
6548
6846
|
|
|
6549
|
-
#handleUnlink() {
|
|
6847
|
+
#handleUnlink = () => {
|
|
6550
6848
|
this.editor.dispatchCommand("unlink");
|
|
6551
6849
|
this.close();
|
|
6552
6850
|
}
|
|
@@ -6581,26 +6879,35 @@ const REMOVE_HIGHLIGHT_SELECTOR = "[data-command='removeHighlight']";
|
|
|
6581
6879
|
const NO_STYLE = Symbol("no_style");
|
|
6582
6880
|
|
|
6583
6881
|
class HighlightDropdown extends ToolbarDropdown {
|
|
6584
|
-
connectedCallback() {
|
|
6585
|
-
super.connectedCallback();
|
|
6586
|
-
this.#registerToggleHandler();
|
|
6587
|
-
}
|
|
6588
|
-
|
|
6589
6882
|
initialize() {
|
|
6590
6883
|
this.#setUpButtons();
|
|
6591
6884
|
this.#registerButtonHandlers();
|
|
6592
6885
|
}
|
|
6593
6886
|
|
|
6594
|
-
|
|
6595
|
-
|
|
6887
|
+
connectedCallback() {
|
|
6888
|
+
super.connectedCallback();
|
|
6889
|
+
this.container.addEventListener("toggle", this.#handleToggle);
|
|
6890
|
+
}
|
|
6891
|
+
|
|
6892
|
+
disconnectedCallback() {
|
|
6893
|
+
this.container?.removeEventListener("toggle", this.#handleToggle);
|
|
6894
|
+
this.#removeButtonHandlers();
|
|
6895
|
+
super.disconnectedCallback();
|
|
6596
6896
|
}
|
|
6597
6897
|
|
|
6598
6898
|
#registerButtonHandlers() {
|
|
6599
|
-
this.#colorButtons.forEach(button => button.addEventListener("click", this.#handleColorButtonClick
|
|
6600
|
-
this.querySelector(REMOVE_HIGHLIGHT_SELECTOR).addEventListener("click", this.#handleRemoveHighlightClick
|
|
6899
|
+
this.#colorButtons.forEach(button => button.addEventListener("click", this.#handleColorButtonClick));
|
|
6900
|
+
this.querySelector(REMOVE_HIGHLIGHT_SELECTOR).addEventListener("click", this.#handleRemoveHighlightClick);
|
|
6901
|
+
}
|
|
6902
|
+
|
|
6903
|
+
#removeButtonHandlers() {
|
|
6904
|
+
this.#colorButtons.forEach(button => button.removeEventListener("click", this.#handleColorButtonClick));
|
|
6905
|
+
this.querySelector(REMOVE_HIGHLIGHT_SELECTOR)?.removeEventListener("click", this.#handleRemoveHighlightClick);
|
|
6601
6906
|
}
|
|
6602
6907
|
|
|
6603
6908
|
#setUpButtons() {
|
|
6909
|
+
this.#buttonContainer.innerHTML = "";
|
|
6910
|
+
|
|
6604
6911
|
const colorGroups = this.editorElement.config.get("highlight.buttons");
|
|
6605
6912
|
|
|
6606
6913
|
this.#populateButtonGroup("color", colorGroups.color);
|
|
@@ -6626,7 +6933,7 @@ class HighlightDropdown extends ToolbarDropdown {
|
|
|
6626
6933
|
return button
|
|
6627
6934
|
}
|
|
6628
6935
|
|
|
6629
|
-
#handleToggle({ newState }) {
|
|
6936
|
+
#handleToggle = ({ newState }) => {
|
|
6630
6937
|
if (newState === "open") {
|
|
6631
6938
|
this.editor.getEditorState().read(() => {
|
|
6632
6939
|
this.#updateColorButtonStates($getSelection());
|
|
@@ -6634,7 +6941,7 @@ class HighlightDropdown extends ToolbarDropdown {
|
|
|
6634
6941
|
}
|
|
6635
6942
|
}
|
|
6636
6943
|
|
|
6637
|
-
#handleColorButtonClick(event) {
|
|
6944
|
+
#handleColorButtonClick = (event) => {
|
|
6638
6945
|
event.preventDefault();
|
|
6639
6946
|
|
|
6640
6947
|
const button = event.target.closest(APPLY_HIGHLIGHT_SELECTOR);
|
|
@@ -6647,7 +6954,7 @@ class HighlightDropdown extends ToolbarDropdown {
|
|
|
6647
6954
|
this.close();
|
|
6648
6955
|
}
|
|
6649
6956
|
|
|
6650
|
-
#handleRemoveHighlightClick(event) {
|
|
6957
|
+
#handleRemoveHighlightClick = (event) => {
|
|
6651
6958
|
event.preventDefault();
|
|
6652
6959
|
|
|
6653
6960
|
this.editor.dispatchCommand("removeHighlight");
|
|
@@ -7298,10 +7605,13 @@ class LexicalPromptElement extends HTMLElement {
|
|
|
7298
7605
|
}
|
|
7299
7606
|
|
|
7300
7607
|
class CodeLanguagePicker extends HTMLElement {
|
|
7608
|
+
#abortController = null
|
|
7609
|
+
|
|
7301
7610
|
connectedCallback() {
|
|
7302
7611
|
this.editorElement = this.closest("lexxy-editor");
|
|
7303
7612
|
this.editor = this.editorElement.editor;
|
|
7304
7613
|
this.classList.add("lexxy-floating-controls");
|
|
7614
|
+
this.#abortController = new AbortController();
|
|
7305
7615
|
|
|
7306
7616
|
this.#attachLanguagePicker();
|
|
7307
7617
|
this.#hide();
|
|
@@ -7309,21 +7619,37 @@ class CodeLanguagePicker extends HTMLElement {
|
|
|
7309
7619
|
}
|
|
7310
7620
|
|
|
7311
7621
|
disconnectedCallback() {
|
|
7622
|
+
this.dispose();
|
|
7623
|
+
}
|
|
7624
|
+
|
|
7625
|
+
dispose() {
|
|
7626
|
+
this.#abortController?.abort();
|
|
7627
|
+
this.#abortController = null;
|
|
7312
7628
|
this.unregisterUpdateListener?.();
|
|
7313
7629
|
this.unregisterUpdateListener = null;
|
|
7314
7630
|
}
|
|
7315
7631
|
|
|
7316
7632
|
#attachLanguagePicker() {
|
|
7317
|
-
this.languagePickerElement = this.#createLanguagePicker();
|
|
7633
|
+
this.languagePickerElement = this.#findLanguagePicker() ?? this.#createLanguagePicker();
|
|
7634
|
+
|
|
7635
|
+
const signal = this.#abortController.signal;
|
|
7318
7636
|
|
|
7319
7637
|
this.languagePickerElement.addEventListener("change", () => {
|
|
7320
7638
|
this.#updateCodeBlockLanguage(this.languagePickerElement.value);
|
|
7321
|
-
});
|
|
7639
|
+
}, { signal });
|
|
7640
|
+
|
|
7641
|
+
this.languagePickerElement.addEventListener("mousedown", (event) => {
|
|
7642
|
+
this.#dispatchOpenEvent(event);
|
|
7643
|
+
}, { signal });
|
|
7322
7644
|
|
|
7323
7645
|
this.languagePickerElement.setAttribute("nonce", getNonce());
|
|
7324
7646
|
this.appendChild(this.languagePickerElement);
|
|
7325
7647
|
}
|
|
7326
7648
|
|
|
7649
|
+
#findLanguagePicker() {
|
|
7650
|
+
return this.querySelector("select")
|
|
7651
|
+
}
|
|
7652
|
+
|
|
7327
7653
|
#createLanguagePicker() {
|
|
7328
7654
|
const selectElement = createElement("select", { className: "lexxy-code-language-picker", "aria-label": "Pick a language…", name: "lexxy-code-language" });
|
|
7329
7655
|
|
|
@@ -7356,6 +7682,21 @@ class CodeLanguagePicker extends HTMLElement {
|
|
|
7356
7682
|
return Object.fromEntries([ plainEntry, ...sortedEntries ])
|
|
7357
7683
|
}
|
|
7358
7684
|
|
|
7685
|
+
#dispatchOpenEvent(event) {
|
|
7686
|
+
const handled = !dispatch(this.editorElement, "lexxy:code-language-picker-open", {
|
|
7687
|
+
languages: this.#bridgeLanguages,
|
|
7688
|
+
currentLanguage: this.languagePickerElement.value
|
|
7689
|
+
}, true);
|
|
7690
|
+
|
|
7691
|
+
if (handled) {
|
|
7692
|
+
event.preventDefault();
|
|
7693
|
+
}
|
|
7694
|
+
}
|
|
7695
|
+
|
|
7696
|
+
get #bridgeLanguages() {
|
|
7697
|
+
return Object.entries(this.#languages).map(([ key, name ]) => ({ key, name }))
|
|
7698
|
+
}
|
|
7699
|
+
|
|
7359
7700
|
#updateCodeBlockLanguage(language) {
|
|
7360
7701
|
this.editor.update(() => {
|
|
7361
7702
|
const codeNode = this.#getCurrentCodeNode();
|
|
@@ -7894,6 +8235,10 @@ class TableTools extends HTMLElement {
|
|
|
7894
8235
|
}
|
|
7895
8236
|
|
|
7896
8237
|
disconnectedCallback() {
|
|
8238
|
+
this.dispose();
|
|
8239
|
+
}
|
|
8240
|
+
|
|
8241
|
+
dispose() {
|
|
7897
8242
|
this.#unregisterKeyboardShortcuts();
|
|
7898
8243
|
|
|
7899
8244
|
this.unregisterUpdateListener?.();
|
|
@@ -7918,6 +8263,8 @@ class TableTools extends HTMLElement {
|
|
|
7918
8263
|
}
|
|
7919
8264
|
|
|
7920
8265
|
#setUpButtons() {
|
|
8266
|
+
this.innerHTML = "";
|
|
8267
|
+
|
|
7921
8268
|
this.appendChild(this.#createRowButtonsContainer());
|
|
7922
8269
|
this.appendChild(this.#createColumnButtonsContainer());
|
|
7923
8270
|
|
|
@@ -8211,9 +8558,107 @@ function defineElements() {
|
|
|
8211
8558
|
});
|
|
8212
8559
|
}
|
|
8213
8560
|
|
|
8561
|
+
class NativeAdapter {
|
|
8562
|
+
frozenLinkKey = null
|
|
8563
|
+
|
|
8564
|
+
constructor(editorElement) {
|
|
8565
|
+
this.editorElement = editorElement;
|
|
8566
|
+
this.editorContentElement = editorElement.editorContentElement;
|
|
8567
|
+
}
|
|
8568
|
+
|
|
8569
|
+
dispatchAttributesChange(attributes, linkHref, highlight, headingTag) {
|
|
8570
|
+
dispatch(this.editorElement, "lexxy:attributes-change", {
|
|
8571
|
+
attributes,
|
|
8572
|
+
link: linkHref ? { href: linkHref } : null,
|
|
8573
|
+
highlight,
|
|
8574
|
+
headingTag
|
|
8575
|
+
});
|
|
8576
|
+
}
|
|
8577
|
+
|
|
8578
|
+
dispatchEditorInitialized(detail) {
|
|
8579
|
+
dispatch(this.editorElement, "lexxy:editor-initialized", detail);
|
|
8580
|
+
}
|
|
8581
|
+
|
|
8582
|
+
freeze() {
|
|
8583
|
+
let frozenLinkKey = null;
|
|
8584
|
+
this.editorElement.editor?.getEditorState().read(() => {
|
|
8585
|
+
const selection = $getSelection();
|
|
8586
|
+
if (!$isRangeSelection(selection)) return
|
|
8587
|
+
|
|
8588
|
+
const linkNode = $getNearestNodeOfType(selection.anchor.getNode(), LinkNode);
|
|
8589
|
+
if (linkNode) {
|
|
8590
|
+
frozenLinkKey = linkNode.getKey();
|
|
8591
|
+
}
|
|
8592
|
+
});
|
|
8593
|
+
|
|
8594
|
+
this.frozenLinkKey = frozenLinkKey;
|
|
8595
|
+
this.editorContentElement.contentEditable = "false";
|
|
8596
|
+
}
|
|
8597
|
+
|
|
8598
|
+
thaw() {
|
|
8599
|
+
this.editorContentElement.contentEditable = "true";
|
|
8600
|
+
}
|
|
8601
|
+
|
|
8602
|
+
unlinkFrozenNode() {
|
|
8603
|
+
const key = this.frozenLinkKey;
|
|
8604
|
+
if (!key) return false
|
|
8605
|
+
|
|
8606
|
+
const linkNode = $getNodeByKey(key);
|
|
8607
|
+
if (!$isLinkNode(linkNode)) {
|
|
8608
|
+
this.frozenLinkKey = null;
|
|
8609
|
+
return false
|
|
8610
|
+
}
|
|
8611
|
+
|
|
8612
|
+
const children = linkNode.getChildren();
|
|
8613
|
+
for (const child of children) {
|
|
8614
|
+
linkNode.insertBefore(child);
|
|
8615
|
+
}
|
|
8616
|
+
linkNode.remove();
|
|
8617
|
+
|
|
8618
|
+
// Select the former link text so a follow-up createLink can re-wrap it.
|
|
8619
|
+
const firstText = this.#findFirstTextDescendant(children);
|
|
8620
|
+
const lastText = this.#findLastTextDescendant(children);
|
|
8621
|
+
if (firstText && lastText) {
|
|
8622
|
+
const selection = $getSelection();
|
|
8623
|
+
if ($isRangeSelection(selection)) {
|
|
8624
|
+
selection.anchor.set(firstText.getKey(), 0, "text");
|
|
8625
|
+
selection.focus.set(lastText.getKey(), lastText.getTextContent().length, "text");
|
|
8626
|
+
}
|
|
8627
|
+
}
|
|
8628
|
+
|
|
8629
|
+
this.frozenLinkKey = null;
|
|
8630
|
+
return true
|
|
8631
|
+
}
|
|
8632
|
+
|
|
8633
|
+
#findFirstTextDescendant(nodes) {
|
|
8634
|
+
for (const node of nodes) {
|
|
8635
|
+
if ($isTextNode(node)) return node
|
|
8636
|
+
if ($isElementNode(node)) {
|
|
8637
|
+
const nestedTextNode = this.#findFirstTextDescendant(node.getChildren());
|
|
8638
|
+
if (nestedTextNode) return nestedTextNode
|
|
8639
|
+
}
|
|
8640
|
+
}
|
|
8641
|
+
|
|
8642
|
+
return null
|
|
8643
|
+
}
|
|
8644
|
+
|
|
8645
|
+
#findLastTextDescendant(nodes) {
|
|
8646
|
+
for (let index = nodes.length - 1; index >= 0; index--) {
|
|
8647
|
+
const node = nodes[index];
|
|
8648
|
+
if ($isTextNode(node)) return node
|
|
8649
|
+
if ($isElementNode(node)) {
|
|
8650
|
+
const nestedTextNode = this.#findLastTextDescendant(node.getChildren());
|
|
8651
|
+
if (nestedTextNode) return nestedTextNode
|
|
8652
|
+
}
|
|
8653
|
+
}
|
|
8654
|
+
|
|
8655
|
+
return null
|
|
8656
|
+
}
|
|
8657
|
+
}
|
|
8658
|
+
|
|
8214
8659
|
const configure = Lexxy.configure;
|
|
8215
8660
|
|
|
8216
8661
|
// Pushing elements definition to after the current call stack to allow global configuration to take place first
|
|
8217
8662
|
setTimeout(defineElements, 0);
|
|
8218
8663
|
|
|
8219
|
-
export { $createActionTextAttachmentNode, $createActionTextAttachmentUploadNode, $isActionTextAttachmentNode, ActionTextAttachmentNode, ActionTextAttachmentUploadNode, CustomActionTextAttachmentNode, LexxyExtension as Extension, HorizontalDividerNode, configure };
|
|
8664
|
+
export { $createActionTextAttachmentNode, $createActionTextAttachmentUploadNode, $isActionTextAttachmentNode, ActionTextAttachmentNode, ActionTextAttachmentUploadNode, CustomActionTextAttachmentNode, LexxyExtension as Extension, HorizontalDividerNode, NativeAdapter, configure };
|