@37signals/lexxy 0.1.16-beta → 0.1.18-beta

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.
Files changed (2) hide show
  1. package/dist/lexxy.esm.js +53 -14
  2. package/package.json +1 -1
package/dist/lexxy.esm.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import DOMPurify from 'dompurify';
2
- import { $getSelection, $isRangeSelection, DecoratorNode, $getNodeByKey, HISTORY_MERGE_TAG, FORMAT_TEXT_COMMAND, UNDO_COMMAND, REDO_COMMAND, PASTE_COMMAND, COMMAND_PRIORITY_LOW, $isNodeSelection, $getRoot, $isLineBreakNode, $isTextNode, $isElementNode, KEY_ARROW_LEFT_COMMAND, KEY_ARROW_RIGHT_COMMAND, KEY_ARROW_UP_COMMAND, KEY_ARROW_DOWN_COMMAND, KEY_DELETE_COMMAND, KEY_BACKSPACE_COMMAND, SELECTION_CHANGE_COMMAND, $createNodeSelection, $setSelection, $createParagraphNode, $createTextNode, KEY_ENTER_COMMAND, COMMAND_PRIORITY_HIGH, $isParagraphNode, $insertNodes, $createLineBreakNode, CLEAR_HISTORY_COMMAND, $addUpdateTag, SKIP_DOM_SELECTION_TAG, createEditor, COMMAND_PRIORITY_NORMAL, KEY_TAB_COMMAND, KEY_SPACE_COMMAND } from 'lexical';
2
+ import { $getSelection, $isRangeSelection, DecoratorNode, $getNodeByKey, HISTORY_MERGE_TAG, FORMAT_TEXT_COMMAND, $createTextNode, UNDO_COMMAND, REDO_COMMAND, PASTE_COMMAND, COMMAND_PRIORITY_LOW, $isNodeSelection, $getRoot, $isLineBreakNode, $isTextNode, $isElementNode, KEY_ARROW_LEFT_COMMAND, KEY_ARROW_RIGHT_COMMAND, KEY_ARROW_UP_COMMAND, KEY_ARROW_DOWN_COMMAND, KEY_DELETE_COMMAND, KEY_BACKSPACE_COMMAND, SELECTION_CHANGE_COMMAND, $createNodeSelection, $setSelection, $createParagraphNode, KEY_ENTER_COMMAND, COMMAND_PRIORITY_HIGH, $isParagraphNode, $insertNodes, $createLineBreakNode, CLEAR_HISTORY_COMMAND, $addUpdateTag, SKIP_DOM_SELECTION_TAG, createEditor, COMMAND_PRIORITY_NORMAL, KEY_TAB_COMMAND, KEY_SPACE_COMMAND } from 'lexical';
3
3
  import { $isListNode, $isListItemNode, INSERT_UNORDERED_LIST_COMMAND, INSERT_ORDERED_LIST_COMMAND, $createListNode, ListNode, ListItemNode, registerList } from '@lexical/list';
4
4
  import { $isQuoteNode, $isHeadingNode, $createQuoteNode, $createHeadingNode, QuoteNode, HeadingNode, registerRichText } from '@lexical/rich-text';
5
5
  import { $isCodeNode, CodeNode, CodeHighlightNode, registerCodeHighlighting, CODE_LANGUAGE_FRIENDLY_NAME_MAP, normalizeCodeLang } from '@lexical/code';
6
- import { $isLinkNode, $toggleLink, $createLinkNode, LinkNode, AutoLinkNode } from '@lexical/link';
6
+ import { $isLinkNode, $createAutoLinkNode, $toggleLink, $createLinkNode, LinkNode, AutoLinkNode } from '@lexical/link';
7
7
  import { $generateNodesFromDOM, $generateHtmlFromNodes } from '@lexical/html';
8
8
  import { registerMarkdownShortcuts, TRANSFORMERS } from '@lexical/markdown';
9
9
  import { createEmptyHistoryState, registerHistory } from '@lexical/history';
@@ -1019,7 +1019,19 @@ class CommandDispatcher {
1019
1019
  }
1020
1020
 
1021
1021
  dispatchLink(url) {
1022
- this.#toggleLink(url);
1022
+ this.editor.update(() => {
1023
+ const selection = $getSelection();
1024
+ if (!$isRangeSelection(selection)) return
1025
+
1026
+ if (selection.isCollapsed()) {
1027
+ const autoLinkNode = $createAutoLinkNode(url);
1028
+ const textNode = $createTextNode(url);
1029
+ autoLinkNode.append(textNode);
1030
+ selection.insertNodes([ autoLinkNode ]);
1031
+ } else {
1032
+ $toggleLink(url);
1033
+ }
1034
+ });
1023
1035
  }
1024
1036
 
