lexxy 0.9.17 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bd7e6d4971352233c9eaf05d0501f780dcf7029822385349c310a4c84c7198a6
4
- data.tar.gz: 62ee0a3aa3009089c6bfbd3bd089afe40e4e7abe7cdba70efe45e20b137d0afe
3
+ metadata.gz: f6b15ddc26ce7b82fd871ed3f5dfa96598b84278da3af81c7c8e7ed1c7d8135b
4
+ data.tar.gz: eedb6f7b9cca87c16c1737c6db392b2fb8a38f560ca20f3c615dbd865afafbfc
5
5
  SHA512:
6
- metadata.gz: 77269a9a5bc4bfee9da077aec96649b68b7cf3a128f0b76c82c0d0b668d7e1e03a15ee83d04816e5dee8a4b82d90e98d7c230913a2b3a40d3cacf00637cbf41c
7
- data.tar.gz: 436e73b527e5e5e9305fa5d8803194f06359e84a6a7f19f35575ec27caa8336f96843ce342f1ded93f88cce8d34dc0f3c46fb163d6cc9e951cdf905f953c3ac3
6
+ metadata.gz: 6945b89f20390bc1f407d95de1f365ef97508b8e7c8ac4c8960d48df6a8d53e39cfcc39795988e8e4e77b37d2d4e60819ffc0faf0a8f1035b3acedaede71d978
7
+ data.tar.gz: 791d8ac7ffebbe6987fc70571bb04450e17e93571f89bd10abdcbf7586b0b3deed48208a0e035b66eceeb5cb42db373399894b2aead182c19139ab3cc0f11d5b
@@ -10357,9 +10357,12 @@ class Selection {
10357
10357
  }
10358
10358
 
10359
10359
  get isOnPreviewableImage() {
10360
- const selection = Qr();
10361
- const firstNode = selection?.getNodes().at(0);
10362
- return $isActionTextAttachmentNode(firstNode) && firstNode.isPreviewableImage
10360
+ return this.previewableImageNode != null
10361
+ }
10362
+
10363
+ get previewableImageNode() {
10364
+ const firstNode = Qr()?.getNodes().at(0);
10365
+ return $isActionTextAttachmentNode(firstNode) && firstNode.isPreviewableImage ? firstNode : null
10363
10366
  }
10364
10367
 
