@37signals/lexxy 0.9.9-beta-preview1 → 0.9.9-beta.preview2
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 +478 -407
- 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,109 @@ 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
|
+
$getEditor().update(() => {
|
|
2659
|
+
for (const [ nodeKey, { patch, replace } ] of Object.entries(rewrites)) {
|
|
2660
|
+
const node = $getNodeByKey(nodeKey);
|
|
2661
|
+
if (!node) continue
|
|
2662
|
+
|
|
2663
|
+
if (patch) Object.assign(node.getWritable(), patch);
|
|
2664
|
+
if (replace) node.replace(replace);
|
|
2665
|
+
}
|
|
2666
|
+
}, { discrete: true, tag: this.#getBackgroundUpdateTags() });
|
|
2667
|
+
|
|
2668
|
+
const nodeKeys = Object.keys(rewrites);
|
|
2669
|
+
|
|
2670
|
+
for (const entry of this.#allHistoryEntries) {
|
|
2671
|
+
if (!this.#entryHasSomeKeys(entry, nodeKeys)) continue
|
|
2672
|
+
|
|
2673
|
+
for (const [ nodeKey, { patch, replace } ] of Object.entries(rewrites)) {
|
|
2674
|
+
const node = entry.editorState._nodeMap.get(nodeKey);
|
|
2675
|
+
if (!node) continue
|
|
2676
|
+
|
|
2677
|
+
entry.editorState = safeCloneEditorState(entry.editorState);
|
|
2678
|
+
|
|
2679
|
+
if (patch) {
|
|
2680
|
+
entry.editorState._nodeMap.set(nodeKey, $cloneNodeWithPatch(node, patch));
|
|
2681
|
+
} else if (replace) {
|
|
2682
|
+
entry.editorState._nodeMap.set(nodeKey, $cloneNodeAdoptingKey(replace, node));
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2686
|
+
|
|
2687
|
+
return true
|
|
2688
|
+
}
|
|
2689
|
+
|
|
2690
|
+
#entryHasSomeKeys(entry, nodeKeys) {
|
|
2691
|
+
return nodeKeys.some(key => entry.editorState._nodeMap.has(key))
|
|
2692
|
+
}
|
|
2693
|
+
|
|
2694
|
+
#getBackgroundUpdateTags() {
|
|
2695
|
+
const tags = [ HISTORY_MERGE_TAG, SKIP_SCROLL_INTO_VIEW_TAG ];
|
|
2696
|
+
if (!isEditorFocused(this.editorElement.editor)) { tags.push(SKIP_DOM_SELECTION_TAG); }
|
|
2697
|
+
return tags
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2700
|
+
|
|
2701
|
+
function $cloneNodeWithPatch(node, patch) {
|
|
2702
|
+
const clone = $cloneWithProperties(node);
|
|
2703
|
+
Object.assign(clone, patch);
|
|
2704
|
+
return clone
|
|
2705
|
+
}
|
|
2706
|
+
|
|
2707
|
+
function $cloneNodeAdoptingKey(source, keyNode) {
|
|
2708
|
+
const clone = $cloneWithProperties(source);
|
|
2709
|
+
clone.__key = keyNode.__key;
|
|
2710
|
+
clone.__parent = keyNode.__parent;
|
|
2711
|
+
clone.__prev = keyNode.__prev;
|
|
2712
|
+
clone.__next = keyNode.__next;
|
|
2713
|
+
return clone
|
|
2714
|
+
}
|
|
2715
|
+
|
|
2716
|
+
// EditorState#clone() keeps the same map reference.
|
|
2717
|
+
// A new Map is needed to prevent editing Lexical's internal map
|
|
2718
|
+
// Warning: this bypasses DEV's safety map freezing
|
|
2719
|
+
function safeCloneEditorState(editorState) {
|
|
2720
|
+
const clone = editorState.clone();
|
|
2721
|
+
clone._nodeMap = new Map(editorState._nodeMap);
|
|
2722
|
+
return clone
|
|
2723
|
+
}
|
|
2724
|
+
|
|
2622
2725
|
class ActionTextAttachmentNode extends DecoratorNode {
|
|
2623
2726
|
static getType() {
|
|
2624
2727
|
return "action_text_attachment"
|
|
@@ -2831,6 +2934,18 @@ class ActionTextAttachmentNode extends DecoratorNode {
|
|
|
2831
2934
|
return figure
|
|
2832
2935
|
}
|
|
2833
2936
|
|
|
2937
|
+
patchAndRewriteHistory(patch) {
|
|
2938
|
+
this.editor.dispatchCommand(REWRITE_HISTORY_COMMAND, {
|
|
2939
|
+
[this.getKey()]: { patch }
|
|
2940
|
+
});
|
|
2941
|
+
}
|
|
2942
|
+
|
|
2943
|
+
replaceAndRewriteHistory(node) {
|
|
2944
|
+
this.editor.dispatchCommand(REWRITE_HISTORY_COMMAND, {
|
|
2945
|
+
[this.getKey()]: { replace: node }
|
|
2946
|
+
});
|
|
2947
|
+
}
|
|
2948
|
+
|
|
2834
2949
|
#createDOMForImage(options = {}) {
|
|
2835
2950
|
const initialSrc = this.previewSrc || this.src;
|
|
2836
2951
|
const img = createElement("img", { src: initialSrc, draggable: false, alt: this.altText, ...this.#imageDimensions, ...options });
|
|
@@ -2859,33 +2974,18 @@ class ActionTextAttachmentNode extends DecoratorNode {
|
|
|
2859
2974
|
|
|
2860
2975
|
#handleImageLoaded(img, previewSrc) {
|
|
2861
2976
|
img.src = this.src;
|
|
2862
|
-
this.
|
|
2863
|
-
if (this.isAttached()) this.getWritable().previewSrc = null;
|
|
2864
|
-
}, { tag: this.#backgroundUpdateTags });
|
|
2977
|
+
this.patchAndRewriteHistory({ previewSrc: null });
|
|
2865
2978
|
this.#revokePreviewSrc(previewSrc);
|
|
2866
2979
|
}
|
|
2867
2980
|
|
|
2868
2981
|
#handleImageLoadError(previewSrc) {
|
|
2869
|
-
this.
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
}
|
|
2874
|
-
}, { tag: this.#backgroundUpdateTags });
|
|
2982
|
+
this.patchAndRewriteHistory({
|
|
2983
|
+
previewSrc: null,
|
|
2984
|
+
uploadError: true
|
|
2985
|
+
});
|
|
2875
2986
|
this.#revokePreviewSrc(previewSrc);
|
|
2876
2987
|
}
|
|
2877
2988
|
|
|
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
2989
|
#revokePreviewSrc(previewSrc) {
|
|
2890
2990
|
if (previewSrc?.startsWith("blob:")) URL.revokeObjectURL(previewSrc);
|
|
2891
2991
|
}
|
|
@@ -2947,9 +3047,7 @@ class ActionTextAttachmentNode extends DecoratorNode {
|
|
|
2947
3047
|
figure.appendChild(this.#createEditableCaption());
|
|
2948
3048
|
});
|
|
2949
3049
|
|
|
2950
|
-
this.
|
|
2951
|
-
if (this.isAttached()) this.getWritable().pendingPreview = false;
|
|
2952
|
-
}, { tag: this.#backgroundUpdateTags });
|
|
3050
|
+
this.patchAndRewriteHistory({ pendingPreview: false });
|
|
2953
3051
|
}
|
|
2954
3052
|
|
|
2955
3053
|
#swapFigureContent(figure, fromClass, toClass, renderContent) {
|
|
@@ -3064,12 +3162,6 @@ class Selection {
|
|
|
3064
3162
|
this.#clearStaleInlineCodeFormat();
|
|
3065
3163
|
}
|
|
3066
3164
|
|
|
3067
|
-
set current(selection) {
|
|
3068
|
-
this.editor.update(() => {
|
|
3069
|
-
this.#syncSelectedClasses();
|
|
3070
|
-
});
|
|
3071
|
-
}
|
|
3072
|
-
|
|
3073
3165
|
get hasNodeSelection() {
|
|
3074
3166
|
return this.editor.getEditorState().read(() => {
|
|
3075
3167
|
const selection = $getSelection();
|
|
@@ -3398,7 +3490,7 @@ class Selection {
|
|
|
3398
3490
|
this.editor.registerCommand(DELETE_CHARACTER_COMMAND, this.#selectDecoratorNodeBeforeDeletion.bind(this), COMMAND_PRIORITY_LOW),
|
|
3399
3491
|
|
|
3400
3492
|
this.editor.registerCommand(SELECTION_CHANGE_COMMAND, () => {
|
|
3401
|
-
this
|
|
3493
|
+
this.#syncSelectedClasses();
|
|
3402
3494
|
}, COMMAND_PRIORITY_LOW)
|
|
3403
3495
|
);
|
|
3404
3496
|
}
|
|
@@ -3596,6 +3688,7 @@ class Selection {
|
|
|
3596
3688
|
|
|
3597
3689
|
#selectInLexical(node) {
|
|
3598
3690
|
if ($isDecoratorNode(node)) {
|
|
3691
|
+
$addUpdateTag(HISTORY_MERGE_TAG);
|
|
3599
3692
|
const selection = $createNodeSelectionWith(node);
|
|
3600
3693
|
$setSelection(selection);
|
|
3601
3694
|
return selection
|
|
@@ -3961,107 +4054,6 @@ class EditorConfiguration {
|
|
|
3961
4054
|
}
|
|
3962
4055
|
}
|
|
3963
4056
|
|
|
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
4057
|
async function loadFileIntoImage(file, image) {
|
|
4066
4058
|
return new Promise((resolve) => {
|
|
4067
4059
|
const reader = new FileReader();
|
|
@@ -4097,10 +4089,10 @@ class ActionTextAttachmentUploadNode extends ActionTextAttachmentNode {
|
|
|
4097
4089
|
}
|
|
4098
4090
|
|
|
4099
4091
|
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
|
|
4092
|
+
const { file, uploadUrl, blobUrlTemplate, progress, width, height, uploadError, fileName, contentType } = node;
|
|
4093
|
+
super({ ...node, contentType: file?.type ?? contentType }, key);
|
|
4094
|
+
this.file = file ?? null;
|
|
4095
|
+
this.fileName = file?.name ?? fileName;
|
|
4104
4096
|
this.uploadUrl = uploadUrl;
|
|
4105
4097
|
this.blobUrlTemplate = blobUrlTemplate;
|
|
4106
4098
|
this.progress = progress ?? null;
|
|
@@ -4157,6 +4149,8 @@ class ActionTextAttachmentUploadNode extends ActionTextAttachmentNode {
|
|
|
4157
4149
|
...super.exportJSON(),
|
|
4158
4150
|
type: "action_text_attachment_upload",
|
|
4159
4151
|
version: 1,
|
|
4152
|
+
fileName: this.fileName,
|
|
4153
|
+
contentType: this.contentType,
|
|
4160
4154
|
uploadUrl: this.uploadUrl,
|
|
4161
4155
|
blobUrlTemplate: this.blobUrlTemplate,
|
|
4162
4156
|
progress: this.progress,
|
|
@@ -4181,14 +4175,14 @@ class ActionTextAttachmentUploadNode extends ActionTextAttachmentNode {
|
|
|
4181
4175
|
}
|
|
4182
4176
|
|
|
4183
4177
|
#getFileExtension() {
|
|
4184
|
-
return this.
|
|
4178
|
+
return (this.fileName || "").split(".").pop().toLowerCase()
|
|
4185
4179
|
}
|
|
4186
4180
|
|
|
4187
4181
|
#createCaption() {
|
|
4188
4182
|
const figcaption = createElement("figcaption", { className: "attachment__caption" });
|
|
4189
4183
|
|
|
4190
|
-
const nameSpan = createElement("span", { className: "attachment__name", textContent: this.caption || this.
|
|
4191
|
-
const sizeSpan = createElement("span", { className: "attachment__size", textContent: bytesToHumanSize(this.file
|
|
4184
|
+
const nameSpan = createElement("span", { className: "attachment__name", textContent: this.caption || this.fileName || "" });
|
|
4185
|
+
const sizeSpan = createElement("span", { className: "attachment__size", textContent: bytesToHumanSize(this.file?.size) });
|
|
4192
4186
|
figcaption.appendChild(nameSpan);
|
|
4193
4187
|
figcaption.appendChild(sizeSpan);
|
|
4194
4188
|
|
|
@@ -4202,11 +4196,7 @@ class ActionTextAttachmentUploadNode extends ActionTextAttachmentNode {
|
|
|
4202
4196
|
#setDimensionsFromImage({ width, height }) {
|
|
4203
4197
|
if (this.#hasDimensions) return
|
|
4204
4198
|
|
|
4205
|
-
this.
|
|
4206
|
-
const writable = this.getWritable();
|
|
4207
|
-
writable.width = width;
|
|
4208
|
-
writable.height = height;
|
|
4209
|
-
}, { tag: this.#backgroundUpdateTags });
|
|
4199
|
+
this.patchAndRewriteHistory({ width, height });
|
|
4210
4200
|
}
|
|
4211
4201
|
|
|
4212
4202
|
get #hasDimensions() {
|
|
@@ -4233,8 +4223,8 @@ class ActionTextAttachmentUploadNode extends ActionTextAttachmentNode {
|
|
|
4233
4223
|
} else {
|
|
4234
4224
|
this.#dispatchEvent("lexxy:upload-end", { file: this.file, error: null });
|
|
4235
4225
|
this.editor.update(() => {
|
|
4236
|
-
this
|
|
4237
|
-
}
|
|
4226
|
+
this.$showUploadedAttachment(blob);
|
|
4227
|
+
});
|
|
4238
4228
|
}
|
|
4239
4229
|
});
|
|
4240
4230
|
}
|
|
@@ -4249,7 +4239,7 @@ class ActionTextAttachmentUploadNode extends ActionTextAttachmentNode {
|
|
|
4249
4239
|
directUploadWillStoreFileWithXHR: (request) => {
|
|
4250
4240
|
if (shouldAuthenticateUploads) request.withCredentials = true;
|
|
4251
4241
|
|
|
4252
|
-
const uploadProgressHandler = (event) => this.#handleUploadProgress(event);
|
|
4242
|
+
const uploadProgressHandler = (event) => this.#handleUploadProgress(event, request);
|
|
4253
4243
|
request.upload.addEventListener("progress", uploadProgressHandler);
|
|
4254
4244
|
}
|
|
4255
4245
|
}
|
|
@@ -4259,70 +4249,35 @@ class ActionTextAttachmentUploadNode extends ActionTextAttachmentNode {
|
|
|
4259
4249
|
this.#setProgress(1);
|
|
4260
4250
|
}
|
|
4261
4251
|
|
|
4262
|
-
#handleUploadProgress(event) {
|
|
4252
|
+
#handleUploadProgress(event, request) {
|
|
4263
4253
|
const progress = Math.round(event.loaded / event.total * 100);
|
|
4264
|
-
|
|
4265
|
-
|
|
4254
|
+
try {
|
|
4255
|
+
this.#setProgress(progress);
|
|
4256
|
+
this.#dispatchEvent("lexxy:upload-progress", { file: this.file, progress });
|
|
4257
|
+
} catch {
|
|
4258
|
+
request.abort();
|
|
4259
|
+
}
|
|
4266
4260
|
}
|
|
4267
4261
|
|
|
4268
4262
|
#setProgress(progress) {
|
|
4269
|
-
this.
|
|
4270
|
-
this.getWritable().progress = progress;
|
|
4271
|
-
}, { tag: this.#backgroundUpdateTags });
|
|
4263
|
+
this.patchAndRewriteHistory({ progress });
|
|
4272
4264
|
}
|
|
4273
4265
|
|
|
4274
4266
|
#handleUploadError(error) {
|
|
4275
4267
|
console.warn(`Upload error for ${this.file?.name ?? "file"}: ${error}`);
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
}, { tag: this.#backgroundUpdateTags });
|
|
4268
|
+
|
|
4269
|
+
this.patchAndRewriteHistory({ uploadError: true });
|
|
4279
4270
|
}
|
|
4280
4271
|
|
|
4281
|
-
showUploadedAttachment(blob) {
|
|
4272
|
+
$showUploadedAttachment(blob) {
|
|
4282
4273
|
const previewSrc = this.isPreviewableImage && this.file ? URL.createObjectURL(this.file) : null;
|
|
4283
4274
|
|
|
4284
4275
|
const replacementNode = this.#toActionTextAttachmentNodeWith(blob, previewSrc);
|
|
4285
|
-
|
|
4286
|
-
this.replace(replacementNode);
|
|
4287
|
-
|
|
4288
|
-
if (shouldSelectAfterReplacement && $isRootOrShadowRoot(replacementNode.getParent())) {
|
|
4289
|
-
replacementNode.selectNext();
|
|
4290
|
-
}
|
|
4276
|
+
this.replaceAndRewriteHistory(replacementNode);
|
|
4291
4277
|
|
|
4292
4278
|
return replacementNode.getKey()
|
|
4293
4279
|
}
|
|
4294
4280
|
|
|
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
4281
|
#toActionTextAttachmentNodeWith(blob, previewSrc) {
|
|
4327
4282
|
const conversion = new AttachmentNodeConversion(this, blob, previewSrc);
|
|
4328
4283
|
return conversion.toAttachmentNode()
|
|
@@ -4672,41 +4627,144 @@ class GalleryUploader extends Uploader {
|
|
|
4672
4627
|
}
|
|
4673
4628
|
}
|
|
4674
4629
|
|
|
4675
|
-
class
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4630
|
+
class NodeInserter {
|
|
4631
|
+
static for(selection) {
|
|
4632
|
+
const INSERTERS = [
|
|
4633
|
+
CodeNodeInserter,
|
|
4634
|
+
QuoteNodeInserter,
|
|
4635
|
+
ShadowRootNodeInserter,
|
|
4636
|
+
NodeSelectionNodeInserter
|
|
4637
|
+
];
|
|
4638
|
+
const Inserter = INSERTERS.find(inserter => inserter.handles(selection));
|
|
4639
|
+
return Inserter ? new Inserter(selection) : selection
|
|
4679
4640
|
}
|
|
4680
4641
|
|
|
4681
|
-
|
|
4682
|
-
this.
|
|
4683
|
-
this.editor = null;
|
|
4642
|
+
constructor(selection) {
|
|
4643
|
+
this.selection = selection;
|
|
4684
4644
|
}
|
|
4645
|
+
}
|
|
4685
4646
|
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
this.insertDOM(parseHtml(html), { tag });
|
|
4647
|
+
class CodeNodeInserter extends NodeInserter {
|
|
4648
|
+
static handles(selection) {
|
|
4649
|
+
return $getNearestNodeOfType(selection.anchor?.getNode(), CodeNode)
|
|
4690
4650
|
}
|
|
4691
4651
|
|
|
4692
|
-
|
|
4693
|
-
this
|
|
4694
|
-
|
|
4695
|
-
this.editor.update(() => {
|
|
4696
|
-
if ($hasUpdateTag(PASTE_TAG)) this.#stripTableCellColorStyles(doc);
|
|
4652
|
+
insertNodes(nodes) {
|
|
4653
|
+
if (!this.selection.isCollapsed()) { this.selection.removeText(); }
|
|
4697
4654
|
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
}, { tag });
|
|
4703
|
-
}
|
|
4655
|
+
$ensureForwardRangeSelection(this.selection);
|
|
4656
|
+
const focusNode = this.selection.focus.getNode();
|
|
4657
|
+
const codeNode = $getNearestNodeOfType(focusNode, CodeNode);
|
|
4658
|
+
const insertionIndex = focusNode.is(codeNode) ? 0 : focusNode.getIndexWithinParent();
|
|
4704
4659
|
|
|
4705
|
-
|
|
4706
|
-
const selection = $getSelection() ?? $getRoot().selectEnd();
|
|
4707
|
-
const inserter = NodeInserter.for(selection);
|
|
4660
|
+
const caret = $getChildCaretAtIndex(codeNode, insertionIndex + 1, "previous");
|
|
4708
4661
|
|
|
4709
|
-
|
|
4662
|
+
for (const node of nodes) {
|
|
4663
|
+
if (!node.isAttached()) continue
|
|
4664
|
+
if (caret.getNodeAtCaret() && $isElementNode(node)) { caret.insert($createLineBreakNode()); }
|
|
4665
|
+
|
|
4666
|
+
caret.insert(this.#convertNodeToCodeChild(node));
|
|
4667
|
+
}
|
|
4668
|
+
|
|
4669
|
+
caret.getNodeAtCaret().selectEnd();
|
|
4670
|
+
}
|
|
4671
|
+
|
|
4672
|
+
#convertNodeToCodeChild(node) {
|
|
4673
|
+
if ($isLineBreakNode(node)) {
|
|
4674
|
+
return node
|
|
4675
|
+
} else {
|
|
4676
|
+
node.remove();
|
|
4677
|
+
return $createTextNode(node.getTextContent())
|
|
4678
|
+
}
|
|
4679
|
+
}
|
|
4680
|
+
|
|
4681
|
+
}
|
|
4682
|
+
|
|
4683
|
+
// Lexical will split a QuoteNode when inserting other Elements - we want them simply inserted as-is
|
|
4684
|
+
class QuoteNodeInserter extends NodeInserter {
|
|
4685
|
+
static handles(selection) {
|
|
4686
|
+
return $getNearestNodeOfType(selection.anchor?.getNode(), QuoteNode)
|
|
4687
|
+
}
|
|
4688
|
+
|
|
4689
|
+
insertNodes(nodes) {
|
|
4690
|
+
if (!this.selection.isCollapsed()) { this.selection.removeText(); }
|
|
4691
|
+
|
|
4692
|
+
$ensureForwardRangeSelection(this.selection);
|
|
4693
|
+
let lastNode = this.selection.focus.getNode();
|
|
4694
|
+
for (const node of nodes) {
|
|
4695
|
+
lastNode = lastNode.insertAfter(node);
|
|
4696
|
+
}
|
|
4697
|
+
|
|
4698
|
+
lastNode.selectEnd();
|
|
4699
|
+
}
|
|
4700
|
+
}
|
|
4701
|
+
|
|
4702
|
+
class ShadowRootNodeInserter extends NodeInserter {
|
|
4703
|
+
static handles(selection) {
|
|
4704
|
+
return $isShadowRoot(selection?.anchor.getNode())
|
|
4705
|
+
}
|
|
4706
|
+
|
|
4707
|
+
insertNodes(nodes) {
|
|
4708
|
+
const anchorNode = this.selection.anchor.getNode();
|
|
4709
|
+
const paragraph = $createParagraphNode();
|
|
4710
|
+
anchorNode.append(paragraph);
|
|
4711
|
+
|
|
4712
|
+
paragraph.selectStart().insertNodes(nodes);
|
|
4713
|
+
}
|
|
4714
|
+
}
|
|
4715
|
+
|
|
4716
|
+
class NodeSelectionNodeInserter extends NodeInserter {
|
|
4717
|
+
static handles(selection) {
|
|
4718
|
+
return $isNodeSelection(selection)
|
|
4719
|
+
}
|
|
4720
|
+
|
|
4721
|
+
insertNodes(nodes) {
|
|
4722
|
+
const selectedNodes = this.selection.getNodes();
|
|
4723
|
+
|
|
4724
|
+
// Overrides Lexical's default behavior of _removing_ the currently selected nodes
|
|
4725
|
+
// https://github.com/facebook/lexical/blob/v0.38.2/packages/lexical/src/LexicalSelection.ts#L412
|
|
4726
|
+
let lastNode = selectedNodes.at(-1);
|
|
4727
|
+
for (const node of nodes) {
|
|
4728
|
+
lastNode = lastNode.insertAfter(node);
|
|
4729
|
+
}
|
|
4730
|
+
}
|
|
4731
|
+
}
|
|
4732
|
+
|
|
4733
|
+
class Contents {
|
|
4734
|
+
constructor(editorElement) {
|
|
4735
|
+
this.editorElement = editorElement;
|
|
4736
|
+
this.editor = editorElement.editor;
|
|
4737
|
+
}
|
|
4738
|
+
|
|
4739
|
+
dispose() {
|
|
4740
|
+
this.editorElement = null;
|
|
4741
|
+
this.editor = null;
|
|
4742
|
+
}
|
|
4743
|
+
|
|
4744
|
+
get selection() { return this.editorElement.selection }
|
|
4745
|
+
|
|
4746
|
+
insertHtml(html, { tag } = {}) {
|
|
4747
|
+
this.insertDOM(parseHtml(html), { tag });
|
|
4748
|
+
}
|
|
4749
|
+
|
|
4750
|
+
insertDOM(doc, { tag } = {}) {
|
|
4751
|
+
this.#unwrapPlaceholderAnchors(doc);
|
|
4752
|
+
|
|
4753
|
+
this.editor.update(() => {
|
|
4754
|
+
if ($hasUpdateTag(PASTE_TAG)) this.#stripTableCellColorStyles(doc);
|
|
4755
|
+
|
|
4756
|
+
const nodes = $generateNodesFromDOM(this.editor, doc);
|
|
4757
|
+
if (!this.#insertUploadNodes(nodes)) {
|
|
4758
|
+
this.insertAtCursor(...nodes);
|
|
4759
|
+
}
|
|
4760
|
+
}, { tag });
|
|
4761
|
+
}
|
|
4762
|
+
|
|
4763
|
+
insertAtCursor(...nodes) {
|
|
4764
|
+
const selection = $getSelection() ?? $getRoot().selectEnd();
|
|
4765
|
+
const inserter = NodeInserter.for(selection);
|
|
4766
|
+
|
|
4767
|
+
inserter.insertNodes(nodes);
|
|
4710
4768
|
}
|
|
4711
4769
|
|
|
4712
4770
|
insertAtCursorEnsuringLineBelow(node) {
|
|
@@ -4942,7 +5000,7 @@ class Contents {
|
|
|
4942
5000
|
const node = $getNodeByKey(nodeKey);
|
|
4943
5001
|
if (!(node instanceof ActionTextAttachmentUploadNode)) return
|
|
4944
5002
|
|
|
4945
|
-
const replacementNodeKey = node
|
|
5003
|
+
const replacementNodeKey = node.$showUploadedAttachment(blob);
|
|
4946
5004
|
if (replacementNodeKey) {
|
|
4947
5005
|
nodeKey = replacementNodeKey;
|
|
4948
5006
|
}
|
|
@@ -5281,113 +5339,6 @@ class Contents {
|
|
|
5281
5339
|
}
|
|
5282
5340
|
}
|
|
5283
5341
|
|
|
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
5342
|
class Clipboard {
|
|
5392
5343
|
constructor(editorElement) {
|
|
5393
5344
|
this.editorElement = editorElement;
|
|
@@ -5694,6 +5645,107 @@ function unwrapSpans(element) {
|
|
|
5694
5645
|
return element
|
|
5695
5646
|
}
|
|
5696
5647
|
|
|
5648
|
+
class ProvisionalParagraphNode extends ParagraphNode {
|
|
5649
|
+
$config() {
|
|
5650
|
+
return this.config("provisonal_paragraph", {
|
|
5651
|
+
extends: ParagraphNode,
|
|
5652
|
+
importDOM: () => null,
|
|
5653
|
+
$transform: (node) => {
|
|
5654
|
+
node.concretizeIfEdited(node);
|
|
5655
|
+
node.removeUnlessRequired(node);
|
|
5656
|
+
}
|
|
5657
|
+
})
|
|
5658
|
+
}
|
|
5659
|
+
|
|
5660
|
+
static neededBetween(nodeBefore, nodeAfter) {
|
|
5661
|
+
return !$isSelectableElement(nodeBefore, "next")
|
|
5662
|
+
&& !$isSelectableElement(nodeAfter, "previous")
|
|
5663
|
+
}
|
|
5664
|
+
|
|
5665
|
+
createDOM(editor) {
|
|
5666
|
+
const p = super.createDOM(editor);
|
|
5667
|
+
const selected = this.isSelected($getSelection());
|
|
5668
|
+
p.classList.add("provisional-paragraph");
|
|
5669
|
+
p.classList.toggle("hidden", !selected);
|
|
5670
|
+
return p
|
|
5671
|
+
}
|
|
5672
|
+
|
|
5673
|
+
updateDOM(_prevNode, dom) {
|
|
5674
|
+
const selected = this.isSelected($getSelection());
|
|
5675
|
+
dom.classList.toggle("hidden", !selected);
|
|
5676
|
+
return false
|
|
5677
|
+
}
|
|
5678
|
+
|
|
5679
|
+
getTextContent() {
|
|
5680
|
+
return ""
|
|
5681
|
+
}
|
|
5682
|
+
|
|
5683
|
+
exportDOM() {
|
|
5684
|
+
return {
|
|
5685
|
+
element: null
|
|
5686
|
+
}
|
|
5687
|
+
}
|
|
5688
|
+
|
|
5689
|
+
// override as Lexical has an interesting view of collapsed selection in ElementNodes
|
|
5690
|
+
// https://github.com/facebook/lexical/blob/f1e4f66014377b1f2595aec2b0ee17f5b7ef4dfc/packages/lexical/src/LexicalNode.ts#L646
|
|
5691
|
+
isSelected(selection = null) {
|
|
5692
|
+
const targetSelection = selection || $getSelection();
|
|
5693
|
+
if (!targetSelection) return false
|
|
5694
|
+
|
|
5695
|
+
if (targetSelection.getNodes().some(node => node.is(this) || this.isParentOf(node))) return true
|
|
5696
|
+
|
|
5697
|
+
// A collapsed range selection on the parent element at an offset adjacent to
|
|
5698
|
+
// this node means the caret is visually at this paragraph's position. Treat it
|
|
5699
|
+
// as selected so the paragraph is visible and the caret renders correctly.
|
|
5700
|
+
//
|
|
5701
|
+
// Both the offset matching our index (cursor just before us) and index + 1
|
|
5702
|
+
// (cursor just after us) count, because the provisional paragraph is an
|
|
5703
|
+
// invisible spacer: the browser resolves both offsets to the same visual spot.
|
|
5704
|
+
if ($isRangeSelection(targetSelection) && targetSelection.isCollapsed()) {
|
|
5705
|
+
const { anchor } = targetSelection;
|
|
5706
|
+
const parent = this.getParent();
|
|
5707
|
+
if (parent && anchor.getNode().is(parent) && anchor.type === "element") {
|
|
5708
|
+
const index = this.getIndexWithinParent();
|
|
5709
|
+
return anchor.offset === index || anchor.offset === index + 1
|
|
5710
|
+
}
|
|
5711
|
+
}
|
|
5712
|
+
|
|
5713
|
+
return false
|
|
5714
|
+
}
|
|
5715
|
+
|
|
5716
|
+
removeUnlessRequired(self = this.getLatest()) {
|
|
5717
|
+
if (!self.required) self.remove();
|
|
5718
|
+
}
|
|
5719
|
+
|
|
5720
|
+
concretizeIfEdited(self = this.getLatest()) {
|
|
5721
|
+
if (self.getTextContentSize() > 0) {
|
|
5722
|
+
self.replace($createParagraphNode(), true);
|
|
5723
|
+
}
|
|
5724
|
+
}
|
|
5725
|
+
|
|
5726
|
+
|
|
5727
|
+
get required() {
|
|
5728
|
+
return this.isDirectRootChild && ProvisionalParagraphNode.neededBetween(...this.immediateSiblings)
|
|
5729
|
+
}
|
|
5730
|
+
|
|
5731
|
+
get isDirectRootChild() {
|
|
5732
|
+
const parent = this.getParent();
|
|
5733
|
+
return $isRootOrShadowRoot(parent)
|
|
5734
|
+
}
|
|
5735
|
+
|
|
5736
|
+
get immediateSiblings() {
|
|
5737
|
+
return [ this.getPreviousSibling(), this.getNextSibling() ]
|
|
5738
|
+
}
|
|
5739
|
+
}
|
|
5740
|
+
|
|
5741
|
+
function $isProvisionalParagraphNode(node) {
|
|
5742
|
+
return node instanceof ProvisionalParagraphNode
|
|
5743
|
+
}
|
|
5744
|
+
|
|
5745
|
+
function $isSelectableElement(node, direction) {
|
|
5746
|
+
return $isElementNode(node) && (direction === "next" ? node.canInsertTextBefore() : node.canInsertTextAfter())
|
|
5747
|
+
}
|
|
5748
|
+
|
|
5697
5749
|
class ProvisionalParagraphExtension extends LexxyExtension {
|
|
5698
5750
|
get lexicalExtension() {
|
|
5699
5751
|
return defineExtension({
|
|
@@ -5715,6 +5767,8 @@ class ProvisionalParagraphExtension extends LexxyExtension {
|
|
|
5715
5767
|
}
|
|
5716
5768
|
|
|
5717
5769
|
function $insertRequiredProvisionalParagraphs(rootNode) {
|
|
5770
|
+
const nodeBeforeRootSelection = $nodeBeforeRootSelection(rootNode);
|
|
5771
|
+
|
|
5718
5772
|
const firstNode = rootNode.getFirstChild();
|
|
5719
5773
|
if (ProvisionalParagraphNode.neededBetween(null, firstNode)) {
|
|
5720
5774
|
$insertFirst(rootNode, new ProvisionalParagraphNode);
|
|
@@ -5724,10 +5778,18 @@ function $insertRequiredProvisionalParagraphs(rootNode) {
|
|
|
5724
5778
|
const nextNode = node.getNextSibling();
|
|
5725
5779
|
if (ProvisionalParagraphNode.neededBetween(node, nextNode)) {
|
|
5726
5780
|
node.insertAfter(new ProvisionalParagraphNode);
|
|
5781
|
+
if (node.is(nodeBeforeRootSelection)) node.selectNext();
|
|
5727
5782
|
}
|
|
5728
5783
|
}
|
|
5729
5784
|
}
|
|
5730
5785
|
|
|
5786
|
+
function $nodeBeforeRootSelection(rootNode) {
|
|
5787
|
+
const selection = $getSelection();
|
|
5788
|
+
if (!$isRootOrShadowRoot(selection?.anchor?.getNode())) return null
|
|
5789
|
+
|
|
5790
|
+
return rootNode.getChildAtIndex(selection.anchor.offset - 1)
|
|
5791
|
+
}
|
|
5792
|
+
|
|
5731
5793
|
function $removeUnneededProvisionalParagraphs(rootNode) {
|
|
5732
5794
|
for (const provisionalParagraph of $getAllProvisionalParagraphs(rootNode)) {
|
|
5733
5795
|
provisionalParagraph.removeUnlessRequired();
|
|
@@ -5735,6 +5797,9 @@ function $removeUnneededProvisionalParagraphs(rootNode) {
|
|
|
5735
5797
|
}
|
|
5736
5798
|
|
|
5737
5799
|
function $markAllProvisionalParagraphsDirty() {
|
|
5800
|
+
// Selection-driven visibility updates must not become standalone undo steps.
|
|
5801
|
+
$addUpdateTag(HISTORY_MERGE_TAG);
|
|
5802
|
+
|
|
5738
5803
|
for (const provisionalParagraph of $getAllProvisionalParagraphs()) {
|
|
5739
5804
|
provisionalParagraph.markDirty();
|
|
5740
5805
|
}
|
|
@@ -6615,54 +6680,58 @@ class LinkOpenerExtension extends LexxyExtension {
|
|
|
6615
6680
|
get lexicalExtension() {
|
|
6616
6681
|
return defineExtension({
|
|
6617
6682
|
name: "lexxy/link-opener",
|
|
6618
|
-
register: () =>
|
|
6619
|
-
|
|
6620
|
-
|
|
6621
|
-
|
|
6622
|
-
|
|
6623
|
-
|
|
6624
|
-
|
|
6625
|
-
}
|
|
6683
|
+
register: (editor) => mergeRegister$1(
|
|
6684
|
+
editor.registerCommand(CLICK_COMMAND, this.#handleClick.bind(this), COMMAND_PRIORITY_NORMAL),
|
|
6685
|
+
registerEventListener(this.editorElement.editorContentElement, "auxclick", this.#handleAuxClick.bind(this)),
|
|
6686
|
+
registerEventListener(window, "keydown", this.#handleKey.bind(this)),
|
|
6687
|
+
registerEventListener(window, "keyup", this.#handleKey.bind(this)),
|
|
6688
|
+
registerEventListener(window, "focus", this.#handleFocus.bind(this))
|
|
6689
|
+
)
|
|
6626
6690
|
})
|
|
6627
6691
|
}
|
|
6628
6692
|
|
|
6629
|
-
#
|
|
6693
|
+
#handleClick(event) {
|
|
6630
6694
|
if (this.#isModified(event)) {
|
|
6631
|
-
|
|
6695
|
+
return $openLink(event.target)
|
|
6632
6696
|
} else {
|
|
6633
|
-
|
|
6697
|
+
return false
|
|
6634
6698
|
}
|
|
6635
6699
|
}
|
|
6636
6700
|
|
|
6637
|
-
#
|
|
6638
|
-
|
|
6639
|
-
|
|
6640
|
-
|
|
6641
|
-
}, 200);
|
|
6701
|
+
#handleAuxClick(event) {
|
|
6702
|
+
if (event.button === 1) {
|
|
6703
|
+
this.editorElement.editor.read(() => $openLink(event.target));
|
|
6704
|
+
}
|
|
6642
6705
|
}
|
|
6643
6706
|
|
|
6644
|
-
#
|
|
6645
|
-
|
|
6707
|
+
#handleKey(event) {
|
|
6708
|
+
this.#updateOpenableAttribute(event);
|
|
6646
6709
|
}
|
|
6647
6710
|
|
|
6648
|
-
|
|
6649
|
-
|
|
6650
|
-
|
|
6651
|
-
|
|
6652
|
-
anchor.setAttribute("rel", "noopener noreferrer");
|
|
6653
|
-
}
|
|
6711
|
+
// Chrome dispatches events without modifier keys *for a while* after changing tabs
|
|
6712
|
+
async #handleFocus() {
|
|
6713
|
+
await delay(200);
|
|
6714
|
+
this.editorElement.addEventListener("mousemove", this.#updateOpenableAttribute.bind(this), { once: true });
|
|
6654
6715
|
}
|
|
6655
6716
|
|
|
6656
|
-
#
|
|
6657
|
-
|
|
6658
|
-
anchor.removeAttribute("contenteditable");
|
|
6659
|
-
anchor.removeAttribute("target");
|
|
6660
|
-
anchor.removeAttribute("rel");
|
|
6661
|
-
}
|
|
6717
|
+
#updateOpenableAttribute(event) {
|
|
6718
|
+
this.editorElement.toggleAttribute("data-links-openable", this.#isModified(event));
|
|
6662
6719
|
}
|
|
6663
6720
|
|
|
6664
|
-
|
|
6665
|
-
return
|
|
6721
|
+
#isModified(event) {
|
|
6722
|
+
return IS_APPLE ? event.metaKey : event.ctrlKey
|
|
6723
|
+
}
|
|
6724
|
+
}
|
|
6725
|
+
|
|
6726
|
+
function $openLink(target) {
|
|
6727
|
+
const node = $getNearestNodeFromDOMNode(target);
|
|
6728
|
+
const linkNode = $findMatchingParent(node, $isLinkNode);
|
|
6729
|
+
if (linkNode) {
|
|
6730
|
+
const url = linkNode.sanitizeUrl(linkNode.getURL());
|
|
6731
|
+
window.open(url, "_blank", "noopener,noreferrer");
|
|
6732
|
+
return true
|
|
6733
|
+
} else {
|
|
6734
|
+
return false
|
|
6666
6735
|
}
|
|
6667
6736
|
}
|
|
6668
6737
|
|
|
@@ -6674,11 +6743,11 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
6674
6743
|
static observedAttributes = [ "connected", "required" ]
|
|
6675
6744
|
|
|
6676
6745
|
#initialValue = ""
|
|
6677
|
-
#initialValueLoaded = false
|
|
6678
6746
|
#validationTextArea = document.createElement("textarea")
|
|
6679
6747
|
#editorInitializedRafId = null
|
|
6680
6748
|
#listeners = new ListenerBin()
|
|
6681
6749
|
#disposables = []
|
|
6750
|
+
#historyState = { undo: false, redo: false }
|
|
6682
6751
|
|
|
6683
6752
|
constructor() {
|
|
6684
6753
|
super();
|
|
@@ -6770,6 +6839,7 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
6770
6839
|
HighlightExtension,
|
|
6771
6840
|
TrixContentExtension,
|
|
6772
6841
|
TablesExtension,
|
|
6842
|
+
RewritableHistoryExtension,
|
|
6773
6843
|
AttachmentsExtension,
|
|
6774
6844
|
FormatEscapeExtension,
|
|
6775
6845
|
LinkOpenerExtension
|
|
@@ -6876,28 +6946,22 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
6876
6946
|
}
|
|
6877
6947
|
|
|
6878
6948
|
set value(html) {
|
|
6879
|
-
const wasEmpty = !this.#initialValueLoaded;
|
|
6880
|
-
|
|
6881
6949
|
this.editor.update(() => {
|
|
6882
|
-
$
|
|
6883
|
-
|
|
6884
|
-
|
|
6885
|
-
|
|
6886
|
-
root.selectEnd();
|
|
6950
|
+
$getRoot()
|
|
6951
|
+
.clear()
|
|
6952
|
+
.selectEnd()
|
|
6953
|
+
.insertNodes(this.#parseHtmlIntoLexicalNodes(html));
|
|
6887
6954
|
|
|
6888
6955
|
this.#toggleEmptyStatus();
|
|
6956
|
+
}, { discrete: true, tag: SKIP_DOM_SELECTION_TAG });
|
|
6957
|
+
}
|
|
6889
6958
|
|
|
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
|
-
});
|
|
6959
|
+
get canUndo() {
|
|
6960
|
+
return this.#historyState.undo
|
|
6961
|
+
}
|
|
6899
6962
|
|
|
6900
|
-
|
|
6963
|
+
get canRedo() {
|
|
6964
|
+
return this.#historyState.redo
|
|
6901
6965
|
}
|
|
6902
6966
|
|
|
6903
6967
|
#parseHtmlIntoLexicalNodes(html) {
|
|
@@ -6933,6 +6997,7 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
6933
6997
|
this.#registerComponents();
|
|
6934
6998
|
this.#handleEnter();
|
|
6935
6999
|
this.#registerFocusEvents();
|
|
7000
|
+
this.#registerHistoryEvents();
|
|
6936
7001
|
this.#attachDebugHooks();
|
|
6937
7002
|
this.#attachToolbar();
|
|
6938
7003
|
this.#configureSanitizer();
|
|
@@ -7029,8 +7094,10 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
7029
7094
|
}
|
|
7030
7095
|
|
|
7031
7096
|
#loadInitialValue() {
|
|
7032
|
-
const initialHtml = this.valueBeforeDisconnect || this.getAttribute("value") || "<p></p>";
|
|
7033
|
-
this.
|
|
7097
|
+
const initialHtml = this.valueBeforeDisconnect || this.getAttribute("value") || "<p><br></p>";
|
|
7098
|
+
this.editor.update(() => {
|
|
7099
|
+
this.value = this.#initialValue = initialHtml;
|
|
7100
|
+
}, { tag: HISTORY_MERGE_TAG });
|
|
7034
7101
|
}
|
|
7035
7102
|
|
|
7036
7103
|
#resetBeforeTurboCaches() {
|
|
@@ -7080,8 +7147,6 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
7080
7147
|
} else {
|
|
7081
7148
|
registered.push(registerPlainText(this.editor));
|
|
7082
7149
|
}
|
|
7083
|
-
this.historyState = createEmptyHistoryState();
|
|
7084
|
-
registered.push(registerHistory(this.editor, this.historyState, 20));
|
|
7085
7150
|
|
|
7086
7151
|
this.#listeners.track(...registered);
|
|
7087
7152
|
}
|
|
@@ -7164,6 +7229,12 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
7164
7229
|
}
|
|
7165
7230
|
}
|
|
7166
7231
|
|
|
7232
|
+
#registerHistoryEvents() {
|
|
7233
|
+
this.#listeners.track(
|
|
7234
|
+
this.editor.registerCommand(CAN_UNDO_COMMAND, (enabled) => { this.#historyState.undo = enabled; }, COMMAND_PRIORITY_NORMAL),
|
|
7235
|
+
this.editor.registerCommand(CAN_REDO_COMMAND, (enabled) => { this.#historyState.redo = enabled; }, COMMAND_PRIORITY_NORMAL)
|
|
7236
|
+
);
|
|
7237
|
+
}
|
|
7167
7238
|
|
|
7168
7239
|
#attachDebugHooks() {
|
|
7169
7240
|
return
|
|
@@ -7254,8 +7325,8 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
7254
7325
|
heading: { active: format.isInHeading, enabled: true },
|
|
7255
7326
|
"unordered-list": { active: format.isInList && format.listType === "bullet", enabled: true },
|
|
7256
7327
|
"ordered-list": { active: format.isInList && format.listType === "number", enabled: true },
|
|
7257
|
-
undo: { active: false, enabled: this.
|
|
7258
|
-
redo: { active: false, enabled: this.
|
|
7328
|
+
undo: { active: false, enabled: this.canUndo },
|
|
7329
|
+
redo: { active: false, enabled: this.canRedo }
|
|
7259
7330
|
};
|
|
7260
7331
|
|
|
7261
7332
|
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.preview2",
|
|
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
|
},
|