1025
1037
  dispatchUnlink() {
@@ -1940,6 +1952,8 @@ class FormatEscaper {
1940
1952
 
1941
1953
  const anchorNode = selection.anchor.getNode();
1942
1954
 
1955
+ if (!this.#isInsideBlockquote(anchorNode)) return false
1956
+
1943
1957
  return this.#handleLists(event, anchorNode)
1944
1958
  || this.#handleBlockquotes(event, anchorNode)
1945
1959
  }
@@ -1964,6 +1978,19 @@ class FormatEscaper {
1964
1978
  return false
1965
1979
  }
1966
1980
 
1981
+ #isInsideBlockquote(node) {
1982
+ let currentNode = node;
1983
+
1984
+ while (currentNode) {
1985
+ if ($isQuoteNode(currentNode)) {
1986
+ return true
1987
+ }
1988
+ currentNode = currentNode.getParent();
1989
+ }
1990
+
1991
+ return false
1992
+ }
1993
+
1967
1994
  #shouldEscapeFromEmptyListItem(node) {
1968
1995
  const listItem = this.#getListItemNode(node);
1969
1996
  if (!listItem) return false
@@ -2261,10 +2288,10 @@ class Contents {
2261
2288
  const selection = $getSelection();
2262
2289
  if (!$isRangeSelection(selection)) return
2263
2290
 
2264
- const topLevelElement = selection.anchor.getNode().getTopLevelElementOrThrow();
2291
+ const topLevelElement = selection.anchor.getNode().getTopLevelElement();
2265
2292
 
2266
2293
  // Check if format is already applied
2267
- if (isFormatAppliedFn(topLevelElement)) {
2294
+ if (topLevelElement && isFormatAppliedFn(topLevelElement)) {
2268
2295
  this.#unwrap(topLevelElement);
2269
2296
  } else {
2270
2297
  this.#insertNodeWrappingAllSelectedNodes(newNodeFn);
@@ -2494,10 +2521,7 @@ class Contents {
2494
2521
  const node = $getNodeByKey(nodeKey);
2495
2522
  if (!node) return
2496
2523
 
2497
- let previousNode = node;
2498
- try {
2499
- previousNode = node.getTopLevelElementOrThrow();
2500
- } catch {}
2524
+ const previousNode = node.getTopLevelElement() || node;
2501
2525
 
2502
2526
  const newNode = options.attachment ? this.#createCustomAttachmentNodeWithHtml(html, options.attachment) : this.#createHtmlNodeWith(html);
2503
2527
  previousNode.insertAfter(newNode);
@@ -2524,16 +2548,21 @@ class Contents {
2524
2548
  if (!$isRangeSelection(selection)) return
2525
2549
 
2526
2550
  const selectedNodes = selection.extract();
2527
- if (selectedNodes.length === 0) return
2528
-
2551
+ if (selectedNodes.length === 0) {
2552
+ return
2553
+ }
2529
2554
  const topLevelElements = new Set();
2530
2555
  selectedNodes.forEach((node) => {
2531
2556
  const topLevel = node.getTopLevelElementOrThrow();
2532
2557
  topLevelElements.add(topLevel);
2533
2558
  });
2534
2559
 
2535
- const elements = this.#removeTrailingEmptyParagraphs(Array.from(topLevelElements));
2536
- if (elements.length === 0) return
2560
+ const elements = this.#withoutTrailingEmptyParagraphs(Array.from(topLevelElements));
2561
+ if (elements.length === 0) {
2562
+ this.#removeStandaloneEmptyParagraph();
2563
+ this.insertAtCursor(newNodeFn());
2564
+ return
2565
+ }
2537
2566
 
2538
2567
  const wrappingNode = newNodeFn();
2539
2568
  elements[0].insertBefore(wrappingNode);
@@ -2545,7 +2574,7 @@ class Contents {
2545
2574
  });
2546
2575
  }
2547
2576
 
2548
- #removeTrailingEmptyParagraphs(elements) {
2577
+ #withoutTrailingEmptyParagraphs(elements) {
2549
2578
  let lastNonEmptyIndex = elements.length - 1;
2550
2579
 
2551
2580
  // Find the last non-empty paragraph
@@ -2569,6 +2598,16 @@ class Contents {
2569
2598
  return children.length === 0 || children.every(child => $isLineBreakNode(child))
2570
2599
  }
2571
2600
 
2601
+ #removeStandaloneEmptyParagraph() {
2602
+ const root = $getRoot();
2603
+ if (root.getChildrenSize() === 1) {
2604
+ const firstChild = root.getFirstChild();
2605
+ if (firstChild && $isParagraphNode(firstChild) && this.#isElementEmpty(firstChild)) {
2606
+ firstChild.remove();
2607
+ }
2608
+ }
2609
+ }
2610
+
2572
2611
  #insertNodeWrappingAllSelectedLines(newNodeFn) {
2573
2612
  this.editor.update(() => {
2574
2613
  const selection = $getSelection();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@37signals/lexxy",
3
- "version": "0.1.16-beta",
3
+ "version": "0.1.18-beta",
4
4
  "description": "Lexxy - A modern rich text editor for Rails.",
5
5
  "module": "dist/lexxy.esm.js",
6
6
  "type": "module",