@37signals/lexxy 0.9.18 → 0.9.19-alpha.1
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 +99 -16
- package/package.json +1 -1
package/dist/lexxy.esm.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { highlightCode, highlightElement } from './lexxy_helpers.esm.js';
|
|
2
2
|
import DOMPurify from 'dompurify';
|
|
3
3
|
import { getStyleObjectFromCSS, getCSSFromStyleObject, $getSelectionStyleValueForProperty, $ensureForwardRangeSelection, $isAtNodeEnd, $patchStyleText, $setBlocksType, $forEachSelectedTextNode } from '@lexical/selection';
|
|
4
|
-
import { SKIP_DOM_SELECTION_TAG, CAN_UNDO_COMMAND, COMMAND_PRIORITY_LOW, CAN_REDO_COMMAND, $getSelection, $isRangeSelection, DecoratorNode, $createTextNode, $getRoot, $caretFromPoint, $setSelectionFromCaretRange, $getCaretRange, $normalizeCaret, $getChildCaret, $getCaretInDirection, $isParagraphNode, $isLineBreakNode, $createParagraphNode, $isElementNode, $isRootOrShadowRoot, $isRootNode, $createNodeSelection, $isDecoratorNode, $isTextNode, $getSiblingCaret, $rewindSiblingCaret, $splitAtPointCaretNext, $isChildCaret, $isTextPointCaret, $isExtendableTextPointCaret, $isSiblingCaret, $getCommonAncestor, $findMatchingParent, TextNode, createCommand, defineExtension, COMMAND_PRIORITY_EDITOR, $getEditor, $getNodeByKey, HISTORY_MERGE_TAG, SKIP_SCROLL_INTO_VIEW_TAG, $cloneWithProperties, $getNearestRootOrShadowRoot, $createRangeSelection, $setSelection, createState, COMMAND_PRIORITY_NORMAL, $getState, $setState, $hasUpdateTag, PASTE_TAG, FORMAT_TEXT_COMMAND, UNDO_COMMAND, REDO_COMMAND, KEY_ARROW_RIGHT_COMMAND, KEY_TAB_COMMAND, OUTDENT_CONTENT_COMMAND, INDENT_CONTENT_COMMAND, $isNodeSelection, 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, SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, PASTE_COMMAND, $onUpdate, ParagraphNode, RootNode, COMMAND_PRIORITY_HIGH, DRAGSTART_COMMAND, DROP_COMMAND, INSERT_PARAGRAPH_COMMAND, mergeRegister as mergeRegister$1, CLEAR_HISTORY_COMMAND, KEY_ENTER_COMMAND, COMMAND_PRIORITY_CRITICAL, KEY_SPACE_COMMAND, INPUT_COMMAND, KEY_BACKSPACE_COMMAND, KEY_DOWN_COMMAND } from 'lexical';
|
|
4
|
+
import { SKIP_DOM_SELECTION_TAG, CAN_UNDO_COMMAND, COMMAND_PRIORITY_LOW, CAN_REDO_COMMAND, $getSelection, $isRangeSelection, DecoratorNode, $createTextNode, $getRoot, $caretFromPoint, $setSelectionFromCaretRange, $getCaretRange, $normalizeCaret, $getChildCaret, $getCaretInDirection, $isParagraphNode, $isLineBreakNode, $createParagraphNode, $isElementNode, $isRootOrShadowRoot, $isRootNode, $createNodeSelection, $isDecoratorNode, $isTextNode, $getSiblingCaret, $rewindSiblingCaret, $splitAtPointCaretNext, $isChildCaret, $isTextPointCaret, $isExtendableTextPointCaret, $isSiblingCaret, $getCommonAncestor, $findMatchingParent, TextNode, createCommand, defineExtension, COMMAND_PRIORITY_EDITOR, $getEditor, $getNodeByKey, HISTORY_MERGE_TAG, SKIP_SCROLL_INTO_VIEW_TAG, $cloneWithProperties, $getNearestRootOrShadowRoot, $createRangeSelection, $setSelection, createState, COMMAND_PRIORITY_NORMAL, $getState, $setState, $hasUpdateTag, PASTE_TAG, FORMAT_TEXT_COMMAND, UNDO_COMMAND, REDO_COMMAND, KEY_ARROW_RIGHT_COMMAND, KEY_TAB_COMMAND, OUTDENT_CONTENT_COMMAND, INDENT_CONTENT_COMMAND, $isNodeSelection, 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, $normalizeSelection__EXPERIMENTAL, SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, PASTE_COMMAND, $onUpdate, ParagraphNode, RootNode, COMMAND_PRIORITY_HIGH, DRAGSTART_COMMAND, DROP_COMMAND, INSERT_PARAGRAPH_COMMAND, mergeRegister as mergeRegister$1, CLEAR_HISTORY_COMMAND, KEY_ENTER_COMMAND, COMMAND_PRIORITY_CRITICAL, KEY_SPACE_COMMAND, INPUT_COMMAND, KEY_BACKSPACE_COMMAND, KEY_DOWN_COMMAND } from 'lexical';
|
|
5
5
|
import { LinkNode, $createAutoLinkNode, $toggleLink, $createLinkNode, $isLinkNode, AutoLinkNode } from '@lexical/link';
|
|
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';
|
|
@@ -4031,9 +4031,12 @@ class Selection {
|
|
|
4031
4031
|
}
|
|
4032
4032
|
|
|
4033
4033
|
get isOnPreviewableImage() {
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4034
|
+
return this.previewableImageNode != null
|
|
4035
|
+
}
|
|
4036
|
+
|
|
4037
|
+
get previewableImageNode() {
|
|
4038
|
+
const firstNode = $getSelection()?.getNodes().at(0);
|
|
4039
|
+
return $isActionTextAttachmentNode(firstNode) && firstNode.isPreviewableImage ? firstNode : null
|
|
4037
4040
|
}
|
|
4038
4041
|
|
|
4039
4042
|
get isAtNodeStart() {
|
|
@@ -5008,7 +5011,10 @@ class GalleryUploader extends Uploader {
|
|
|
5008
5011
|
|
|
5009
5012
|
#findOrCreateGallery() {
|
|
5010
5013
|
if (this.selection.isOnPreviewableImage) {
|
|
5011
|
-
|
|
5014
|
+
// Resolve from the previewable image itself (the selection's first node), not from
|
|
5015
|
+
// #selectedNode (the anchor) — those differ when the selection runs from an image
|
|
5016
|
+
// into following text, and the anchor text node can't join a gallery (returns null).
|
|
5017
|
+
this.#gallery = $findOrCreateGalleryForImage(this.selection.previewableImageNode);
|
|
5012
5018
|
} else if (this.#selectionIsAfterGalleryEdge) {
|
|
5013
5019
|
this.#gallery = $findOrCreateGalleryForImage(this.selection.nodeBeforeCursor);
|
|
5014
5020
|
} else {
|
|
@@ -5345,7 +5351,8 @@ class NodeInserter {
|
|
|
5345
5351
|
const INSERTERS = [
|
|
5346
5352
|
CodeNodeInserter,
|
|
5347
5353
|
ShadowRootNodeInserter,
|
|
5348
|
-
NodeSelectionNodeInserter
|
|
5354
|
+
NodeSelectionNodeInserter,
|
|
5355
|
+
BlockContainerNodeInserter
|
|
5349
5356
|
];
|
|
5350
5357
|
const Inserter = INSERTERS.find(inserter => inserter.handles(selection));
|
|
5351
5358
|
return Inserter ? new Inserter(selection) : selection
|
|
@@ -5371,14 +5378,40 @@ class CodeNodeInserter extends NodeInserter {
|
|
|
5371
5378
|
|
|
5372
5379
|
const caret = $getChildCaretAtIndex(codeNode, insertionIndex + 1, "previous");
|
|
5373
5380
|
|
|
5381
|
+
// Nodes that are already in the document come from the format-toggle path (existing
|
|
5382
|
+
// content converted into this code block). Brand-new nodes (dropped/pasted content)
|
|
5383
|
+
// were never attached.
|
|
5384
|
+
const existingNodes = new Set(nodes.filter(node => node.isAttached()));
|
|
5385
|
+
const trailingNodes = [];
|
|
5386
|
+
|
|
5374
5387
|
for (const node of nodes) {
|
|
5375
|
-
if (
|
|
5376
|
-
|
|
5388
|
+
if (existingNodes.has(node)) {
|
|
5389
|
+
if (!node.isAttached()) continue // already pulled in when a converted ancestor was removed
|
|
5390
|
+
} else if (!this.#canJoinCodeBlock(node)) {
|
|
5391
|
+
trailingNodes.push(node); // e.g. a dropped attachment, which a code block can't hold
|
|
5392
|
+
continue
|
|
5393
|
+
}
|
|
5377
5394
|
|
|
5395
|
+
if (caret.getNodeAtCaret() && $isElementNode(node)) { caret.insert($createLineBreakNode()); }
|
|
5378
5396
|
caret.insert(this.#convertNodeToCodeChild(node));
|
|
5379
5397
|
}
|
|
5380
5398
|
|
|
5381
|
-
|
|
5399
|
+
const lastTrailingNode = this.#insertAfterCodeBlock(codeNode, trailingNodes);
|
|
5400
|
+
const nodeToSelect = lastTrailingNode ?? caret.getNodeAtCaret();
|
|
5401
|
+
nodeToSelect?.selectEnd();
|
|
5402
|
+
}
|
|
5403
|
+
|
|
5404
|
+
#canJoinCodeBlock(node) {
|
|
5405
|
+
return $isTextNode(node) || $isLineBreakNode(node)
|
|
5406
|
+
}
|
|
5407
|
+
|
|
5408
|
+
#insertAfterCodeBlock(codeNode, nodes) {
|
|
5409
|
+
let previousNode = codeNode;
|
|
5410
|
+
for (const node of nodes) {
|
|
5411
|
+
previousNode.insertAfter(node);
|
|
5412
|
+
previousNode = node;
|
|
5413
|
+
}
|
|
5414
|
+
return nodes.at(-1)
|
|
5382
5415
|
}
|
|
5383
5416
|
|
|
5384
5417
|
#convertNodeToCodeChild(node) {
|
|
@@ -5394,7 +5427,7 @@ class CodeNodeInserter extends NodeInserter {
|
|
|
5394
5427
|
|
|
5395
5428
|
class ShadowRootNodeInserter extends NodeInserter {
|
|
5396
5429
|
static handles(selection) {
|
|
5397
|
-
return $isShadowRoot(selection?.anchor
|
|
5430
|
+
return $isShadowRoot(selection?.anchor?.getNode())
|
|
5398
5431
|
}
|
|
5399
5432
|
|
|
5400
5433
|
insertNodes(nodes) {
|
|
@@ -5423,6 +5456,31 @@ class NodeSelectionNodeInserter extends NodeInserter {
|
|
|
5423
5456
|
}
|
|
5424
5457
|
}
|
|
5425
5458
|
|
|
5459
|
+
// Lexical's RangeSelection.insertNodes requires every selection point to have a block
|
|
5460
|
+
// ancestor with inline children. An element point on a container of block nodes — e.g.
|
|
5461
|
+
// a quote holding paragraphs — has none, so Lexical throws invariant #211 or #212.
|
|
5462
|
+
// Descend such points to a leaf position before inserting.
|
|
5463
|
+
class BlockContainerNodeInserter extends NodeInserter {
|
|
5464
|
+
static handles(selection) {
|
|
5465
|
+
return $isRangeSelection(selection) &&
|
|
5466
|
+
[ selection.anchor, selection.focus ].some($isPointOnBlockContainer)
|
|
5467
|
+
}
|
|
5468
|
+
|
|
5469
|
+
insertNodes(nodes) {
|
|
5470
|
+
$normalizeSelection__EXPERIMENTAL(this.selection);
|
|
5471
|
+
this.selection.insertNodes(nodes);
|
|
5472
|
+
}
|
|
5473
|
+
}
|
|
5474
|
+
|
|
5475
|
+
function $isPointOnBlockContainer(point) {
|
|
5476
|
+
if (point.type === "element") {
|
|
5477
|
+
const firstChild = point.getNode().getFirstChild();
|
|
5478
|
+
return ($isElementNode(firstChild) || $isDecoratorNode(firstChild)) && !firstChild.isInline()
|
|
5479
|
+
} else {
|
|
5480
|
+
return false
|
|
5481
|
+
}
|
|
5482
|
+
}
|
|
5483
|
+
|
|
5426
5484
|
class Contents {
|
|
5427
5485
|
constructor(editorElement) {
|
|
5428
5486
|
this.editorElement = editorElement;
|
|
@@ -5519,7 +5577,7 @@ class Contents {
|
|
|
5519
5577
|
blockElements.forEach(node => this.#unwrapCodeBlock(node));
|
|
5520
5578
|
} else {
|
|
5521
5579
|
$expandSelectionToLineBreaksAndSplitAtEdges(selection);
|
|
5522
|
-
const elements = this.#blockLevelElementsInSelection(selection);
|
|
5580
|
+
const elements = this.#outermostElements(this.#blockLevelElementsInSelection(selection));
|
|
5523
5581
|
if (elements.length === 0) return
|
|
5524
5582
|
|
|
5525
5583
|
const codeNode = $createCodeNode("plain");
|
|
@@ -5655,6 +5713,8 @@ class Contents {
|
|
|
5655
5713
|
}
|
|
5656
5714
|
|
|
5657
5715
|
uploadFiles(files, { selectLast } = {}) {
|
|
5716
|
+
if (!this.editorElement) return // Disposed (e.g. on turbo:before-cache); a late drop can still land here
|
|
5717
|
+
|
|
5658
5718
|
if (!this.editorElement.supportsAttachments) {
|
|
5659
5719
|
console.warn("This editor does not supports attachments (it's configured with [attachments=false])");
|
|
5660
5720
|
return
|
|
@@ -5845,6 +5905,18 @@ class Contents {
|
|
|
5845
5905
|
return Array.from(elements)
|
|
5846
5906
|
}
|
|
5847
5907
|
|
|
5908
|
+
// Selections spanning nested structures (a quote and its inner paragraphs,
|
|
5909
|
+
// nested list items) yield both an element and its ancestor. Converting the
|
|
5910
|
+
// ancestor detaches its whole subtree — including a node freshly inserted
|
|
5911
|
+
// inside it — which can leave the selection on removed nodes (Lexical
|
|
5912
|
+
// invariant #19). The outermost elements already cover their descendants'
|
|
5913
|
+
// text content, so keep only those.
|
|
5914
|
+
#outermostElements(elements) {
|
|
5915
|
+
return elements.filter((element) => {
|
|
5916
|
+
return elements.every((other) => other === element || !element.getParents().includes(other))
|
|
5917
|
+
})
|
|
5918
|
+
}
|
|
5919
|
+
|
|
5848
5920
|
#insertUploadNodes(nodes) {
|
|
5849
5921
|
if (nodes.every($isActionTextAttachmentNode)) {
|
|
5850
5922
|
const uploader = Uploader.for(this.editorElement, []);
|
|
@@ -7372,12 +7444,23 @@ class EarlyEscapeListItemNode extends ListItemNode {
|
|
|
7372
7444
|
return super.insertNewAfter(selection, restoreSelection)
|
|
7373
7445
|
}
|
|
7374
7446
|
|
|
7447
|
+
get #isInBlockquote() {
|
|
7448
|
+
return Boolean($getNearestNodeOfType(this, QuoteNode))
|
|
7449
|
+
}
|
|
7450
|
+
|
|
7375
7451
|
#shouldEscape(selection) {
|
|
7376
|
-
if (
|
|
7377
|
-
|
|
7452
|
+
if (this.#isInPasteOperation() || !this.#isInBlockquote) {
|
|
7453
|
+
return false
|
|
7454
|
+
} else if ($isBlankNode(this)) {
|
|
7455
|
+
return true
|
|
7456
|
+
} else {
|
|
7457
|
+
const paragraph = $getNearestNodeOfType(selection.anchor.getNode(), ParagraphNode);
|
|
7458
|
+
return paragraph && $isBlankNode(paragraph) && $isListItemNode(paragraph.getParent())
|
|
7459
|
+
}
|
|
7460
|
+
}
|
|
7378
7461
|
|
|
7379
|
-
|
|
7380
|
-
return
|
|
7462
|
+
#isInPasteOperation() {
|
|
7463
|
+
return $hasUpdateTag(PASTE_TAG)
|
|
7381
7464
|
}
|
|
7382
7465
|
|
|
7383
7466
|
#escapeFromList() {
|
|
@@ -7425,7 +7508,7 @@ class FormatEscapeExtension extends LexxyExtension {
|
|
|
7425
7508
|
}
|
|
7426
7509
|
|
|
7427
7510
|
get allowedElements() {
|
|
7428
|
-
return [ { tag: "li", attributes: [ "value" ] } ]
|
|
7511
|
+
return [ { tag: "ol", attributes: [ "start" ] }, { tag: "li", attributes: [ "value" ] } ]
|
|
7429
7512
|
}
|
|
7430
7513
|
|
|
7431
7514
|
get lexicalExtension() {
|