@37signals/lexxy 0.9.9-beta-preview1 → 0.9.9-beta.preview3.domselection
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 +490 -400
- package/dist/stylesheets/lexxy-editor.css +4 -0
- package/package.json +15 -15
package/dist/lexxy.esm.js
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { isActiveAndVisible, createElement, extractPlainTextFromHtml, createAttachmentFigure, isPreviewableImage, dispatch, parseHtml, addBlockSpacing, generateDomId } from './lexxy_helpers.esm.js';
|
|
2
2
|
export { highlightCode } from './lexxy_helpers.esm.js';
|
|
3
3
|
import DOMPurify from 'dompurify';
|
|
4
|
-
import { getStyleObjectFromCSS, getCSSFromStyleObject, $isAtNodeEnd, $getSelectionStyleValueForProperty, $patchStyleText, $
|
|
5
|
-
import { SKIP_DOM_SELECTION_TAG, $getSelection, $isRangeSelection, DecoratorNode, $createParagraphNode, $getNodeByKey, $isTextNode, $createRangeSelection, $setSelection, $createTextNode,
|
|
4
|
+
import { getStyleObjectFromCSS, getCSSFromStyleObject, $isAtNodeEnd, $getSelectionStyleValueForProperty, $patchStyleText, $ensureForwardRangeSelection, $setBlocksType, $forEachSelectedTextNode } from '@lexical/selection';
|
|
5
|
+
import { SKIP_DOM_SELECTION_TAG, CAN_UNDO_COMMAND, COMMAND_PRIORITY_LOW, CAN_REDO_COMMAND, $getSelection, $isRangeSelection, DecoratorNode, $createParagraphNode, $getNodeByKey, $isTextNode, $createRangeSelection, $setSelection, $createTextNode, $isElementNode, $isRootOrShadowRoot, $isRootNode, $createNodeSelection, $isDecoratorNode, $isLineBreakNode, TextNode, createCommand, createState, defineExtension, COMMAND_PRIORITY_NORMAL, $getState, $setState, $hasUpdateTag, PASTE_TAG, FORMAT_TEXT_COMMAND, UNDO_COMMAND, REDO_COMMAND, PASTE_COMMAND, KEY_ARROW_RIGHT_COMMAND, KEY_TAB_COMMAND, OUTDENT_CONTENT_COMMAND, INDENT_CONTENT_COMMAND, COMMAND_PRIORITY_EDITOR, $getEditor, HISTORY_MERGE_TAG, SKIP_SCROLL_INTO_VIEW_TAG, $cloneWithProperties, $getNearestRootOrShadowRoot, $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, $splitNode, $getChildCaretAtIndex, $createLineBreakNode, $isParagraphNode, ParagraphNode, RootNode, COMMAND_PRIORITY_HIGH, DRAGSTART_COMMAND, DROP_COMMAND, INSERT_PARAGRAPH_COMMAND, mergeRegister as mergeRegister$1, $findMatchingParent, CLEAR_HISTORY_COMMAND, KEY_ENTER_COMMAND, COMMAND_PRIORITY_CRITICAL, KEY_SPACE_COMMAND, KEY_BACKSPACE_COMMAND, KEY_DOWN_COMMAND } from 'lexical';
|
|
6
6
|
import { buildEditorFromExtensions } from '@lexical/extension';
|
|
7
7
|
import { ListNode, ListItemNode, $getListDepth, INSERT_UNORDERED_LIST_COMMAND, INSERT_ORDERED_LIST_COMMAND, $isListItemNode, $isListNode, registerList } from '@lexical/list';
|
|
8
|
-
import { LinkNode, $createAutoLinkNode, $toggleLink, $createLinkNode,
|
|
8
|
+
import { LinkNode, $createAutoLinkNode, $toggleLink, $createLinkNode, $isLinkNode, AutoLinkNode } from '@lexical/link';
|
|
9
9
|
import { $getNearestNodeOfType, $wrapNodeInElement, $lastToFirstIterator, mergeRegister, $insertFirst, $unwrapAndFilterDescendants, $firstToLastIterator, $getNearestBlockElementAncestorOrThrow, $descendantsMatching, IS_APPLE } from '@lexical/utils';
|
|
10
10
|
import { registerPlainText } from '@lexical/plain-text';
|
|
11
|
-
import { RichTextExtension, $isQuoteNode, $isHeadingNode, $createHeadingNode, $createQuoteNode,
|
|
11
|
+
import { RichTextExtension, $isQuoteNode, $isHeadingNode, QuoteNode, $createHeadingNode, $createQuoteNode, HeadingNode, registerRichText } from '@lexical/rich-text';
|
|
12
12
|
import { $generateNodesFromDOM, $generateHtmlFromNodes } from '@lexical/html';
|
|
13
13
|
import { $isCodeNode, CodeHighlightNode, CodeNode, $createCodeNode, $isCodeHighlightNode, $createCodeHighlightNode, normalizeCodeLang, registerCodeHighlighting, CODE_LANGUAGE_FRIENDLY_NAME_MAP } from '@lexical/code';
|
|
14
14
|
import { TRANSFORMERS, registerMarkdownShortcuts } from '@lexical/markdown';
|
|
15
|
-
import { createEmptyHistoryState, registerHistory } from '@lexical/history';
|
|
16
15
|
import { INSERT_TABLE_COMMAND, $getTableCellNodeFromLexicalNode, TableCellNode, TableNode, TableRowNode, setScrollableTablesActive, registerTablePlugin, registerTableSelectionObserver, TableCellHeaderStates, $insertTableRowAtSelection, $insertTableColumnAtSelection, $deleteTableRowAtSelection, $deleteTableColumnAtSelection, $findTableNode, $getTableRowIndexFromTableCellNode, $getTableColumnIndexFromTableCellNode, $findCellNode, $getElementForTableNode } from '@lexical/table';
|
|
16
|
+
import { HistoryExtension } from '@lexical/history';
|
|
17
17
|
import { marked } from 'marked';
|
|
18
18
|
import { $insertDataTransferForRichText } from '@lexical/clipboard';
|
|
19
19
|
import 'prismjs';
|
|
@@ -511,19 +511,10 @@ class LexicalToolbarElement extends HTMLElement {
|
|
|
511
511
|
}
|
|
512
512
|
|
|
513
513
|
#monitorHistoryChanges() {
|
|
514
|
-
this.#listeners.track(
|
|
515
|
-
this.#
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
#updateUndoRedoButtonStates() {
|
|
520
|
-
this.editor.getEditorState().read(() => {
|
|
521
|
-
const historyState = this.editorElement.historyState;
|
|
522
|
-
if (historyState) {
|
|
523
|
-
this.#setButtonDisabled("undo", historyState.undoStack.length === 0);
|
|
524
|
-
this.#setButtonDisabled("redo", historyState.redoStack.length === 0);
|
|
525
|
-
}
|
|
526
|
-
});
|
|
514
|
+
this.#listeners.track(
|
|
515
|
+
this.editor.registerCommand(CAN_UNDO_COMMAND, (enabled) => { this.#setButtonDisabled("undo", !enabled); }, COMMAND_PRIORITY_LOW),
|
|
516
|
+
this.editor.registerCommand(CAN_REDO_COMMAND, (enabled) => { this.#setButtonDisabled("redo", !enabled); }, COMMAND_PRIORITY_LOW),
|
|
517
|
+
);
|
|
527
518
|
}
|
|
528
519
|
|
|
529
520
|
#updateButtonStates() {
|
|
@@ -557,8 +548,6 @@ class LexicalToolbarElement extends HTMLElement {
|
|
|
557
548
|
this.#setButtonPressed("code", isInCode);
|
|
558
549
|
|
|
559
550
|
this.#setButtonPressed("table", isInTable);
|
|
560
|
-
|
|
561
|
-
this.#updateUndoRedoButtonStates();
|
|
562
551
|
}
|
|
563
552
|
|
|
564
553
|
#setButtonPressed(name, isPressed) {
|
|
@@ -769,11 +758,11 @@ class LexicalToolbarElement extends HTMLElement {
|
|
|
769
758
|
|
|
770
759
|
<div class="lexxy-editor__toolbar-spacer" role="separator"></div>
|
|
771
760
|
|
|
772
|
-
<button class="lexxy-editor__toolbar-button" type="button" name="undo" data-command="undo" title="Undo">
|
|
761
|
+
<button class="lexxy-editor__toolbar-button" type="button" name="undo" data-command="undo" title="Undo" disabled aria-disabled="true">
|
|
773
762
|
${ToolbarIcons.undo}
|
|
774
763
|
</button>
|
|
775
764
|
|
|
776
|
-
<button class="lexxy-editor__toolbar-button" type="button" name="redo" data-command="redo" title="Redo">
|
|
765
|
+
<button class="lexxy-editor__toolbar-button" type="button" name="redo" data-command="redo" title="Redo" disabled aria-disabled="true">
|
|
777
766
|
${ToolbarIcons.redo}
|
|
778
767
|
</button>
|
|
779
768
|
|
|
@@ -1332,14 +1321,16 @@ class CustomActionTextAttachmentNode extends DecoratorNode {
|
|
|
1332
1321
|
}
|
|
1333
1322
|
}
|
|
1334
1323
|
|
|
1335
|
-
const SILENT_UPDATE_TAGS = [ HISTORY_MERGE_TAG, SKIP_SCROLL_INTO_VIEW_TAG ];
|
|
1336
|
-
|
|
1337
1324
|
function $createNodeSelectionWith(...nodes) {
|
|
1338
1325
|
const selection = $createNodeSelection();
|
|
1339
1326
|
nodes.forEach(node => selection.add(node.getKey()));
|
|
1340
1327
|
return selection
|
|
1341
1328
|
}
|
|
1342
1329
|
|
|
1330
|
+
function $isShadowRoot(node) {
|
|
1331
|
+
return $isElementNode(node) && $isRootOrShadowRoot(node) && !$isRootNode(node)
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1343
1334
|
function $makeSafeForRoot(node) {
|
|
1344
1335
|
if ($isTextNode(node)) {
|
|
1345
1336
|
return $wrapNodeInElement(node, $createParagraphNode)
|
|
@@ -1356,6 +1347,11 @@ function getListType(node) {
|
|
|
1356
1347
|
return list?.getListType() ?? null
|
|
1357
1348
|
}
|
|
1358
1349
|
|
|
1350
|
+
function isEditorFocused(editor) {
|
|
1351
|
+
const rootElement = editor.getRootElement();
|
|
1352
|
+
return rootElement !== null && rootElement.contains(document.activeElement)
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1359
1355
|
function $isAtNodeEdge(point, atStart = null) {
|
|
1360
1356
|
if (atStart === null) {
|
|
1361
1357
|
return $isAtNodeEdge(point, true) || $isAtNodeEdge(point, false)
|
|
@@ -2571,6 +2567,10 @@ function debounceAsync(fn, wait) {
|
|
|
2571
2567
|
}
|
|
2572
2568
|
}
|
|
2573
2569
|
|
|
2570
|
+
function delay(ms) {
|
|
2571
|
+
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
2572
|
+
}
|
|
2573
|
+
|
|
2574
2574
|
function nextFrame() {
|
|
2575
2575
|
return new Promise(requestAnimationFrame)
|
|
2576
2576
|
}
|
|
@@ -2619,6 +2619,124 @@ function parseBoolean(value) {
|
|
|
2619
2619
|
return Boolean(value)
|
|
2620
2620
|
}
|
|
2621
2621
|
|
|
2622
|
+
// Payload: Record<nodeKey, { patch?, replace? }>
|
|
2623
|
+
// - patch: plain object, shallow-merged into the existing node's properties
|
|
2624
|
+
// - replace: a LexicalNode instance that replaces the node
|
|
2625
|
+
const REWRITE_HISTORY_COMMAND = createCommand("REWRITE_HISTORY_COMMAND");
|
|
2626
|
+
|
|
2627
|
+
class RewritableHistoryExtension extends LexxyExtension {
|
|
2628
|
+
#historyState = null
|
|
2629
|
+
|
|
2630
|
+
get lexicalExtension() {
|
|
2631
|
+
return defineExtension({
|
|
2632
|
+
name: "lexxy/rewritable-history",
|
|
2633
|
+
dependencies: [ HistoryExtension ],
|
|
2634
|
+
register: (editor, _config, state) => {
|
|
2635
|
+
const historyOutput = state.getDependency(HistoryExtension).output;
|
|
2636
|
+
this.#historyState = historyOutput.historyState.value;
|
|
2637
|
+
|
|
2638
|
+
return editor.registerCommand(
|
|
2639
|
+
REWRITE_HISTORY_COMMAND,
|
|
2640
|
+
(rewrites) => this.#rewriteHistory(rewrites),
|
|
2641
|
+
COMMAND_PRIORITY_EDITOR
|
|
2642
|
+
)
|
|
2643
|
+
}
|
|
2644
|
+
})
|
|
2645
|
+
}
|
|
2646
|
+
|
|
2647
|
+
get historyState() {
|
|
2648
|
+
return this.#historyState
|
|
2649
|
+
}
|
|
2650
|
+
|
|
2651
|
+
get #allHistoryEntries() {
|
|
2652
|
+
const entries = Array.from(this.#historyState.undoStack);
|
|
2653
|
+
if (this.#historyState.current) entries.push(this.#historyState.current);
|
|
2654
|
+
return entries.concat(this.#historyState.redoStack)
|
|
2655
|
+
}
|
|
2656
|
+
|
|
2657
|
+
#rewriteHistory(rewrites) {
|
|
2658
|
+
this.#applyRewritesImmediatelyToCurrentState(rewrites);
|
|
2659
|
+
this.#applyRewritesToHistory(rewrites);
|
|
2660
|
+
|
|
2661
|
+
return true
|
|
2662
|
+
}
|
|
2663
|
+
|
|
2664
|
+
#applyRewritesImmediatelyToCurrentState(rewrites) {
|
|
2665
|
+
$getEditor().update(() => {
|
|
2666
|
+
for (const [ nodeKey, { patch, replace } ] of Object.entries(rewrites)) {
|
|
2667
|
+
const node = $getNodeByKey(nodeKey);
|
|
2668
|
+
if (!node) continue
|
|
2669
|
+
|
|
2670
|
+
if (patch) Object.assign(node.getWritable(), patch);
|
|
2671
|
+
if (replace) node.replace(replace);
|
|
2672
|
+
}
|
|
2673
|
+
}, { discrete: true, tag: this.#getBackgroundUpdateTags() });
|
|
2674
|
+
}
|
|
2675
|
+
|
|
2676
|
+
#applyRewritesToHistory(rewrites) {
|
|
2677
|
+
const nodeKeys = Object.keys(rewrites);
|
|
2678
|
+
|
|
2679
|
+
for (const entry of this.#allHistoryEntries) {
|
|
2680
|
+
if (!this.#entryHasSomeKeys(entry, nodeKeys)) continue
|
|
2681
|
+
|
|
2682
|
+
const editorState = entry.editorState = safeCloneEditorState(entry.editorState);
|
|
2683
|
+
|
|
2684
|
+
for (const [ nodeKey, { patch, replace } ] of Object.entries(rewrites)) {
|
|
2685
|
+
const node = editorState._nodeMap.get(nodeKey);
|
|
2686
|
+
if (!node) continue
|
|
2687
|
+
|
|
2688
|
+
if (patch) {
|
|
2689
|
+
this.#patchNodeInEditorState(editorState, node, patch);
|
|
2690
|
+
} else if (replace) {
|
|
2691
|
+
this.#replaceNodeInEditorState(editorState, node, replace);
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2695
|
+
}
|
|
2696
|
+
|
|
2697
|
+
#entryHasSomeKeys(entry, nodeKeys) {
|
|
2698
|
+
return nodeKeys.some(key => entry.editorState._nodeMap.has(key))
|
|
2699
|
+
}
|
|
2700
|
+
|
|
2701
|
+
#getBackgroundUpdateTags() {
|
|
2702
|
+
const tags = [ HISTORY_MERGE_TAG, SKIP_SCROLL_INTO_VIEW_TAG ];
|
|
2703
|
+
if (!isEditorFocused(this.editorElement.editor)) { tags.push(SKIP_DOM_SELECTION_TAG); }
|
|
2704
|
+
return tags
|
|
2705
|
+
}
|
|
2706
|
+
|
|
2707
|
+
#patchNodeInEditorState(editorState, node, patch) {
|
|
2708
|
+
editorState._nodeMap.set(node.__key, $cloneNodeWithPatch(node, patch));
|
|
2709
|
+
}
|
|
2710
|
+
|
|
2711
|
+
#replaceNodeInEditorState(editorState, node, replaceWith) {
|
|
2712
|
+
editorState._nodeMap.set(node.__key, $cloneNodeAdoptingKeys(replaceWith, node));
|
|
2713
|
+
}
|
|
2714
|
+
}
|
|
2715
|
+
|
|
2716
|
+
function $cloneNodeWithPatch(node, patch) {
|
|
2717
|
+
const clone = $cloneWithProperties(node);
|
|
2718
|
+
Object.assign(clone, patch);
|
|
2719
|
+
return clone
|
|
2720
|
+
}
|
|
2721
|
+
|
|
2722
|
+
function $cloneNodeAdoptingKeys(node, previousNode) {
|
|
2723
|
+
const clone = $cloneWithProperties(node);
|
|
2724
|
+
clone.__key = previousNode.__key;
|
|
2725
|
+
clone.__parent = previousNode.__parent;
|
|
2726
|
+
clone.__prev = previousNode.__prev;
|
|
2727
|
+
clone.__next = previousNode.__next;
|
|
2728
|
+
return clone
|
|
2729
|
+
}
|
|
2730
|
+
|
|
2731
|
+
// EditorState#clone() keeps the same map reference.
|
|
2732
|
+
// A new Map is needed to prevent editing Lexical's internal map
|
|
2733
|
+
// Warning: this bypasses DEV's safety map freezing
|
|
2734
|
+
function safeCloneEditorState(editorState) {
|
|
2735
|
+
const clone = editorState.clone();
|
|
2736
|
+
clone._nodeMap = new Map(editorState._nodeMap);
|
|
2737
|
+
return clone
|
|
2738
|
+
}
|
|
2739
|
+
|
|
2622
2740
|
class ActionTextAttachmentNode extends DecoratorNode {
|
|
2623
2741
|
static getType() {
|
|
2624
2742
|
return "action_text_attachment"
|
|
@@ -2831,6 +2949,18 @@ class ActionTextAttachmentNode extends DecoratorNode {
|
|
|
2831
2949
|
return figure
|
|
2832
2950
|
}
|
|
2833
2951
|
|
|
2952
|
+
patchAndRewriteHistory(patch) {
|
|
2953
|
+
this.editor.dispatchCommand(REWRITE_HISTORY_COMMAND, {
|
|
2954
|
+
[this.getKey()]: { patch }
|
|
2955
|
+
});
|
|
2956
|
+
}
|
|
2957
|
+
|
|
2958
|
+
replaceAndRewriteHistory(node) {
|
|
2959
|
+
this.editor.dispatchCommand(REWRITE_HISTORY_COMMAND, {
|
|
2960
|
+
[this.getKey()]: { replace: node }
|
|
2961
|
+
});
|
|
2962
|
+
}
|
|
2963
|
+
|
|
2834
2964
|
#createDOMForImage(options = {}) {
|
|
2835
2965
|
const initialSrc = this.previewSrc || this.src;
|
|
2836
2966
|
const img = createElement("img", { src: initialSrc, draggable: false, alt: this.altText, ...this.#imageDimensions, ...options });
|
|
@@ -2859,33 +2989,18 @@ class ActionTextAttachmentNode extends DecoratorNode {
|
|
|
2859
2989
|
|
|
2860
2990
|
#handleImageLoaded(img, previewSrc) {
|
|
2861
2991
|
img.src = this.src;
|
|
2862
|
-
this.
|
|
2863
|
-
if (this.isAttached()) this.getWritable().previewSrc = null;
|
|
2864
|
-
}, { tag: this.#backgroundUpdateTags });
|
|
2992
|
+
this.patchAndRewriteHistory({ previewSrc: null });
|
|
2865
2993
|
this.#revokePreviewSrc(previewSrc);
|
|
2866
2994
|
}
|
|
2867
2995
|
|
|
2868
2996
|
#handleImageLoadError(previewSrc) {
|
|
2869
|
-
this.
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
}
|
|
2874
|
-
}, { tag: this.#backgroundUpdateTags });
|
|
2997
|
+
this.patchAndRewriteHistory({
|
|
2998
|
+
previewSrc: null,
|
|
2999
|
+
uploadError: true
|
|
3000
|
+
});
|
|
2875
3001
|
this.#revokePreviewSrc(previewSrc);
|
|
2876
3002
|
}
|
|
2877
3003
|
|
|
2878
|
-
get #backgroundUpdateTags() {
|
|
2879
|
-
const rootElement = this.editor.getRootElement();
|
|
2880
|
-
const editorHasFocus = rootElement !== null && rootElement.contains(document.activeElement);
|
|
2881
|
-
|
|
2882
|
-
if (editorHasFocus) {
|
|
2883
|
-
return SILENT_UPDATE_TAGS
|
|
2884
|
-
} else {
|
|
2885
|
-
return [ ...SILENT_UPDATE_TAGS, SKIP_DOM_SELECTION_TAG ]
|
|
2886
|
-
}
|
|
2887
|
-
}
|
|
2888
|
-
|
|
2889
3004
|
#revokePreviewSrc(previewSrc) {
|
|
2890
3005
|
if (previewSrc?.startsWith("blob:")) URL.revokeObjectURL(previewSrc);
|
|
2891
3006
|
}
|
|
@@ -2947,9 +3062,7 @@ class ActionTextAttachmentNode extends DecoratorNode {
|
|
|
2947
3062
|
figure.appendChild(this.#createEditableCaption());
|
|
2948
3063
|
});
|
|
2949
3064
|
|
|
2950
|
-
this.
|
|
2951
|
-
if (this.isAttached()) this.getWritable().pendingPreview = false;
|
|
2952
|
-
}, { tag: this.#backgroundUpdateTags });
|
|
3065
|
+
this.patchAndRewriteHistory({ pendingPreview: false });
|
|
2953
3066
|
}
|
|
2954
3067
|
|
|
2955
3068
|
#swapFigureContent(figure, fromClass, toClass, renderContent) {
|
|
@@ -3064,12 +3177,6 @@ class Selection {
|
|
|
3064
3177
|
this.#clearStaleInlineCodeFormat();
|
|
3065
3178
|
}
|
|
3066
3179
|
|
|
3067
|
-
set current(selection) {
|
|
3068
|
-
this.editor.update(() => {
|
|
3069
|
-
this.#syncSelectedClasses();
|
|
3070
|
-
});
|
|
3071
|
-
}
|
|
3072
|
-
|
|
3073
3180
|
get hasNodeSelection() {
|
|
3074
3181
|
return this.editor.getEditorState().read(() => {
|
|
3075
3182
|
const selection = $getSelection();
|
|
@@ -3398,7 +3505,7 @@ class Selection {
|
|
|
3398
3505
|
this.editor.registerCommand(DELETE_CHARACTER_COMMAND, this.#selectDecoratorNodeBeforeDeletion.bind(this), COMMAND_PRIORITY_LOW),
|
|
3399
3506
|
|
|
3400
3507
|
this.editor.registerCommand(SELECTION_CHANGE_COMMAND, () => {
|
|
3401
|
-
this
|
|
3508
|
+
this.#syncSelectedClasses();
|
|
3402
3509
|
}, COMMAND_PRIORITY_LOW)
|
|
3403
3510
|
);
|
|
3404
3511
|
}
|
|
@@ -3596,6 +3703,7 @@ class Selection {
|
|
|
3596
3703
|
|
|
3597
3704
|
#selectInLexical(node) {
|
|
3598
3705
|
if ($isDecoratorNode(node)) {
|
|
3706
|
+
$addUpdateTag(HISTORY_MERGE_TAG);
|
|
3599
3707
|
const selection = $createNodeSelectionWith(node);
|
|
3600
3708
|
$setSelection(selection);
|
|
3601
3709
|
return selection
|
|
@@ -3961,107 +4069,6 @@ class EditorConfiguration {
|
|
|
3961
4069
|
}
|
|
3962
4070
|
}
|
|
3963
4071
|
|
|
3964
|
-
class ProvisionalParagraphNode extends ParagraphNode {
|
|
3965
|
-
$config() {
|
|
3966
|
-
return this.config("provisonal_paragraph", {
|
|
3967
|
-
extends: ParagraphNode,
|
|
3968
|
-
importDOM: () => null,
|
|
3969
|
-
$transform: (node) => {
|
|
3970
|
-
node.concretizeIfEdited(node);
|
|
3971
|
-
node.removeUnlessRequired(node);
|
|
3972
|
-
}
|
|
3973
|
-
})
|
|
3974
|
-
}
|
|
3975
|
-
|
|
3976
|
-
static neededBetween(nodeBefore, nodeAfter) {
|
|
3977
|
-
return !$isSelectableElement(nodeBefore, "next")
|
|
3978
|
-
&& !$isSelectableElement(nodeAfter, "previous")
|
|
3979
|
-
}
|
|
3980
|
-
|
|
3981
|
-
createDOM(editor) {
|
|
3982
|
-
const p = super.createDOM(editor);
|
|
3983
|
-
const selected = this.isSelected($getSelection());
|
|
3984
|
-
p.classList.add("provisional-paragraph");
|
|
3985
|
-
p.classList.toggle("hidden", !selected);
|
|
3986
|
-
return p
|
|
3987
|
-
}
|
|
3988
|
-
|
|
3989
|
-
updateDOM(_prevNode, dom) {
|
|
3990
|
-
const selected = this.isSelected($getSelection());
|
|
3991
|
-
dom.classList.toggle("hidden", !selected);
|
|
3992
|
-
return false
|
|
3993
|
-
}
|
|
3994
|
-
|
|
3995
|
-
getTextContent() {
|
|
3996
|
-
return ""
|
|
3997
|
-
}
|
|
3998
|
-
|
|
3999
|
-
exportDOM() {
|
|
4000
|
-
return {
|
|
4001
|
-
element: null
|
|
4002
|
-
}
|
|
4003
|
-
}
|
|
4004
|
-
|
|
4005
|
-
// override as Lexical has an interesting view of collapsed selection in ElementNodes
|
|
4006
|
-
// https://github.com/facebook/lexical/blob/f1e4f66014377b1f2595aec2b0ee17f5b7ef4dfc/packages/lexical/src/LexicalNode.ts#L646
|
|
4007
|
-
isSelected(selection = null) {
|
|
4008
|
-
const targetSelection = selection || $getSelection();
|
|
4009
|
-
if (!targetSelection) return false
|
|
4010
|
-
|
|
4011
|
-
if (targetSelection.getNodes().some(node => node.is(this) || this.isParentOf(node))) return true
|
|
4012
|
-
|
|
4013
|
-
// A collapsed range selection on the parent element at an offset adjacent to
|
|
4014
|
-
// this node means the caret is visually at this paragraph's position. Treat it
|
|
4015
|
-
// as selected so the paragraph is visible and the caret renders correctly.
|
|
4016
|
-
//
|
|
4017
|
-
// Both the offset matching our index (cursor just before us) and index + 1
|
|
4018
|
-
// (cursor just after us) count, because the provisional paragraph is an
|
|
4019
|
-
// invisible spacer: the browser resolves both offsets to the same visual spot.
|
|
4020
|
-
if ($isRangeSelection(targetSelection) && targetSelection.isCollapsed()) {
|
|
4021
|
-
const { anchor } = targetSelection;
|
|
4022
|
-
const parent = this.getParent();
|
|
4023
|
-
if (parent && anchor.getNode().is(parent) && anchor.type === "element") {
|
|
4024
|
-
const index = this.getIndexWithinParent();
|
|
4025
|
-
return anchor.offset === index || anchor.offset === index + 1
|
|
4026
|
-
}
|
|
4027
|
-
}
|
|
4028
|
-
|
|
4029
|
-
return false
|
|
4030
|
-
}
|
|
4031
|
-
|
|
4032
|
-
removeUnlessRequired(self = this.getLatest()) {
|
|
4033
|
-
if (!self.required) self.remove();
|
|
4034
|
-
}
|
|
4035
|
-
|
|
4036
|
-
concretizeIfEdited(self = this.getLatest()) {
|
|
4037
|
-
if (self.getTextContentSize() > 0) {
|
|
4038
|
-
self.replace($createParagraphNode(), true);
|
|
4039
|
-
}
|
|
4040
|
-
}
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
get required() {
|
|
4044
|
-
return this.isDirectRootChild && ProvisionalParagraphNode.neededBetween(...this.immediateSiblings)
|
|
4045
|
-
}
|
|
4046
|
-
|
|
4047
|
-
get isDirectRootChild() {
|
|
4048
|
-
const parent = this.getParent();
|
|
4049
|
-
return $isRootOrShadowRoot(parent)
|
|
4050
|
-
}
|
|
4051
|
-
|
|
4052
|
-
get immediateSiblings() {
|
|
4053
|
-
return [ this.getPreviousSibling(), this.getNextSibling() ]
|
|
4054
|
-
}
|
|
4055
|
-
}
|
|
4056
|
-
|
|
4057
|
-
function $isProvisionalParagraphNode(node) {
|
|
4058
|
-
return node instanceof ProvisionalParagraphNode
|
|
4059
|
-
}
|
|
4060
|
-
|
|
4061
|
-
function $isSelectableElement(node, direction) {
|
|
4062
|
-
return $isElementNode(node) && (direction === "next" ? node.canInsertTextBefore() : node.canInsertTextAfter())
|
|
4063
|
-
}
|
|
4064
|
-
|
|
4065
4072
|
async function loadFileIntoImage(file, image) {
|
|
4066
4073
|
return new Promise((resolve) => {
|
|
4067
4074
|
const reader = new FileReader();
|
|
@@ -4097,10 +4104,10 @@ class ActionTextAttachmentUploadNode extends ActionTextAttachmentNode {
|
|
|
4097
4104
|
}
|
|
4098
4105
|
|
|
4099
4106
|
constructor(node, key) {
|
|
4100
|
-
const { file, uploadUrl, blobUrlTemplate, progress, width, height, uploadError } = node;
|
|
4101
|
-
super({ ...node, contentType: file
|
|
4102
|
-
this.file = file;
|
|
4103
|
-
this.fileName = file
|
|
4107
|
+
const { file, uploadUrl, blobUrlTemplate, progress, width, height, uploadError, fileName, contentType } = node;
|
|
4108
|
+
super({ ...node, contentType: file?.type ?? contentType }, key);
|
|
4109
|
+
this.file = file ?? null;
|
|
4110
|
+
this.fileName = file?.name ?? fileName;
|
|
4104
4111
|
this.uploadUrl = uploadUrl;
|
|
4105
4112
|
this.blobUrlTemplate = blobUrlTemplate;
|
|
4106
4113
|
this.progress = progress ?? null;
|
|
@@ -4157,6 +4164,8 @@ class ActionTextAttachmentUploadNode extends ActionTextAttachmentNode {
|
|
|
4157
4164
|
...super.exportJSON(),
|
|
4158
4165
|
type: "action_text_attachment_upload",
|
|
4159
4166
|
version: 1,
|
|
4167
|
+
fileName: this.fileName,
|
|
4168
|
+
contentType: this.contentType,
|
|
4160
4169
|
uploadUrl: this.uploadUrl,
|
|
4161
4170
|
blobUrlTemplate: this.blobUrlTemplate,
|
|
4162
4171
|
progress: this.progress,
|
|
@@ -4181,14 +4190,14 @@ class ActionTextAttachmentUploadNode extends ActionTextAttachmentNode {
|
|
|
4181
4190
|
}
|
|
4182
4191
|
|
|
4183
4192
|
#getFileExtension() {
|
|
4184
|
-
return this.
|
|
4193
|
+
return (this.fileName || "").split(".").pop().toLowerCase()
|
|
4185
4194
|
}
|
|
4186
4195
|
|
|
4187
4196
|
#createCaption() {
|
|
4188
4197
|
const figcaption = createElement("figcaption", { className: "attachment__caption" });
|
|
4189
4198
|
|
|
4190
|
-
const nameSpan = createElement("span", { className: "attachment__name", textContent: this.caption || this.
|
|
4191
|
-
const sizeSpan = createElement("span", { className: "attachment__size", textContent: bytesToHumanSize(this.file
|
|
4199
|
+
const nameSpan = createElement("span", { className: "attachment__name", textContent: this.caption || this.fileName || "" });
|
|
4200
|
+
const sizeSpan = createElement("span", { className: "attachment__size", textContent: bytesToHumanSize(this.file?.size) });
|
|
4192
4201
|
figcaption.appendChild(nameSpan);
|
|
4193
4202
|
figcaption.appendChild(sizeSpan);
|
|
4194
4203
|
|
|
@@ -4202,11 +4211,7 @@ class ActionTextAttachmentUploadNode extends ActionTextAttachmentNode {
|
|
|
4202
4211
|
#setDimensionsFromImage({ width, height }) {
|
|
4203
4212
|
if (this.#hasDimensions) return
|
|
4204
4213
|
|
|
4205
|
-
this.
|
|
4206
|
-
const writable = this.getWritable();
|
|
4207
|
-
writable.width = width;
|
|
4208
|
-
writable.height = height;
|
|
4209
|
-
}, { tag: this.#backgroundUpdateTags });
|
|
4214
|
+
this.patchAndRewriteHistory({ width, height });
|
|
4210
4215
|
}
|
|
4211
4216
|
|
|
4212
4217
|
get #hasDimensions() {
|
|
@@ -4233,8 +4238,8 @@ class ActionTextAttachmentUploadNode extends ActionTextAttachmentNode {
|
|
|
4233
4238
|
} else {
|
|
4234
4239
|
this.#dispatchEvent("lexxy:upload-end", { file: this.file, error: null });
|
|
4235
4240
|
this.editor.update(() => {
|
|
4236
|
-
this
|
|
4237
|
-
}
|
|
4241
|
+
this.$showUploadedAttachment(blob);
|
|
4242
|
+
});
|
|
4238
4243
|
}
|
|
4239
4244
|
});
|
|
4240
4245
|
}
|
|
@@ -4249,7 +4254,7 @@ class ActionTextAttachmentUploadNode extends ActionTextAttachmentNode {
|
|
|
4249
4254
|
directUploadWillStoreFileWithXHR: (request) => {
|
|
4250
4255
|
if (shouldAuthenticateUploads) request.withCredentials = true;
|
|
4251
4256
|
|
|
4252
|
-
const uploadProgressHandler = (event) => this.#handleUploadProgress(event);
|
|
4257
|
+
const uploadProgressHandler = (event) => this.#handleUploadProgress(event, request);
|
|
4253
4258
|
request.upload.addEventListener("progress", uploadProgressHandler);
|
|
4254
4259
|
}
|
|
4255
4260
|
}
|
|
@@ -4259,70 +4264,35 @@ class ActionTextAttachmentUploadNode extends ActionTextAttachmentNode {
|
|
|
4259
4264
|
this.#setProgress(1);
|
|
4260
4265
|
}
|
|
4261
4266
|
|
|
4262
|
-
#handleUploadProgress(event) {
|
|
4267
|
+
#handleUploadProgress(event, request) {
|
|
4263
4268
|
const progress = Math.round(event.loaded / event.total * 100);
|
|
4264
|
-
|
|
4265
|
-
|
|
4269
|
+
try {
|
|
4270
|
+
this.#setProgress(progress);
|
|
4271
|
+
this.#dispatchEvent("lexxy:upload-progress", { file: this.file, progress });
|
|
4272
|
+
} catch {
|
|
4273
|
+
request.abort();
|
|
4274
|
+
}
|
|
4266
4275
|
}
|
|
4267
4276
|
|
|
4268
4277
|
#setProgress(progress) {
|
|
4269
|
-
this.
|
|
4270
|
-
this.getWritable().progress = progress;
|
|
4271
|
-
}, { tag: this.#backgroundUpdateTags });
|
|
4278
|
+
this.patchAndRewriteHistory({ progress });
|
|
4272
4279
|
}
|
|
4273
4280
|
|
|
4274
4281
|
#handleUploadError(error) {
|
|
4275
4282
|
console.warn(`Upload error for ${this.file?.name ?? "file"}: ${error}`);
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
}, { tag: this.#backgroundUpdateTags });
|
|
4283
|
+
|
|
4284
|
+
this.patchAndRewriteHistory({ uploadError: true });
|
|
4279
4285
|
}
|
|
4280
4286
|
|
|
4281
|
-
showUploadedAttachment(blob) {
|
|
4287
|
+
$showUploadedAttachment(blob) {
|
|
4282
4288
|
const previewSrc = this.isPreviewableImage && this.file ? URL.createObjectURL(this.file) : null;
|
|
4283
4289
|
|
|
4284
4290
|
const replacementNode = this.#toActionTextAttachmentNodeWith(blob, previewSrc);
|
|
4285
|
-
|
|
4286
|
-
this.replace(replacementNode);
|
|
4287
|
-
|
|
4288
|
-
if (shouldSelectAfterReplacement && $isRootOrShadowRoot(replacementNode.getParent())) {
|
|
4289
|
-
replacementNode.selectNext();
|
|
4290
|
-
}
|
|
4291
|
+
this.replaceAndRewriteHistory(replacementNode);
|
|
4291
4292
|
|
|
4292
4293
|
return replacementNode.getKey()
|
|
4293
4294
|
}
|
|
4294
4295
|
|
|
4295
|
-
// Upload lifecycle methods (progress, completion, errors) run asynchronously and may
|
|
4296
|
-
// fire while the user is focused on another element (e.g., a title field). Without
|
|
4297
|
-
// SKIP_DOM_SELECTION_TAG, Lexical's reconciler would move the DOM selection back into
|
|
4298
|
-
// the editor, stealing focus from wherever the user is currently typing.
|
|
4299
|
-
get #backgroundUpdateTags() {
|
|
4300
|
-
if (this.#editorHasFocus) {
|
|
4301
|
-
return SILENT_UPDATE_TAGS
|
|
4302
|
-
} else {
|
|
4303
|
-
return [ ...SILENT_UPDATE_TAGS, SKIP_DOM_SELECTION_TAG ]
|
|
4304
|
-
}
|
|
4305
|
-
}
|
|
4306
|
-
|
|
4307
|
-
get #editorHasFocus() {
|
|
4308
|
-
const rootElement = this.editor.getRootElement();
|
|
4309
|
-
return rootElement !== null && rootElement.contains(document.activeElement)
|
|
4310
|
-
}
|
|
4311
|
-
|
|
4312
|
-
get #selectionIncludesUploadNode() {
|
|
4313
|
-
const selection = $getSelection();
|
|
4314
|
-
if (selection === null) return false
|
|
4315
|
-
|
|
4316
|
-
if (selection.getNodes().some((node) => node.is(this))) return true
|
|
4317
|
-
if (!$isRangeSelection(selection) || !selection.isCollapsed()) return false
|
|
4318
|
-
|
|
4319
|
-
const anchorNode = selection.anchor.getNode();
|
|
4320
|
-
if (!$isProvisionalParagraphNode(anchorNode) || !anchorNode.isEmpty()) return false
|
|
4321
|
-
|
|
4322
|
-
const previousSibling = anchorNode.getPreviousSibling();
|
|
4323
|
-
return previousSibling !== null && previousSibling.is(this)
|
|
4324
|
-
}
|
|
4325
|
-
|
|
4326
4296
|
#toActionTextAttachmentNodeWith(blob, previewSrc) {
|
|
4327
4297
|
const conversion = new AttachmentNodeConversion(this, blob, previewSrc);
|
|
4328
4298
|
return conversion.toAttachmentNode()
|
|
@@ -4672,34 +4642,137 @@ class GalleryUploader extends Uploader {
|
|
|
4672
4642
|
}
|
|
4673
4643
|
}
|
|
4674
4644
|
|
|
4675
|
-
class
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4645
|
+
class NodeInserter {
|
|
4646
|
+
static for(selection) {
|
|
4647
|
+
const INSERTERS = [
|
|
4648
|
+
CodeNodeInserter,
|
|
4649
|
+
QuoteNodeInserter,
|
|
4650
|
+
ShadowRootNodeInserter,
|
|
4651
|
+
NodeSelectionNodeInserter
|
|
4652
|
+
];
|
|
4653
|
+
const Inserter = INSERTERS.find(inserter => inserter.handles(selection));
|
|
4654
|
+
return Inserter ? new Inserter(selection) : selection
|
|
4679
4655
|
}
|
|
4680
4656
|
|
|
4681
|
-
|
|
4682
|
-
this.
|
|
4683
|
-
this.editor = null;
|
|
4657
|
+
constructor(selection) {
|
|
4658
|
+
this.selection = selection;
|
|
4684
4659
|
}
|
|
4660
|
+
}
|
|
4685
4661
|
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
this.insertDOM(parseHtml(html), { tag });
|
|
4662
|
+
class CodeNodeInserter extends NodeInserter {
|
|
4663
|
+
static handles(selection) {
|
|
4664
|
+
return $getNearestNodeOfType(selection.anchor?.getNode(), CodeNode)
|
|
4690
4665
|
}
|
|
4691
4666
|
|
|
4692
|
-
|
|
4693
|
-
this
|
|
4667
|
+
insertNodes(nodes) {
|
|
4668
|
+
if (!this.selection.isCollapsed()) { this.selection.removeText(); }
|
|
4694
4669
|
|
|
4695
|
-
this.
|
|
4696
|
-
|
|
4670
|
+
$ensureForwardRangeSelection(this.selection);
|
|
4671
|
+
const focusNode = this.selection.focus.getNode();
|
|
4672
|
+
const codeNode = $getNearestNodeOfType(focusNode, CodeNode);
|
|
4673
|
+
const insertionIndex = focusNode.is(codeNode) ? 0 : focusNode.getIndexWithinParent();
|
|
4697
4674
|
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4675
|
+
const caret = $getChildCaretAtIndex(codeNode, insertionIndex + 1, "previous");
|
|
4676
|
+
|
|
4677
|
+
for (const node of nodes) {
|
|
4678
|
+
if (!node.isAttached()) continue
|
|
4679
|
+
if (caret.getNodeAtCaret() && $isElementNode(node)) { caret.insert($createLineBreakNode()); }
|
|
4680
|
+
|
|
4681
|
+
caret.insert(this.#convertNodeToCodeChild(node));
|
|
4682
|
+
}
|
|
4683
|
+
|
|
4684
|
+
caret.getNodeAtCaret().selectEnd();
|
|
4685
|
+
}
|
|
4686
|
+
|
|
4687
|
+
#convertNodeToCodeChild(node) {
|
|
4688
|
+
if ($isLineBreakNode(node)) {
|
|
4689
|
+
return node
|
|
4690
|
+
} else {
|
|
4691
|
+
node.remove();
|
|
4692
|
+
return $createTextNode(node.getTextContent())
|
|
4693
|
+
}
|
|
4694
|
+
}
|
|
4695
|
+
|
|
4696
|
+
}
|
|
4697
|
+
|
|
4698
|
+
// Lexical will split a QuoteNode when inserting other Elements - we want them simply inserted as-is
|
|
4699
|
+
class QuoteNodeInserter extends NodeInserter {
|
|
4700
|
+
static handles(selection) {
|
|
4701
|
+
return $getNearestNodeOfType(selection.anchor?.getNode(), QuoteNode)
|
|
4702
|
+
}
|
|
4703
|
+
|
|
4704
|
+
insertNodes(nodes) {
|
|
4705
|
+
if (!this.selection.isCollapsed()) { this.selection.removeText(); }
|
|
4706
|
+
|
|
4707
|
+
$ensureForwardRangeSelection(this.selection);
|
|
4708
|
+
let lastNode = this.selection.focus.getNode();
|
|
4709
|
+
for (const node of nodes) {
|
|
4710
|
+
lastNode = lastNode.insertAfter(node);
|
|
4711
|
+
}
|
|
4712
|
+
|
|
4713
|
+
lastNode.selectEnd();
|
|
4714
|
+
}
|
|
4715
|
+
}
|
|
4716
|
+
|
|
4717
|
+
class ShadowRootNodeInserter extends NodeInserter {
|
|
4718
|
+
static handles(selection) {
|
|
4719
|
+
return $isShadowRoot(selection?.anchor.getNode())
|
|
4720
|
+
}
|
|
4721
|
+
|
|
4722
|
+
insertNodes(nodes) {
|
|
4723
|
+
const anchorNode = this.selection.anchor.getNode();
|
|
4724
|
+
const paragraph = $createParagraphNode();
|
|
4725
|
+
anchorNode.append(paragraph);
|
|
4726
|
+
|
|
4727
|
+
paragraph.selectStart().insertNodes(nodes);
|
|
4728
|
+
}
|
|
4729
|
+
}
|
|
4730
|
+
|
|
4731
|
+
class NodeSelectionNodeInserter extends NodeInserter {
|
|
4732
|
+
static handles(selection) {
|
|
4733
|
+
return $isNodeSelection(selection)
|
|
4734
|
+
}
|
|
4735
|
+
|
|
4736
|
+
insertNodes(nodes) {
|
|
4737
|
+
const selectedNodes = this.selection.getNodes();
|
|
4738
|
+
|
|
4739
|
+
// Overrides Lexical's default behavior of _removing_ the currently selected nodes
|
|
4740
|
+
// https://github.com/facebook/lexical/blob/v0.38.2/packages/lexical/src/LexicalSelection.ts#L412
|
|
4741
|
+
let lastNode = selectedNodes.at(-1);
|
|
4742
|
+
for (const node of nodes) {
|
|
4743
|
+
lastNode = lastNode.insertAfter(node);
|
|
4744
|
+
}
|
|
4745
|
+
}
|
|
4746
|
+
}
|
|
4747
|
+
|
|
4748
|
+
class Contents {
|
|
4749
|
+
constructor(editorElement) {
|
|
4750
|
+
this.editorElement = editorElement;
|
|
4751
|
+
this.editor = editorElement.editor;
|
|
4752
|
+
}
|
|
4753
|
+
|
|
4754
|
+
dispose() {
|
|
4755
|
+
this.editorElement = null;
|
|
4756
|
+
this.editor = null;
|
|
4757
|
+
}
|
|
4758
|
+
|
|
4759
|
+
get selection() { return this.editorElement.selection }
|
|
4760
|
+
|
|
4761
|
+
insertHtml(html, { tag } = {}) {
|
|
4762
|
+
this.insertDOM(parseHtml(html), { tag });
|
|
4763
|
+
}
|
|
4764
|
+
|
|
4765
|
+
insertDOM(doc, { tag } = {}) {
|
|
4766
|
+
this.#unwrapPlaceholderAnchors(doc);
|
|
4767
|
+
|
|
4768
|
+
this.editor.update(() => {
|
|
4769
|
+
if ($hasUpdateTag(PASTE_TAG)) this.#stripTableCellColorStyles(doc);
|
|
4770
|
+
|
|
4771
|
+
const nodes = $generateNodesFromDOM(this.editor, doc);
|
|
4772
|
+
if (!this.#insertUploadNodes(nodes)) {
|
|
4773
|
+
this.insertAtCursor(...nodes);
|
|
4774
|
+
}
|
|
4775
|
+
}, { tag });
|
|
4703
4776
|
}
|
|
4704
4777
|
|
|
4705
4778
|
insertAtCursor(...nodes) {
|
|
@@ -4942,7 +5015,7 @@ class Contents {
|
|
|
4942
5015
|
const node = $getNodeByKey(nodeKey);
|
|
4943
5016
|
if (!(node instanceof ActionTextAttachmentUploadNode)) return
|
|
4944
5017
|
|
|
4945
|
-
const replacementNodeKey = node
|
|
5018
|
+
const replacementNodeKey = node.$showUploadedAttachment(blob);
|
|
4946
5019
|
if (replacementNodeKey) {
|
|
4947
5020
|
nodeKey = replacementNodeKey;
|
|
4948
5021
|
}
|
|
@@ -5281,113 +5354,6 @@ class Contents {
|
|
|
5281
5354
|
}
|
|
5282
5355
|
}
|
|
5283
5356
|
|
|
5284
|
-
function $isShadowRoot(node) {
|
|
5285
|
-
return $isElementNode(node) && $isRootOrShadowRoot(node) && !$isRootNode(node)
|
|
5286
|
-
}
|
|
5287
|
-
|
|
5288
|
-
class NodeInserter {
|
|
5289
|
-
static for(selection) {
|
|
5290
|
-
const INSERTERS = [
|
|
5291
|
-
CodeNodeInserter,
|
|
5292
|
-
QuoteNodeInserter,
|
|
5293
|
-
ShadowRootNodeInserter,
|
|
5294
|
-
NodeSelectionNodeInserter
|
|
5295
|
-
];
|
|
5296
|
-
const Inserter = INSERTERS.find(inserter => inserter.handles(selection));
|
|
5297
|
-
return Inserter ? new Inserter(selection) : selection
|
|
5298
|
-
}
|
|
5299
|
-
|
|
5300
|
-
constructor(selection) {
|
|
5301
|
-
this.selection = selection;
|
|
5302
|
-
}
|
|
5303
|
-
}
|
|
5304
|
-
|
|
5305
|
-
class CodeNodeInserter extends NodeInserter {
|
|
5306
|
-
static handles(selection) {
|
|
5307
|
-
return $getNearestNodeOfType(selection.anchor?.getNode(), CodeNode)
|
|
5308
|
-
}
|
|
5309
|
-
|
|
5310
|
-
insertNodes(nodes) {
|
|
5311
|
-
if (!this.selection.isCollapsed()) { this.selection.removeText(); }
|
|
5312
|
-
|
|
5313
|
-
$ensureForwardRangeSelection(this.selection);
|
|
5314
|
-
const focusNode = this.selection.focus.getNode();
|
|
5315
|
-
const codeNode = $getNearestNodeOfType(focusNode, CodeNode);
|
|
5316
|
-
const insertionIndex = focusNode.is(codeNode) ? 0 : focusNode.getIndexWithinParent();
|
|
5317
|
-
|
|
5318
|
-
const caret = $getChildCaretAtIndex(codeNode, insertionIndex + 1, "previous");
|
|
5319
|
-
|
|
5320
|
-
for (const node of nodes) {
|
|
5321
|
-
if (!node.isAttached()) continue
|
|
5322
|
-
if (caret.getNodeAtCaret() && $isElementNode(node)) { caret.insert($createLineBreakNode()); }
|
|
5323
|
-
|
|
5324
|
-
caret.insert(this.#convertNodeToCodeChild(node));
|
|
5325
|
-
}
|
|
5326
|
-
|
|
5327
|
-
caret.getNodeAtCaret().selectEnd();
|
|
5328
|
-
}
|
|
5329
|
-
|
|
5330
|
-
#convertNodeToCodeChild(node) {
|
|
5331
|
-
if ($isLineBreakNode(node)) {
|
|
5332
|
-
return node
|
|
5333
|
-
} else {
|
|
5334
|
-
node.remove();
|
|
5335
|
-
return $createTextNode(node.getTextContent())
|
|
5336
|
-
}
|
|
5337
|
-
}
|
|
5338
|
-
|
|
5339
|
-
}
|
|
5340
|
-
|
|
5341
|
-
// Lexical will split a QuoteNode when inserting other Elements - we want them simply inserted as-is
|
|
5342
|
-
class QuoteNodeInserter extends NodeInserter {
|
|
5343
|
-
static handles(selection) {
|
|
5344
|
-
return $getNearestNodeOfType(selection.anchor?.getNode(), QuoteNode)
|
|
5345
|
-
}
|
|
5346
|
-
|
|
5347
|
-
insertNodes(nodes) {
|
|
5348
|
-
if (!this.selection.isCollapsed()) { this.selection.removeText(); }
|
|
5349
|
-
|
|
5350
|
-
$ensureForwardRangeSelection(this.selection);
|
|
5351
|
-
let lastNode = this.selection.focus.getNode();
|
|
5352
|
-
for (const node of nodes) {
|
|
5353
|
-
lastNode = lastNode.insertAfter(node);
|
|
5354
|
-
}
|
|
5355
|
-
|
|
5356
|
-
lastNode.selectEnd();
|
|
5357
|
-
}
|
|
5358
|
-
}
|
|
5359
|
-
|
|
5360
|
-
class ShadowRootNodeInserter extends NodeInserter {
|
|
5361
|
-
static handles(selection) {
|
|
5362
|
-
return $isShadowRoot(selection?.anchor.getNode())
|
|
5363
|
-
}
|
|
5364
|
-
|
|
5365
|
-
insertNodes(nodes) {
|
|
5366
|
-
const anchorNode = this.selection.anchor.getNode();
|
|
5367
|
-
const paragraph = $createParagraphNode();
|
|
5368
|
-
anchorNode.append(paragraph);
|
|
5369
|
-
|
|
5370
|
-
paragraph.selectStart().insertNodes(nodes);
|
|
5371
|
-
}
|
|
5372
|
-
}
|
|
5373
|
-
|
|
5374
|
-
class NodeSelectionNodeInserter extends NodeInserter {
|
|
5375
|
-
static handles(selection) {
|
|
5376
|
-
return $isNodeSelection(selection)
|
|
5377
|
-
}
|
|
5378
|
-
|
|
5379
|
-
insertNodes(nodes) {
|
|
5380
|
-
const selectedNodes = this.selection.getNodes();
|
|
5381
|
-
|
|
5382
|
-
// Overrides Lexical's default behavior of _removing_ the currently selected nodes
|
|
5383
|
-
// https://github.com/facebook/lexical/blob/v0.38.2/packages/lexical/src/LexicalSelection.ts#L412
|
|
5384
|
-
let lastNode = selectedNodes.at(-1);
|
|
5385
|
-
for (const node of nodes) {
|
|
5386
|
-
lastNode = lastNode.insertAfter(node);
|
|
5387
|
-
}
|
|
5388
|
-
}
|
|
5389
|
-
}
|
|
5390
|
-
|
|
5391
5357
|
class Clipboard {
|
|
5392
5358
|
constructor(editorElement) {
|
|
5393
5359
|
this.editorElement = editorElement;
|
|
@@ -5694,6 +5660,107 @@ function unwrapSpans(element) {
|
|
|
5694
5660
|
return element
|
|
5695
5661
|
}
|
|
5696
5662
|
|
|
5663
|
+
class ProvisionalParagraphNode extends ParagraphNode {
|
|
5664
|
+
$config() {
|
|
5665
|
+
return this.config("provisonal_paragraph", {
|
|
5666
|
+
extends: ParagraphNode,
|
|
5667
|
+
importDOM: () => null,
|
|
5668
|
+
$transform: (node) => {
|
|
5669
|
+
node.concretizeIfEdited(node);
|
|
5670
|
+
node.removeUnlessRequired(node);
|
|
5671
|
+
}
|
|
5672
|
+
})
|
|
5673
|
+
}
|
|
5674
|
+
|
|
5675
|
+
static neededBetween(nodeBefore, nodeAfter) {
|
|
5676
|
+
return !$isSelectableElement(nodeBefore, "next")
|
|
5677
|
+
&& !$isSelectableElement(nodeAfter, "previous")
|
|
5678
|
+
}
|
|
5679
|
+
|
|
5680
|
+
createDOM(editor) {
|
|
5681
|
+
const p = super.createDOM(editor);
|
|
5682
|
+
const selected = this.isSelected($getSelection());
|
|
5683
|
+
p.classList.add("provisional-paragraph");
|
|
5684
|
+
p.classList.toggle("hidden", !selected);
|
|
5685
|
+
return p
|
|
5686
|
+
}
|
|
5687
|
+
|
|
5688
|
+
updateDOM(_prevNode, dom) {
|
|
5689
|
+
const selected = this.isSelected($getSelection());
|
|
5690
|
+
dom.classList.toggle("hidden", !selected);
|
|
5691
|
+
return false
|
|
5692
|
+
}
|
|
5693
|
+
|
|
5694
|
+
getTextContent() {
|
|
5695
|
+
return ""
|
|
5696
|
+
}
|
|
5697
|
+
|
|
5698
|
+
exportDOM() {
|
|
5699
|
+
return {
|
|
5700
|
+
element: null
|
|
5701
|
+
}
|
|
5702
|
+
}
|
|
5703
|
+
|
|
5704
|
+
// override as Lexical has an interesting view of collapsed selection in ElementNodes
|
|
5705
|
+
// https://github.com/facebook/lexical/blob/f1e4f66014377b1f2595aec2b0ee17f5b7ef4dfc/packages/lexical/src/LexicalNode.ts#L646
|
|
5706
|
+
isSelected(selection = null) {
|
|
5707
|
+
const targetSelection = selection || $getSelection();
|
|
5708
|
+
if (!targetSelection) return false
|
|
5709
|
+
|
|
5710
|
+
if (targetSelection.getNodes().some(node => node.is(this) || this.isParentOf(node))) return true
|
|
5711
|
+
|
|
5712
|
+
// A collapsed range selection on the parent element at an offset adjacent to
|
|
5713
|
+
// this node means the caret is visually at this paragraph's position. Treat it
|
|
5714
|
+
// as selected so the paragraph is visible and the caret renders correctly.
|
|
5715
|
+
//
|
|
5716
|
+
// Both the offset matching our index (cursor just before us) and index + 1
|
|
5717
|
+
// (cursor just after us) count, because the provisional paragraph is an
|
|
5718
|
+
// invisible spacer: the browser resolves both offsets to the same visual spot.
|
|
5719
|
+
if ($isRangeSelection(targetSelection) && targetSelection.isCollapsed()) {
|
|
5720
|
+
const { anchor } = targetSelection;
|
|
5721
|
+
const parent = this.getParent();
|
|
5722
|
+
if (parent && anchor.getNode().is(parent) && anchor.type === "element") {
|
|
5723
|
+
const index = this.getIndexWithinParent();
|
|
5724
|
+
return anchor.offset === index || anchor.offset === index + 1
|
|
5725
|
+
}
|
|
5726
|
+
}
|
|
5727
|
+
|
|
5728
|
+
return false
|
|
5729
|
+
}
|
|
5730
|
+
|
|
5731
|
+
removeUnlessRequired(self = this.getLatest()) {
|
|
5732
|
+
if (!self.required) self.remove();
|
|
5733
|
+
}
|
|
5734
|
+
|
|
5735
|
+
concretizeIfEdited(self = this.getLatest()) {
|
|
5736
|
+
if (self.getTextContentSize() > 0) {
|
|
5737
|
+
self.replace($createParagraphNode(), true);
|
|
5738
|
+
}
|
|
5739
|
+
}
|
|
5740
|
+
|
|
5741
|
+
|
|
5742
|
+
get required() {
|
|
5743
|
+
return this.isDirectRootChild && ProvisionalParagraphNode.neededBetween(...this.immediateSiblings)
|
|
5744
|
+
}
|
|
5745
|
+
|
|
5746
|
+
get isDirectRootChild() {
|
|
5747
|
+
const parent = this.getParent();
|
|
5748
|
+
return $isRootOrShadowRoot(parent)
|
|
5749
|
+
}
|
|
5750
|
+
|
|
5751
|
+
get immediateSiblings() {
|
|
5752
|
+
return [ this.getPreviousSibling(), this.getNextSibling() ]
|
|
5753
|
+
}
|
|
5754
|
+
}
|
|
5755
|
+
|
|
5756
|
+
function $isProvisionalParagraphNode(node) {
|
|
5757
|
+
return node instanceof ProvisionalParagraphNode
|
|
5758
|
+
}
|
|
5759
|
+
|
|
5760
|
+
function $isSelectableElement(node, direction) {
|
|
5761
|
+
return $isElementNode(node) && (direction === "next" ? node.canInsertTextBefore() : node.canInsertTextAfter())
|
|
5762
|
+
}
|
|
5763
|
+
|
|
5697
5764
|
class ProvisionalParagraphExtension extends LexxyExtension {
|
|
5698
5765
|
get lexicalExtension() {
|
|
5699
5766
|
return defineExtension({
|
|
@@ -5715,6 +5782,8 @@ class ProvisionalParagraphExtension extends LexxyExtension {
|
|
|
5715
5782
|
}
|
|
5716
5783
|
|
|
5717
5784
|
function $insertRequiredProvisionalParagraphs(rootNode) {
|
|
5785
|
+
const nodeBeforeRootSelection = $nodeBeforeRootSelection(rootNode);
|
|
5786
|
+
|
|
5718
5787
|
const firstNode = rootNode.getFirstChild();
|
|
5719
5788
|
if (ProvisionalParagraphNode.neededBetween(null, firstNode)) {
|
|
5720
5789
|
$insertFirst(rootNode, new ProvisionalParagraphNode);
|
|
@@ -5724,10 +5793,18 @@ function $insertRequiredProvisionalParagraphs(rootNode) {
|
|
|
5724
5793
|
const nextNode = node.getNextSibling();
|
|
5725
5794
|
if (ProvisionalParagraphNode.neededBetween(node, nextNode)) {
|
|
5726
5795
|
node.insertAfter(new ProvisionalParagraphNode);
|
|
5796
|
+
if (node.is(nodeBeforeRootSelection)) node.selectNext();
|
|
5727
5797
|
}
|
|
5728
5798
|
}
|
|
5729
5799
|
}
|
|
5730
5800
|
|
|
5801
|
+
function $nodeBeforeRootSelection(rootNode) {
|
|
5802
|
+
const selection = $getSelection();
|
|
5803
|
+
if (!$isRootOrShadowRoot(selection?.anchor?.getNode())) return null
|
|
5804
|
+
|
|
5805
|
+
return rootNode.getChildAtIndex(selection.anchor.offset - 1)
|
|
5806
|
+
}
|
|
5807
|
+
|
|
5731
5808
|
function $removeUnneededProvisionalParagraphs(rootNode) {
|
|
5732
5809
|
for (const provisionalParagraph of $getAllProvisionalParagraphs(rootNode)) {
|
|
5733
5810
|
provisionalParagraph.removeUnlessRequired();
|
|
@@ -5735,6 +5812,9 @@ function $removeUnneededProvisionalParagraphs(rootNode) {
|
|
|
5735
5812
|
}
|
|
5736
5813
|
|
|
5737
5814
|
function $markAllProvisionalParagraphsDirty() {
|
|
5815
|
+
// Selection-driven visibility updates must not become standalone undo steps.
|
|
5816
|
+
$addUpdateTag(HISTORY_MERGE_TAG);
|
|
5817
|
+
|
|
5738
5818
|
for (const provisionalParagraph of $getAllProvisionalParagraphs()) {
|
|
5739
5819
|
provisionalParagraph.markDirty();
|
|
5740
5820
|
}
|
|
@@ -6615,54 +6695,58 @@ class LinkOpenerExtension extends LexxyExtension {
|
|
|
6615
6695
|
get lexicalExtension() {
|
|
6616
6696
|
return defineExtension({
|
|
6617
6697
|
name: "lexxy/link-opener",
|
|
6618
|
-
register: () =>
|
|
6619
|
-
|
|
6620
|
-
|
|
6621
|
-
|
|
6622
|
-
|
|
6623
|
-
|
|
6624
|
-
|
|
6625
|
-
}
|
|
6698
|
+
register: (editor) => mergeRegister$1(
|
|
6699
|
+
editor.registerCommand(CLICK_COMMAND, this.#handleClick.bind(this), COMMAND_PRIORITY_NORMAL),
|
|
6700
|
+
registerEventListener(this.editorElement.editorContentElement, "auxclick", this.#handleAuxClick.bind(this)),
|
|
6701
|
+
registerEventListener(window, "keydown", this.#handleKey.bind(this)),
|
|
6702
|
+
registerEventListener(window, "keyup", this.#handleKey.bind(this)),
|
|
6703
|
+
registerEventListener(window, "focus", this.#handleFocus.bind(this))
|
|
6704
|
+
)
|
|
6626
6705
|
})
|
|
6627
6706
|
}
|
|
6628
6707
|
|
|
6629
|
-
#
|
|
6708
|
+
#handleClick(event) {
|
|
6630
6709
|
if (this.#isModified(event)) {
|
|
6631
|
-
|
|
6710
|
+
return $openLink(event.target)
|
|
6632
6711
|
} else {
|
|
6633
|
-
|
|
6712
|
+
return false
|
|
6634
6713
|
}
|
|
6635
6714
|
}
|
|
6636
6715
|
|
|
6637
|
-
#
|
|
6638
|
-
|
|
6639
|
-
|
|
6640
|
-
|
|
6641
|
-
}, 200);
|
|
6716
|
+
#handleAuxClick(event) {
|
|
6717
|
+
if (event.button === 1) {
|
|
6718
|
+
this.editorElement.editor.read(() => $openLink(event.target));
|
|
6719
|
+
}
|
|
6642
6720
|
}
|
|
6643
6721
|
|
|
6644
|
-
#
|
|
6645
|
-
|
|
6722
|
+
#handleKey(event) {
|
|
6723
|
+
this.#updateOpenableAttribute(event);
|
|
6646
6724
|
}
|
|
6647
6725
|
|
|
6648
|
-
|
|
6649
|
-
|
|
6650
|
-
|
|
6651
|
-
|
|
6652
|
-
anchor.setAttribute("rel", "noopener noreferrer");
|
|
6653
|
-
}
|
|
6726
|
+
// Chrome dispatches events without modifier keys *for a while* after changing tabs
|
|
6727
|
+
async #handleFocus() {
|
|
6728
|
+
await delay(200);
|
|
6729
|
+
this.editorElement.addEventListener("mousemove", this.#updateOpenableAttribute.bind(this), { once: true });
|
|
6654
6730
|
}
|
|
6655
6731
|
|
|
6656
|
-
#
|
|
6657
|
-
|
|
6658
|
-
anchor.removeAttribute("contenteditable");
|
|
6659
|
-
anchor.removeAttribute("target");
|
|
6660
|
-
anchor.removeAttribute("rel");
|
|
6661
|
-
}
|
|
6732
|
+
#updateOpenableAttribute(event) {
|
|
6733
|
+
this.editorElement.toggleAttribute("data-links-openable", this.#isModified(event));
|
|
6662
6734
|
}
|
|
6663
6735
|
|
|
6664
|
-
|
|
6665
|
-
return
|
|
6736
|
+
#isModified(event) {
|
|
6737
|
+
return IS_APPLE ? event.metaKey : event.ctrlKey
|
|
6738
|
+
}
|
|
6739
|
+
}
|
|
6740
|
+
|
|
6741
|
+
function $openLink(target) {
|
|
6742
|
+
const node = $getNearestNodeFromDOMNode(target);
|
|
6743
|
+
const linkNode = $findMatchingParent(node, $isLinkNode);
|
|
6744
|
+
if (linkNode) {
|
|
6745
|
+
const url = linkNode.sanitizeUrl(linkNode.getURL());
|
|
6746
|
+
window.open(url, "_blank", "noopener,noreferrer");
|
|
6747
|
+
return true
|
|
6748
|
+
} else {
|
|
6749
|
+
return false
|
|
6666
6750
|
}
|
|
6667
6751
|
}
|
|
6668
6752
|
|
|
@@ -6674,11 +6758,11 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
6674
6758
|
static observedAttributes = [ "connected", "required" ]
|
|
6675
6759
|
|
|
6676
6760
|
#initialValue = ""
|
|
6677
|
-
#initialValueLoaded = false
|
|
6678
6761
|
#validationTextArea = document.createElement("textarea")
|
|
6679
6762
|
#editorInitializedRafId = null
|
|
6680
6763
|
#listeners = new ListenerBin()
|
|
6681
6764
|
#disposables = []
|
|
6765
|
+
#historyState = { undo: false, redo: false }
|
|
6682
6766
|
|
|
6683
6767
|
constructor() {
|
|
6684
6768
|
super();
|
|
@@ -6770,6 +6854,7 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
6770
6854
|
HighlightExtension,
|
|
6771
6855
|
TrixContentExtension,
|
|
6772
6856
|
TablesExtension,
|
|
6857
|
+
RewritableHistoryExtension,
|
|
6773
6858
|
AttachmentsExtension,
|
|
6774
6859
|
FormatEscapeExtension,
|
|
6775
6860
|
LinkOpenerExtension
|
|
@@ -6876,28 +6961,26 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
6876
6961
|
}
|
|
6877
6962
|
|
|
6878
6963
|
set value(html) {
|
|
6879
|
-
const
|
|
6964
|
+
const editorHasFocus = isEditorFocused(this.editor);
|
|
6880
6965
|
|
|
6881
6966
|
this.editor.update(() => {
|
|
6882
|
-
$addUpdateTag(SKIP_DOM_SELECTION_TAG);
|
|
6883
|
-
|
|
6884
|
-
|
|
6885
|
-
|
|
6886
|
-
|
|
6967
|
+
if (!editorHasFocus) $addUpdateTag(SKIP_DOM_SELECTION_TAG);
|
|
6968
|
+
|
|
6969
|
+
$getRoot()
|
|
6970
|
+
.clear()
|
|
6971
|
+
.selectEnd()
|
|
6972
|
+
.insertNodes(this.#parseHtmlIntoLexicalNodes(html));
|
|
6887
6973
|
|
|
6888
6974
|
this.#toggleEmptyStatus();
|
|
6975
|
+
}, { discrete: true });
|
|
6976
|
+
}
|
|
6889
6977
|
|
|
6890
|
-
|
|
6891
|
-
|
|
6892
|
-
|
|
6893
|
-
// it. Only fire on the first load — subsequent set value calls don't hit
|
|
6894
|
-
// the inconsistent state and the extra reconciler cycle is pure overhead.
|
|
6895
|
-
if (wasEmpty) {
|
|
6896
|
-
requestAnimationFrame(() => this.editor?.update(() => { }));
|
|
6897
|
-
}
|
|
6898
|
-
});
|
|
6978
|
+
get canUndo() {
|
|
6979
|
+
return this.#historyState.undo
|
|
6980
|
+
}
|
|
6899
6981
|
|
|
6900
|
-
|
|
6982
|
+
get canRedo() {
|
|
6983
|
+
return this.#historyState.redo
|
|
6901
6984
|
}
|
|
6902
6985
|
|
|
6903
6986
|
#parseHtmlIntoLexicalNodes(html) {
|
|
@@ -6933,6 +7016,7 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
6933
7016
|
this.#registerComponents();
|
|
6934
7017
|
this.#handleEnter();
|
|
6935
7018
|
this.#registerFocusEvents();
|
|
7019
|
+
this.#registerHistoryEvents();
|
|
6936
7020
|
this.#attachDebugHooks();
|
|
6937
7021
|
this.#attachToolbar();
|
|
6938
7022
|
this.#configureSanitizer();
|
|
@@ -7029,8 +7113,10 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
7029
7113
|
}
|
|
7030
7114
|
|
|
7031
7115
|
#loadInitialValue() {
|
|
7032
|
-
const initialHtml = this.valueBeforeDisconnect || this.getAttribute("value") || "<p></p>";
|
|
7033
|
-
this.
|
|
7116
|
+
const initialHtml = this.valueBeforeDisconnect || this.getAttribute("value") || "<p><br></p>";
|
|
7117
|
+
this.editor.update(() => {
|
|
7118
|
+
this.value = this.#initialValue = initialHtml;
|
|
7119
|
+
}, { tag: HISTORY_MERGE_TAG });
|
|
7034
7120
|
}
|
|
7035
7121
|
|
|
7036
7122
|
#resetBeforeTurboCaches() {
|
|
@@ -7080,8 +7166,6 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
7080
7166
|
} else {
|
|
7081
7167
|
registered.push(registerPlainText(this.editor));
|
|
7082
7168
|
}
|
|
7083
|
-
this.historyState = createEmptyHistoryState();
|
|
7084
|
-
registered.push(registerHistory(this.editor, this.historyState, 20));
|
|
7085
7169
|
|
|
7086
7170
|
this.#listeners.track(...registered);
|
|
7087
7171
|
}
|
|
@@ -7164,6 +7248,12 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
7164
7248
|
}
|
|
7165
7249
|
}
|
|
7166
7250
|
|
|
7251
|
+
#registerHistoryEvents() {
|
|
7252
|
+
this.#listeners.track(
|
|
7253
|
+
this.editor.registerCommand(CAN_UNDO_COMMAND, (enabled) => { this.#historyState.undo = enabled; }, COMMAND_PRIORITY_NORMAL),
|
|
7254
|
+
this.editor.registerCommand(CAN_REDO_COMMAND, (enabled) => { this.#historyState.redo = enabled; }, COMMAND_PRIORITY_NORMAL)
|
|
7255
|
+
);
|
|
7256
|
+
}
|
|
7167
7257
|
|
|
7168
7258
|
#attachDebugHooks() {
|
|
7169
7259
|
return
|
|
@@ -7254,8 +7344,8 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
7254
7344
|
heading: { active: format.isInHeading, enabled: true },
|
|
7255
7345
|
"unordered-list": { active: format.isInList && format.listType === "bullet", enabled: true },
|
|
7256
7346
|
"ordered-list": { active: format.isInList && format.listType === "number", enabled: true },
|
|
7257
|
-
undo: { active: false, enabled: this.
|
|
7258
|
-
redo: { active: false, enabled: this.
|
|
7347
|
+
undo: { active: false, enabled: this.canUndo },
|
|
7348
|
+
redo: { active: false, enabled: this.canRedo }
|
|
7259
7349
|
};
|
|
7260
7350
|
|
|
7261
7351
|
linkHref = linkNode ? linkNode.getURL() : null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@37signals/lexxy",
|
|
3
|
-
"version": "0.9.9-beta
|
|
3
|
+
"version": "0.9.9-beta.preview3.domselection",
|
|
4
4
|
"description": "Lexxy - A modern rich text editor for Rails.",
|
|
5
5
|
"module": "dist/lexxy.esm.js",
|
|
6
6
|
"type": "module",
|
|
@@ -48,21 +48,21 @@
|
|
|
48
48
|
"release": "yarn build:npm && yarn publish"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@lexical/clipboard": "^0.
|
|
52
|
-
"@lexical/code": "^0.
|
|
53
|
-
"@lexical/extension": "^0.
|
|
54
|
-
"@lexical/history": "^0.
|
|
55
|
-
"@lexical/html": "^0.
|
|
56
|
-
"@lexical/link": "^0.
|
|
57
|
-
"@lexical/list": "^0.
|
|
58
|
-
"@lexical/markdown": "^0.
|
|
59
|
-
"@lexical/plain-text": "^0.
|
|
60
|
-
"@lexical/rich-text": "^0.
|
|
61
|
-
"@lexical/selection": "^0.
|
|
62
|
-
"@lexical/table": "^0.
|
|
63
|
-
"@lexical/utils": "^0.
|
|
51
|
+
"@lexical/clipboard": "^0.43.0",
|
|
52
|
+
"@lexical/code": "^0.43.0",
|
|
53
|
+
"@lexical/extension": "^0.43.0",
|
|
54
|
+
"@lexical/history": "^0.43.0",
|
|
55
|
+
"@lexical/html": "^0.43.0",
|
|
56
|
+
"@lexical/link": "^0.43.0",
|
|
57
|
+
"@lexical/list": "^0.43.0",
|
|
58
|
+
"@lexical/markdown": "^0.43.0",
|
|
59
|
+
"@lexical/plain-text": "^0.43.0",
|
|
60
|
+
"@lexical/rich-text": "^0.43.0",
|
|
61
|
+
"@lexical/selection": "^0.43.0",
|
|
62
|
+
"@lexical/table": "^0.43.0",
|
|
63
|
+
"@lexical/utils": "^0.43.0",
|
|
64
64
|
"dompurify": "^3.3.0",
|
|
65
|
-
"lexical": "^0.
|
|
65
|
+
"lexical": "^0.43.0",
|
|
66
66
|
"marked": "^16.4.1",
|
|
67
67
|
"prismjs": "^1.30.0"
|
|
68
68
|
},
|