@37signals/lexxy 0.9.9-beta-preview1 → 0.9.9-beta.preview3
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 +505 -401
- 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
|
}
|
|
@@ -6432,6 +6512,15 @@ class EarlyEscapeCodeNode extends CodeNode {
|
|
|
6432
6512
|
insertNewAfter(selection, restoreSelection) {
|
|
6433
6513
|
if (!selection.isCollapsed()) return super.insertNewAfter(selection, restoreSelection)
|
|
6434
6514
|
|
|
6515
|
+
// Clamp element-type selection offsets that may have been invalidated
|
|
6516
|
+
// by the code retokenizer. The retokenizer's $updateAndRetainSelection
|
|
6517
|
+
// restores the element offset verbatim after re-tokenizing, but when
|
|
6518
|
+
// highlight splits changed the child count before retokenization, the
|
|
6519
|
+
// restored offset can exceed the current child count. Without clamping,
|
|
6520
|
+
// CodeNode.insertNewAfter passes the stale offset to splice(), which
|
|
6521
|
+
// throws "start + deleteCount > oldSize".
|
|
6522
|
+
this.#clampSelectionOffset(selection);
|
|
6523
|
+
|
|
6435
6524
|
if (this.#isCursorAtStart(selection)) {
|
|
6436
6525
|
this.insertBefore($createParagraphNode());
|
|
6437
6526
|
return null
|
|
@@ -6448,6 +6537,15 @@ class EarlyEscapeCodeNode extends CodeNode {
|
|
|
6448
6537
|
return super.insertNewAfter(selection, restoreSelection)
|
|
6449
6538
|
}
|
|
6450
6539
|
|
|
6540
|
+
#clampSelectionOffset(selection) {
|
|
6541
|
+
const childrenSize = this.getChildrenSize();
|
|
6542
|
+
for (const point of [ selection.anchor, selection.focus ]) {
|
|
6543
|
+
if (point.type === "element" && point.key === this.__key && point.offset > childrenSize) {
|
|
6544
|
+
point.set(this.__key, childrenSize, "element");
|
|
6545
|
+
}
|
|
6546
|
+
}
|
|
6547
|
+
}
|
|
6548
|
+
|
|
6451
6549
|
#isCursorAtStart(selection) {
|
|
6452
6550
|
const { anchor } = selection;
|
|
6453
6551
|
if (!$isAtNodeStart(anchor)) return false
|
|
@@ -6615,54 +6713,58 @@ class LinkOpenerExtension extends LexxyExtension {
|
|
|
6615
6713
|
get lexicalExtension() {
|
|
6616
6714
|
return defineExtension({
|
|
6617
6715
|
name: "lexxy/link-opener",
|
|
6618
|
-
register: () =>
|
|
6619
|
-
|
|
6620
|
-
|
|
6621
|
-
|
|
6622
|
-
|
|
6623
|
-
|
|
6624
|
-
|
|
6625
|
-
}
|
|
6716
|
+
register: (editor) => mergeRegister$1(
|
|
6717
|
+
editor.registerCommand(CLICK_COMMAND, this.#handleClick.bind(this), COMMAND_PRIORITY_NORMAL),
|
|
6718
|
+
registerEventListener(this.editorElement.editorContentElement, "auxclick", this.#handleAuxClick.bind(this)),
|
|
6719
|
+
registerEventListener(window, "keydown", this.#handleKey.bind(this)),
|
|
6720
|
+
registerEventListener(window, "keyup", this.#handleKey.bind(this)),
|
|
6721
|
+
registerEventListener(window, "focus", this.#handleFocus.bind(this))
|
|
6722
|
+
)
|
|
6626
6723
|
})
|
|
6627
6724
|
}
|
|
6628
6725
|
|
|
6629
|
-
#
|
|
6726
|
+
#handleClick(event) {
|
|
6630
6727
|
if (this.#isModified(event)) {
|
|
6631
|
-
|
|
6728
|
+
return $openLink(event.target)
|
|
6632
6729
|
} else {
|
|
6633
|
-
|
|
6730
|
+
return false
|
|
6634
6731
|
}
|
|
6635
6732
|
}
|
|
6636
6733
|
|
|
6637
|
-
#
|
|
6638
|
-
|
|
6639
|
-
|
|
6640
|
-
|
|
6641
|
-
}, 200);
|
|
6734
|
+
#handleAuxClick(event) {
|
|
6735
|
+
if (event.button === 1) {
|
|
6736
|
+
this.editorElement.editor.read(() => $openLink(event.target));
|
|
6737
|
+
}
|
|
6642
6738
|
}
|
|
6643
6739
|
|
|
6644
|
-
#
|
|
6645
|
-
|
|
6740
|
+
#handleKey(event) {
|
|
6741
|
+
this.#updateOpenableAttribute(event);
|
|
6646
6742
|
}
|
|
6647
6743
|
|
|
6648
|
-
|
|
6649
|
-
|
|
6650
|
-
|
|
6651
|
-
|
|
6652
|
-
anchor.setAttribute("rel", "noopener noreferrer");
|
|
6653
|
-
}
|
|
6744
|
+
// Chrome dispatches events without modifier keys *for a while* after changing tabs
|
|
6745
|
+
async #handleFocus() {
|
|
6746
|
+
await delay(200);
|
|
6747
|
+
this.editorElement.addEventListener("mousemove", this.#updateOpenableAttribute.bind(this), { once: true });
|
|
6654
6748
|
}
|
|
6655
6749
|
|
|
6656
|
-
#
|
|
6657
|
-
|
|
6658
|
-
anchor.removeAttribute("contenteditable");
|
|
6659
|
-
anchor.removeAttribute("target");
|
|
6660
|
-
anchor.removeAttribute("rel");
|
|
6661
|
-
}
|
|
6750
|
+
#updateOpenableAttribute(event) {
|
|
6751
|
+
this.editorElement.toggleAttribute("data-links-openable", this.#isModified(event));
|
|
6662
6752
|
}
|
|
6663
6753
|
|
|
6664
|
-
|
|
6665
|
-
return
|
|
6754
|
+
#isModified(event) {
|
|
6755
|
+
return IS_APPLE ? event.metaKey : event.ctrlKey
|
|
6756
|
+
}
|
|
6757
|
+
}
|
|
6758
|
+
|
|
6759
|
+
function $openLink(target) {
|
|
6760
|
+
const node = $getNearestNodeFromDOMNode(target);
|
|
6761
|
+
const linkNode = $findMatchingParent(node, $isLinkNode);
|
|
6762
|
+
if (linkNode) {
|
|
6763
|
+
const url = linkNode.sanitizeUrl(linkNode.getURL());
|
|
6764
|
+
window.open(url, "_blank", "noopener,noreferrer");
|
|
6765
|
+
return true
|
|
6766
|
+
} else {
|
|
6767
|
+
return false
|
|
6666
6768
|
}
|
|
6667
6769
|
}
|
|
6668
6770
|
|
|
@@ -6674,11 +6776,11 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
6674
6776
|
static observedAttributes = [ "connected", "required" ]
|
|
6675
6777
|
|
|
6676
6778
|
#initialValue = ""
|
|
6677
|
-
#initialValueLoaded = false
|
|
6678
6779
|
#validationTextArea = document.createElement("textarea")
|
|
6679
6780
|
#editorInitializedRafId = null
|
|
6680
6781
|
#listeners = new ListenerBin()
|
|
6681
6782
|
#disposables = []
|
|
6783
|
+
#historyState = { undo: false, redo: false }
|
|
6682
6784
|
|
|
6683
6785
|
constructor() {
|
|
6684
6786
|
super();
|
|
@@ -6770,6 +6872,7 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
6770
6872
|
HighlightExtension,
|
|
6771
6873
|
TrixContentExtension,
|
|
6772
6874
|
TablesExtension,
|
|
6875
|
+
RewritableHistoryExtension,
|
|
6773
6876
|
AttachmentsExtension,
|
|
6774
6877
|
FormatEscapeExtension,
|
|
6775
6878
|
LinkOpenerExtension
|
|
@@ -6876,28 +6979,22 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
6876
6979
|
}
|
|
6877
6980
|
|
|
6878
6981
|
set value(html) {
|
|
6879
|
-
const wasEmpty = !this.#initialValueLoaded;
|
|
6880
|
-
|
|
6881
6982
|
this.editor.update(() => {
|
|
6882
|
-
$
|
|
6883
|
-
|
|
6884
|
-
|
|
6885
|
-
|
|
6886
|
-
root.selectEnd();
|
|
6983
|
+
$getRoot()
|
|
6984
|
+
.clear()
|
|
6985
|
+
.selectEnd()
|
|
6986
|
+
.insertNodes(this.#parseHtmlIntoLexicalNodes(html));
|
|
6887
6987
|
|
|
6888
6988
|
this.#toggleEmptyStatus();
|
|
6989
|
+
}, { discrete: true, tag: SKIP_DOM_SELECTION_TAG });
|
|
6990
|
+
}
|
|
6889
6991
|
|
|
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
|
-
});
|
|
6992
|
+
get canUndo() {
|
|
6993
|
+
return this.#historyState.undo
|
|
6994
|
+
}
|
|
6899
6995
|
|
|
6900
|
-
|
|
6996
|
+
get canRedo() {
|
|
6997
|
+
return this.#historyState.redo
|
|
6901
6998
|
}
|
|
6902
6999
|
|
|
6903
7000
|
#parseHtmlIntoLexicalNodes(html) {
|
|
@@ -6933,6 +7030,7 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
6933
7030
|
this.#registerComponents();
|
|
6934
7031
|
this.#handleEnter();
|
|
6935
7032
|
this.#registerFocusEvents();
|
|
7033
|
+
this.#registerHistoryEvents();
|
|
6936
7034
|
this.#attachDebugHooks();
|
|
6937
7035
|
this.#attachToolbar();
|
|
6938
7036
|
this.#configureSanitizer();
|
|
@@ -7029,8 +7127,10 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
7029
7127
|
}
|
|
7030
7128
|
|
|
7031
7129
|
#loadInitialValue() {
|
|
7032
|
-
const initialHtml = this.valueBeforeDisconnect || this.getAttribute("value") || "<p></p>";
|
|
7033
|
-
this.
|
|
7130
|
+
const initialHtml = this.valueBeforeDisconnect || this.getAttribute("value") || "<p><br></p>";
|
|
7131
|
+
this.editor.update(() => {
|
|
7132
|
+
this.value = this.#initialValue = initialHtml;
|
|
7133
|
+
}, { tag: HISTORY_MERGE_TAG });
|
|
7034
7134
|
}
|
|
7035
7135
|
|
|
7036
7136
|
#resetBeforeTurboCaches() {
|
|
@@ -7080,8 +7180,6 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
7080
7180
|
} else {
|
|
7081
7181
|
registered.push(registerPlainText(this.editor));
|
|
7082
7182
|
}
|
|
7083
|
-
this.historyState = createEmptyHistoryState();
|
|
7084
|
-
registered.push(registerHistory(this.editor, this.historyState, 20));
|
|
7085
7183
|
|
|
7086
7184
|
this.#listeners.track(...registered);
|
|
7087
7185
|
}
|
|
@@ -7164,6 +7262,12 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
7164
7262
|
}
|
|
7165
7263
|
}
|
|
7166
7264
|
|
|
7265
|
+
#registerHistoryEvents() {
|
|
7266
|
+
this.#listeners.track(
|
|
7267
|
+
this.editor.registerCommand(CAN_UNDO_COMMAND, (enabled) => { this.#historyState.undo = enabled; }, COMMAND_PRIORITY_NORMAL),
|
|
7268
|
+
this.editor.registerCommand(CAN_REDO_COMMAND, (enabled) => { this.#historyState.redo = enabled; }, COMMAND_PRIORITY_NORMAL)
|
|
7269
|
+
);
|
|
7270
|
+
}
|
|
7167
7271
|
|
|
7168
7272
|
#attachDebugHooks() {
|
|
7169
7273
|
return
|
|
@@ -7254,8 +7358,8 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
7254
7358
|
heading: { active: format.isInHeading, enabled: true },
|
|
7255
7359
|
"unordered-list": { active: format.isInList && format.listType === "bullet", enabled: true },
|
|
7256
7360
|
"ordered-list": { active: format.isInList && format.listType === "number", enabled: true },
|
|
7257
|
-
undo: { active: false, enabled: this.
|
|
7258
|
-
redo: { active: false, enabled: this.
|
|
7361
|
+
undo: { active: false, enabled: this.canUndo },
|
|
7362
|
+
redo: { active: false, enabled: this.canRedo }
|
|
7259
7363
|
};
|
|
7260
7364
|
|
|
7261
7365
|
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",
|
|
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
|
},
|