10365
10368
  get isAtNodeStart() {
@@ -11334,7 +11337,10 @@ class GalleryUploader extends Uploader {
11334
11337
 
11335
11338
  #findOrCreateGallery() {
11336
11339
  if (this.selection.isOnPreviewableImage) {
11337
- this.#gallery = $findOrCreateGalleryForImage(this.#selectedNode);
11340
+ // Resolve from the previewable image itself (the selection's first node), not from
11341
+ // #selectedNode (the anchor) — those differ when the selection runs from an image
11342
+ // into following text, and the anchor text node can't join a gallery (returns null).
11343
+ this.#gallery = $findOrCreateGalleryForImage(this.selection.previewableImageNode);
11338
11344
  } else if (this.#selectionIsAfterGalleryEdge) {
11339
11345
  this.#gallery = $findOrCreateGalleryForImage(this.selection.nodeBeforeCursor);
11340
11346
  } else {
@@ -11671,7 +11677,8 @@ class NodeInserter {
11671
11677
  const INSERTERS = [
11672
11678
  CodeNodeInserter,
11673
11679
  ShadowRootNodeInserter,
11674
- NodeSelectionNodeInserter
11680
+ NodeSelectionNodeInserter,
11681
+ BlockContainerNodeInserter
11675
11682
  ];
11676
11683
  const Inserter = INSERTERS.find(inserter => inserter.handles(selection));
11677
11684
  return Inserter ? new Inserter(selection) : selection
@@ -11697,14 +11704,40 @@ class CodeNodeInserter extends NodeInserter {
11697
11704
 
11698
11705
  const caret = lc(codeNode, insertionIndex + 1, "previous");
11699
11706
 
11707
+ // Nodes that are already in the document come from the format-toggle path (existing
11708
+ // content converted into this code block). Brand-new nodes (dropped/pasted content)
11709
+ // were never attached.
11710
+ const existingNodes = new Set(nodes.filter(node => node.isAttached()));
11711
+ const trailingNodes = [];
11712
+
11700
11713
  for (const node of nodes) {
11701
- if (!node.isAttached()) continue
11702
- if (caret.getNodeAtCaret() && Wi(node)) { caret.insert(or()); }
11714
+ if (existingNodes.has(node)) {
11715
+ if (!node.isAttached()) continue // already pulled in when a converted ancestor was removed
11716
+ } else if (!this.#canJoinCodeBlock(node)) {
11717
+ trailingNodes.push(node); // e.g. a dropped attachment, which a code block can't hold
11718
+ continue
11719
+ }
11703
11720
 
11721
+ if (caret.getNodeAtCaret() && Wi(node)) { caret.insert(or()); }
11704
11722
  caret.insert(this.#convertNodeToCodeChild(node));
11705
11723
  }
11706
11724
 
11707
- caret.getNodeAtCaret().selectEnd();
11725
+ const lastTrailingNode = this.#insertAfterCodeBlock(codeNode, trailingNodes);
11726
+ const nodeToSelect = lastTrailingNode ?? caret.getNodeAtCaret();
11727
+ nodeToSelect?.selectEnd();
11728
+ }
11729
+
11730
+ #canJoinCodeBlock(node) {
11731
+ return Tr(node) || sr(node)
11732
+ }
11733
+
11734
+ #insertAfterCodeBlock(codeNode, nodes) {
11735
+ let previousNode = codeNode;
11736
+ for (const node of nodes) {
11737
+ previousNode.insertAfter(node);
11738
+ previousNode = node;
11739
+ }
11740
+ return nodes.at(-1)
11708
11741
  }
11709
11742
 
11710
11743
  #convertNodeToCodeChild(node) {
@@ -11720,7 +11753,7 @@ class CodeNodeInserter extends NodeInserter {
11720
11753
 
11721
11754
  class ShadowRootNodeInserter extends NodeInserter {
11722
11755
  static handles(selection) {
11723
- return $isShadowRoot(selection?.anchor.getNode())
11756
+ return $isShadowRoot(selection?.anchor?.getNode())
11724
11757
  }
11725
11758
 
11726
11759
  insertNodes(nodes) {
@@ -11749,6 +11782,31 @@ class NodeSelectionNodeInserter extends NodeInserter {
11749
11782
  }
11750
11783
  }
11751
11784
 
11785
+ // Lexical's RangeSelection.insertNodes requires every selection point to have a block
11786
+ // ancestor with inline children. An element point on a container of block nodes — e.g.
11787
+ // a quote holding paragraphs — has none, so Lexical throws invariant #211 or #212.
11788
+ // Descend such points to a leaf position before inserting.
11789
+ class BlockContainerNodeInserter extends NodeInserter {
11790
+ static handles(selection) {
11791
+ return Fr(selection) &&
11792
+ [ selection.anchor, selection.focus ].some($isPointOnBlockContainer)
11793
+ }
11794
+
11795
+ insertNodes(nodes) {
11796
+ St$3(this.selection);
11797
+ this.selection.insertNodes(nodes);
11798
+ }
11799
+ }
11800
+
11801
+ function $isPointOnBlockContainer(point) {
11802
+ if (point.type === "element") {
11803
+ const firstChild = point.getNode().getFirstChild();
11804
+ return (Wi(firstChild) || ji(firstChild)) && !firstChild.isInline()
11805
+ } else {
11806
+ return false
11807
+ }
11808
+ }
11809
+
11752
11810
  class Contents {
11753
11811
  constructor(editorElement) {
11754
11812
  this.editorElement = editorElement;
@@ -11845,7 +11903,7 @@ class Contents {
11845
11903
  blockElements.forEach(node => this.#unwrapCodeBlock(node));
11846
11904
  } else {
11847
11905
  $expandSelectionToLineBreaksAndSplitAtEdges(selection);
11848
- const elements = this.#blockLevelElementsInSelection(selection);
11906
+ const elements = this.#outermostElements(this.#blockLevelElementsInSelection(selection));
11849
11907
  if (elements.length === 0) return
11850
11908
 
11851
11909
  const codeNode = I$1("plain");
@@ -11981,6 +12039,8 @@ class Contents {
11981
12039
  }
11982
12040
 
11983
12041
  uploadFiles(files, { selectLast } = {}) {
12042
+ if (!this.editorElement) return // Disposed (e.g. on turbo:before-cache); a late drop can still land here
12043
+
11984
12044
  if (!this.editorElement.supportsAttachments) {
11985
12045
  console.warn("This editor does not supports attachments (it's configured with [attachments=false])");
11986
12046
  return
@@ -12171,6 +12231,18 @@ class Contents {
12171
12231
  return Array.from(elements)
12172
12232
  }
12173
12233
 
12234
+ // Selections spanning nested structures (a quote and its inner paragraphs,
12235
+ // nested list items) yield both an element and its ancestor. Converting the
12236
+ // ancestor detaches its whole subtree — including a node freshly inserted
12237
+ // inside it — which can leave the selection on removed nodes (Lexical
12238
+ // invariant #19). The outermost elements already cover their descendants'
12239
+ // text content, so keep only those.
12240
+ #outermostElements(elements) {
12241
+ return elements.filter((element) => {
12242
+ return elements.every((other) => other === element || !element.getParents().includes(other))
12243
+ })
12244
+ }
12245
+
12174
12246
  #insertUploadNodes(nodes) {
12175
12247
  if (nodes.every($isActionTextAttachmentNode)) {
12176
12248
  const uploader = Uploader.for(this.editorElement, []);
@@ -13770,12 +13842,23 @@ class EarlyEscapeListItemNode extends ge$2 {
13770
13842
  return super.insertNewAfter(selection, restoreSelection)
13771
13843
  }
13772
13844
 
13845
+ get #isInBlockquote() {
13846
+ return Boolean(At$2(this, At$1))
13847
+ }
13848
+
13773
13849
  #shouldEscape(selection) {
13774
- if (!At$2(this, At$1)) return false
13775
- if ($isBlankNode(this)) return true
13850
+ if (this.#isInPasteOperation() || !this.#isInBlockquote) {
13851
+ return false
13852
+ } else if ($isBlankNode(this)) {
13853
+ return true
13854
+ } else {
13855
+ const paragraph = At$2(selection.anchor.getNode(), Zi);
13856
+ return paragraph && $isBlankNode(paragraph) && pe$2(paragraph.getParent())
13857
+ }
13858
+ }
13776
13859
 
13777
- const paragraph = At$2(selection.anchor.getNode(), Zi);
13778
- return paragraph && $isBlankNode(paragraph) && pe$2(paragraph.getParent())
13860
+ #isInPasteOperation() {
13861
+ return Es(jn)
13779
13862
  }
13780
13863
 
13781
13864
  #escapeFromList() {
@@ -13823,7 +13906,7 @@ class FormatEscapeExtension extends LexxyExtension {
13823
13906
  }
13824
13907
 
13825
13908
  get allowedElements() {
13826
- return [ { tag: "li", attributes: [ "value" ] } ]
13909
+ return [ { tag: "ol", attributes: [ "start" ] }, { tag: "li", attributes: [ "value" ] } ]
13827
13910
  }
13828
13911
 
13829
13912
  get lexicalExtension() {
@@ -14296,8 +14379,8 @@ class LexicalEditorElement extends HTMLElement {
14296
14379
  return this.#historyState.redo
14297
14380
  }
14298
14381
 
14299
- #readSanitizedEditorValue(editor = this.editor) {
14300
- return editor?.read(() => {
14382
+ #readSanitizedEditorValue() {
14383
+ return this.editor?.read(() => {
14301
14384
  return sanitize(ht$1(this.editor, null))
14302
14385
  }) ?? null
14303
14386
  }
@@ -14331,7 +14414,6 @@ class LexicalEditorElement extends HTMLElement {
14331
14414
  }
14332
14415
 
14333
14416
  #initialize() {
14334
- this.#synchronizeWithChanges();
14335
14417
  this.#registerComponents();
14336
14418
  this.#handleEnter();
14337
14419
  this.#registerFocusEvents();
@@ -14340,6 +14422,9 @@ class LexicalEditorElement extends HTMLElement {
14340
14422
  this.#attachDebugHooks();
14341
14423
  this.#attachToolbar();
14342
14424
  this.#resetBeforeTurboCaches();
14425
+
14426
+ this.#setInternalFormValue(this.value, { suppressEvent: true });
14427
+ this.#synchronizeWithChanges();
14343
14428
  }
14344
14429
 
14345
14430
  #registerFileAcceptFilter() {
@@ -14367,7 +14452,6 @@ class LexicalEditorElement extends HTMLElement {
14367
14452
  $initialEditorState: (editor) => {
14368
14453
  this.#configureSanitizer(editor);
14369
14454
  this.#loadInitialValue(editor);
14370
- this.#setInternalFormValue(this.#readSanitizedEditorValue(editor));
14371
14455
  },
14372
14456
  },
14373
14457
  ...this.extensions.lexicalExtensions
@@ -14435,13 +14519,13 @@ class LexicalEditorElement extends HTMLElement {
14435
14519
  return Array.from(this.attributes).filter(attribute => attribute.name.startsWith("aria-"))
14436
14520
  }
14437
14521
 
14438
- #setInternalFormValue(html) {
14439
- const changed = this.#previousInternalFormValue !== null && html !== this.#previousInternalFormValue;
14522
+ #setInternalFormValue(html, { suppressEvent = false } = {}) {
14523
+ const changed = html !== this.#previousInternalFormValue;
14440
14524
 
14441
14525
  this.internals.setFormValue(html);
14442
14526
  this.#previousInternalFormValue = html;
14443
14527
 
14444
- if (changed) {
14528
+ if (changed && !suppressEvent) {
14445
14529
  dispatch(this, "lexxy:change");
14446
14530
  }
14447
14531
  }
Binary file
Binary file