@37signals/lexxy 0.9.9-beta.preview3.domselection1 → 0.9.9-beta.preview5

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 CHANGED
@@ -1,14 +1,14 @@
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, $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, $onUpdate, KEY_ENTER_COMMAND, COMMAND_PRIORITY_CRITICAL, KEY_SPACE_COMMAND, KEY_BACKSPACE_COMMAND, KEY_DOWN_COMMAND } from 'lexical';
4
+ import { getStyleObjectFromCSS, getCSSFromStyleObject, $ensureForwardRangeSelection, $isAtNodeEnd, $getSelectionStyleValueForProperty, $patchStyleText, $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, $isParagraphNode, $splitNode, $getSiblingCaret, LineBreakNode, 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, $getChildCaretAtIndex, $createLineBreakNode, ParagraphNode, RootNode, COMMAND_PRIORITY_HIGH, DRAGSTART_COMMAND, DROP_COMMAND, INSERT_PARAGRAPH_COMMAND, mergeRegister as mergeRegister$1, $findMatchingParent, CLEAR_HISTORY_COMMAND, $onUpdate, 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
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, QuoteNode, $createHeadingNode, $createQuoteNode, HeadingNode, registerRichText } from '@lexical/rich-text';
11
+ import { RichTextExtension, $isQuoteNode, $isHeadingNode, $createHeadingNode, $createQuoteNode, QuoteNode, 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';
@@ -1460,6 +1460,40 @@ function isAttachmentSpacerTextNode(node, previousNode, index, childCount) {
1460
1460
  && previousNode instanceof CustomActionTextAttachmentNode
1461
1461
  }
1462
1462
 
1463
+ function $splitParagraphsAtLineBreakBoundaries(selection) {
1464
+ $ensureForwardRangeSelection(selection);
1465
+
1466
+ // Split focus first so the anchor split position stays valid.
1467
+ $splitAtNearestLineBreak(selection.focus, "next");
1468
+ $splitAtNearestLineBreak(selection.anchor, "previous");
1469
+ }
1470
+
1471
+ function $splitAtNearestLineBreak(point, direction) {
1472
+ const paragraph = point.getNode().getTopLevelElement();
1473
+ if (!paragraph || !$isParagraphNode(paragraph)) return
1474
+
1475
+ const pointNode = point.getNode();
1476
+ const selectionChild = pointNode.getParent().is(paragraph) ? pointNode : pointNode.getParentOrThrow();
1477
+ const lineBreakCaret = $caretAtNearestNodeOfType(selectionChild, LineBreakNode, direction);
1478
+ if (!lineBreakCaret) return
1479
+
1480
+ const lineBreak = lineBreakCaret.origin;
1481
+ const isEdge = lineBreakCaret.getNodeAtCaret() === null;
1482
+
1483
+ if (!isEdge) {
1484
+ $splitNode(paragraph, lineBreak.getIndexWithinParent());
1485
+ }
1486
+
1487
+ lineBreak.remove();
1488
+ }
1489
+
1490
+ function $caretAtNearestNodeOfType(node, klass, direction) {
1491
+ for (const caret of $getSiblingCaret(node, direction)) {
1492
+ if (caret.origin instanceof klass) return caret
1493
+ }
1494
+ return null
1495
+ }
1496
+
1463
1497
  // Shared, strictly-contained element used to attach ephemeral nodes when we
1464
1498
  // need to read computed styles (e.g. canonicalizing style values, resolving
1465
1499
  // CSS custom properties). The container is created once and attached to
@@ -4646,7 +4680,6 @@ class NodeInserter {
4646
4680
  static for(selection) {
4647
4681
  const INSERTERS = [
4648
4682
  CodeNodeInserter,
4649
- QuoteNodeInserter,
4650
4683
  ShadowRootNodeInserter,
4651
4684
  NodeSelectionNodeInserter
4652
4685
  ];
@@ -4695,25 +4728,6 @@ class CodeNodeInserter extends NodeInserter {
4695
4728
 
4696
4729
  }
4697
4730
 
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
4731
  class ShadowRootNodeInserter extends NodeInserter {
4718
4732
  static handles(selection) {
4719
4733
  return $isShadowRoot(selection?.anchor.getNode())
@@ -4861,7 +4875,7 @@ class Contents {
4861
4875
  } else {
4862
4876
  topLevelElements.filter($isQuoteNode).forEach(node => this.#unwrap(node));
4863
4877
 
4864
- this.#splitParagraphsAtLineBreaks(selection);
4878
+ $splitParagraphsAtLineBreakBoundaries(selection);
4865
4879
 
4866
4880
  const elements = this.#topLevelElementsInSelection(selection);
4867
4881
  if (elements.length === 0) return
@@ -5364,7 +5378,13 @@ class Clipboard {
5364
5378
  paste(event) {
5365
5379
  const clipboardData = event.clipboardData;
5366
5380
 
5367
- if (!clipboardData || this.#isPastingIntoCodeBlock()) return false
5381
+ if (!clipboardData) return false
5382
+
5383
+ if (this.#isPastingIntoCodeBlock()) {
5384
+ this.#pastePlainTextIntoCodeBlock(clipboardData);
5385
+ event.preventDefault();
5386
+ return true
5387
+ }
5368
5388
 
5369
5389
  if (this.#isPlainTextOrURLPasted(clipboardData)) {
5370
5390
  this.#pastePlainText(clipboardData);
@@ -5413,6 +5433,16 @@ class Clipboard {
5413
5433
  return result
5414
5434
  }
5415
5435
 
5436
+ #pastePlainTextIntoCodeBlock(clipboardData) {
5437
+ const text = clipboardData.getData("text/plain");
5438
+ if (!text) return
5439
+
5440
+ this.editor.update(() => {
5441
+ const selection = $getSelection();
5442
+ if ($isRangeSelection(selection)) selection.insertRawText(text);
5443
+ }, { tag: PASTE_TAG });
5444
+ }
5445
+
5416
5446
  #pastePlainText(clipboardData) {
5417
5447
  const item = clipboardData.items[0];
5418
5448
  item.getAsString((text) => {
@@ -6510,22 +6540,17 @@ class EarlyEscapeCodeNode extends CodeNode {
6510
6540
  }
6511
6541
 
6512
6542
  insertNewAfter(selection, restoreSelection) {
6513
- if (!selection.isCollapsed()) return super.insertNewAfter(selection, restoreSelection)
6514
-
6515
- if (this.#isCursorAtStart(selection)) {
6516
- this.insertBefore($createParagraphNode());
6517
- return null
6518
- }
6519
-
6520
- if (this.#isCursorOnEmptyLastLine(selection)) {
6521
- $trimTrailingBlankNodes(this);
6522
-
6523
- const paragraph = $createParagraphNode();
6524
- this.insertAfter(paragraph);
6525
- return paragraph
6543
+ if ($hasUpdateTag(PASTE_TAG) || !selection.isCollapsed()) {
6544
+ return super.insertNewAfter(selection, restoreSelection)
6545
+ } else if (this.#isCursorAtStart(selection)) {
6546
+ return this.#insertParagraphBefore()
6547
+ } else if (this.#isCursorOnWhitespaceOnlyLastLine(selection)) {
6548
+ return this.#insertBlankLineBelow(selection, restoreSelection)
6549
+ } else if (this.#isCursorOnEmptyLastLine(selection)) {
6550
+ return this.#escapeToNewParagraphAfter()
6551
+ } else {
6552
+ return super.insertNewAfter(selection, restoreSelection)
6526
6553
  }
6527
-
6528
- return super.insertNewAfter(selection, restoreSelection)
6529
6554
  }
6530
6555
 
6531
6556
  #isCursorAtStart(selection) {
@@ -6543,6 +6568,32 @@ class EarlyEscapeCodeNode extends CodeNode {
6543
6568
  return textContent === "" || textContent.endsWith("\n")
6544
6569
  }
6545
6570
 
6571
+ #isCursorOnWhitespaceOnlyLastLine(selection) {
6572
+ if (!$isCursorOnLastLine(selection)) return false
6573
+
6574
+ const textContent = this.getTextContent();
6575
+ const lastNewlineIndex = textContent.lastIndexOf("\n");
6576
+ const lastLine = lastNewlineIndex === -1 ? textContent : textContent.slice(lastNewlineIndex + 1);
6577
+ return lastLine.length > 0 && lastLine.trim() === ""
6578
+ }
6579
+
6580
+ #insertParagraphBefore() {
6581
+ this.insertBefore($createParagraphNode());
6582
+ return null
6583
+ }
6584
+
6585
+ #insertBlankLineBelow(selection, restoreSelection) {
6586
+ super.insertNewAfter(selection, restoreSelection);
6587
+ this.getLastChild().remove();
6588
+ return null
6589
+ }
6590
+
6591
+ #escapeToNewParagraphAfter() {
6592
+ $trimTrailingBlankNodes(this);
6593
+ const paragraph = $createParagraphNode();
6594
+ this.insertAfter(paragraph);
6595
+ return paragraph
6596
+ }
6546
6597
  }
6547
6598
 
6548
6599
  class EarlyEscapeListItemNode extends ListItemNode {
@@ -114,7 +114,7 @@ function highlightElement(preElement) {
114
114
  applyHighlightRanges(codeElement, highlights);
115
115
  }
116
116
 
117
- preElement.replaceWith(codeElement);
117
+ preElement.replaceChildren(codeElement);
118
118
  }
119
119
 
120
120
  // Walk the DOM tree inside a <pre> element and build a list of
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@37signals/lexxy",
3
- "version": "0.9.9-beta.preview3.domselection1",
3
+ "version": "0.9.9-beta.preview5",
4
4
  "description": "Lexxy - A modern rich text editor for Rails.",
5
5
  "module": "dist/lexxy.esm.js",
6
6
  "type": "module",