@37signals/lexxy 0.9.14-beta → 0.9.15-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.
Files changed (2) hide show
  1. package/dist/lexxy.esm.js +58 -13
  2. package/package.json +1 -1
package/dist/lexxy.esm.js CHANGED
@@ -1,7 +1,7 @@
1
1
  export { highlightCode } 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, $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, $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, PASTE_COMMAND, SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, ParagraphNode, RootNode, COMMAND_PRIORITY_HIGH, DRAGSTART_COMMAND, DROP_COMMAND, INSERT_PARAGRAPH_COMMAND, mergeRegister as mergeRegister$1, CLEAR_HISTORY_COMMAND, $onUpdate, 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, PASTE_COMMAND, SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, ParagraphNode, RootNode, COMMAND_PRIORITY_HIGH, DRAGSTART_COMMAND, DROP_COMMAND, INSERT_PARAGRAPH_COMMAND, mergeRegister as mergeRegister$1, CLEAR_HISTORY_COMMAND, $onUpdate, 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';
@@ -1527,14 +1527,15 @@ function $isShadowRoot(node) {
1527
1527
  return $isElementNode(node) && $isRootOrShadowRoot(node) && !$isRootNode(node)
1528
1528
  }
1529
1529
 
1530
+ function $isSafeForRoot(node) {
1531
+ return ($isElementNode(node) || $isDecoratorNode(node)) && !node.isParentRequired()
1532
+ }
1533
+
1530
1534
  function $makeSafeForRoot(node) {
1531
- if ($isTextNode(node)) {
1532
- return $wrapNodeInElement(node, $createParagraphNode)
1533
- } else if (node.isParentRequired()) {
1534
- const parent = node.createRequiredParent();
1535
- return $wrapNodeInElement(node, parent)
1536
- } else {
1535
+ if ($isSafeForRoot(node)) {
1537
1536
  return node
1537
+ } else {
1538
+ return $wrapNodeInElement(node, () => node.createParentElementNode())
1538
1539
  }
1539
1540
  }
1540
1541
 
@@ -1649,6 +1650,39 @@ function $isListItemStructurallyEmpty(listItem) {
1649
1650
  return true
1650
1651
  }
1651
1652
 
1653
+ // Returns the document text up to `offset` inside `targetNode`. Non-inline
1654
+ // element siblings are joined with `\n\n`, matching Lexical's own
1655
+ // ElementNode.getTextContent behavior.
1656
+ function $textBeforeOffset(targetNode, offset) {
1657
+ const parts = [];
1658
+ let done = false;
1659
+
1660
+ function visit(node) {
1661
+ if (done) return
1662
+ if (node === targetNode) {
1663
+ parts.push(node.getTextContent().slice(0, offset));
1664
+ done = true;
1665
+ return
1666
+ }
1667
+ if ($isElementNode(node)) {
1668
+ const children = node.getChildren();
1669
+ for (let i = 0; i < children.length; i++) {
1670
+ visit(children[i]);
1671
+ if (done) return
1672
+ const child = children[i];
1673
+ if ($isElementNode(child) && !child.isInline() && i < children.length - 1) {
1674
+ parts.push("\n\n");
1675
+ }
1676
+ }
1677
+ } else {
1678
+ parts.push(node.getTextContent());
1679
+ }
1680
+ }
1681
+
1682
+ visit($getRoot());
1683
+ return parts.join("")
1684
+ }
1685
+
1652
1686
  function isAttachmentSpacerTextNode(node, previousNode, index, childCount) {
1653
1687
  return $isTextNode(node)
1654
1688
  && node.getTextContent() === " "
@@ -8552,6 +8586,9 @@ class RemoteFilterSource extends BaseSource {
8552
8586
  const NOTHING_FOUND_DEFAULT_MESSAGE = "Nothing found";
8553
8587
  const FILTER_DEBOUNCE_INTERVAL = 50;
8554
8588
 
8589
+ // Start of line, or after a space or newline.
8590
+ const DEFAULT_ONLY_AT_PATTERN = "^|[ \\n]";
8591
+
8555
8592
  class LexicalPromptElement extends HTMLElement {
8556
8593
  #globalListeners = new ListenerBin()
8557
8594
  #popoverListeners = new ListenerBin()
@@ -8597,6 +8634,10 @@ class LexicalPromptElement extends HTMLElement {
8597
8634
  return this.hasAttribute("supports-space-in-searches")
8598
8635
  }
8599
8636
 
8637
+ get onlyAt() {
8638
+ return this.getAttribute("only-at")
8639
+ }
8640
+
8600
8641
  get open() {
8601
8642
  return this.popoverElement?.classList?.contains("lexxy-prompt-menu--visible")
8602
8643
  }
@@ -8640,14 +8681,10 @@ class LexicalPromptElement extends HTMLElement {
8640
8681
  if (offset >= triggerLength) {
8641
8682
  const textBeforeCursor = fullText.slice(offset - triggerLength, offset);
8642
8683
 
8643
- // Check if trigger is at the start of the text node (new line case) or preceded by space or newline
8644
8684
  if (textBeforeCursor === this.trigger) {
8645
- const isAtStart = offset === triggerLength;
8646
-
8647
- const charBeforeTrigger = offset > triggerLength ? fullText[offset - triggerLength - 1] : null;
8648
- const isPrecededBySpaceOrNewline = charBeforeTrigger === " " || charBeforeTrigger === "\n";
8685
+ const textBeforeTrigger = $textBeforeOffset(node, offset - triggerLength);
8649
8686
 
8650
- if (isAtStart || isPrecededBySpaceOrNewline) {
8687
+ if (this.#onlyAtRegExp.test(textBeforeTrigger)) {
8651
8688
  this.#popoverListeners.dispose();
8652
8689
  this.#showPopover();
8653
8690
  }
@@ -8658,7 +8695,15 @@ class LexicalPromptElement extends HTMLElement {
8658
8695
  }));
8659
8696
  }
8660
8697
 
8698
+ get #onlyAtRegExp() {
8699
+ return new RegExp(`(?:${this.onlyAt ?? DEFAULT_ONLY_AT_PATTERN})$`)
8700
+ }
8701
+
8661
8702
  get #promptContentTypePermitted() {
8703
+ // `insert-editable-text` prompts never create attachments, so the
8704
+ // editor's attachment support and content-type allowlist don't apply.
8705
+ if (this.hasAttribute("insert-editable-text")) return true
8706
+
8662
8707
  const el = this.#editorElement;
8663
8708
  if (!el.supportsAttachments) {
8664
8709
  return false
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@37signals/lexxy",
3
- "version": "0.9.14-beta",
3
+ "version": "0.9.15-alpha.1",
4
4
  "description": "Lexxy - A modern rich text editor for Rails.",
5
5
  "module": "dist/lexxy.esm.js",
6
6
  "type": "module",