| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328 | 
							- import { $isAtNodeEnd } from '@lexical/selection'
 - import type {
 -   ElementNode,
 -   Klass,
 -   LexicalEditor,
 -   LexicalNode,
 -   RangeSelection,
 -   TextNode,
 - } from 'lexical'
 - import {
 -   $createTextNode,
 -   $getSelection,
 -   $isRangeSelection,
 -   $isTextNode,
 - } from 'lexical'
 - import type { EntityMatch } from '@lexical/text'
 - import { CustomTextNode } from './plugins/custom-text/node'
 - import type { MenuTextMatch } from './types'
 - 
 - export function getSelectedNode(
 -   selection: RangeSelection,
 - ): TextNode | ElementNode {
 -   const anchor = selection.anchor
 -   const focus = selection.focus
 -   const anchorNode = selection.anchor.getNode()
 -   const focusNode = selection.focus.getNode()
 -   if (anchorNode === focusNode)
 -     return anchorNode
 - 
 -   const isBackward = selection.isBackward()
 -   if (isBackward)
 -     return $isAtNodeEnd(focus) ? anchorNode : focusNode
 -   else
 -     return $isAtNodeEnd(anchor) ? anchorNode : focusNode
 - }
 - 
 - export function registerLexicalTextEntity<T extends TextNode>(
 -   editor: LexicalEditor,
 -   getMatch: (text: string) => null | EntityMatch,
 -   targetNode: Klass<T>,
 -   createNode: (textNode: TextNode) => T,
 - ) {
 -   const isTargetNode = (node: LexicalNode | null | undefined): node is T => {
 -     return node instanceof targetNode
 -   }
 - 
 -   const replaceWithSimpleText = (node: TextNode): void => {
 -     const textNode = $createTextNode(node.getTextContent())
 -     textNode.setFormat(node.getFormat())
 -     node.replace(textNode)
 -   }
 - 
 -   const getMode = (node: TextNode): number => {
 -     return node.getLatest().__mode
 -   }
 - 
 -   const textNodeTransform = (node: TextNode) => {
 -     if (!node.isSimpleText())
 -       return
 - 
 -     const prevSibling = node.getPreviousSibling()
 -     let text = node.getTextContent()
 -     let currentNode = node
 -     let match
 - 
 -     if ($isTextNode(prevSibling)) {
 -       const previousText = prevSibling.getTextContent()
 -       const combinedText = previousText + text
 -       const prevMatch = getMatch(combinedText)
 - 
 -       if (isTargetNode(prevSibling)) {
 -         if (prevMatch === null || getMode(prevSibling) !== 0) {
 -           replaceWithSimpleText(prevSibling)
 -           return
 -         }
 -         else {
 -           const diff = prevMatch.end - previousText.length
 - 
 -           if (diff > 0) {
 -             const concatText = text.slice(0, diff)
 -             const newTextContent = previousText + concatText
 -             prevSibling.select()
 -             prevSibling.setTextContent(newTextContent)
 - 
 -             if (diff === text.length) {
 -               node.remove()
 -             }
 -             else {
 -               const remainingText = text.slice(diff)
 -               node.setTextContent(remainingText)
 -             }
 - 
 -             return
 -           }
 -         }
 -       }
 -       else if (prevMatch === null || prevMatch.start < previousText.length) {
 -         return
 -       }
 -     }
 - 
 -     while (true) {
 -       match = getMatch(text)
 -       let nextText = match === null ? '' : text.slice(match.end)
 -       text = nextText
 - 
 -       if (nextText === '') {
 -         const nextSibling = currentNode.getNextSibling()
 - 
 -         if ($isTextNode(nextSibling)) {
 -           nextText = currentNode.getTextContent() + nextSibling.getTextContent()
 -           const nextMatch = getMatch(nextText)
 - 
 -           if (nextMatch === null) {
 -             if (isTargetNode(nextSibling))
 -               replaceWithSimpleText(nextSibling)
 -             else
 -               nextSibling.markDirty()
 - 
 -             return
 -           }
 -           else if (nextMatch.start !== 0) {
 -             return
 -           }
 -         }
 -       }
 -       else {
 -         const nextMatch = getMatch(nextText)
 - 
 -         if (nextMatch !== null && nextMatch.start === 0)
 -           return
 -       }
 - 
 -       if (match === null)
 -         return
 - 
 -       if (match.start === 0 && $isTextNode(prevSibling) && prevSibling.isTextEntity())
 -         continue
 - 
 -       let nodeToReplace
 - 
 -       if (match.start === 0)
 -         [nodeToReplace, currentNode] = currentNode.splitText(match.end)
 -       else
 -         [, nodeToReplace, currentNode] = currentNode.splitText(match.start, match.end)
 - 
 -       const replacementNode = createNode(nodeToReplace)
 -       replacementNode.setFormat(nodeToReplace.getFormat())
 -       nodeToReplace.replace(replacementNode)
 - 
 -       if (currentNode == null)
 -         return
 -     }
 -   }
 - 
 -   const reverseNodeTransform = (node: T) => {
 -     const text = node.getTextContent()
 -     const match = getMatch(text)
 - 
 -     if (match === null || match.start !== 0) {
 -       replaceWithSimpleText(node)
 -       return
 -     }
 - 
 -     if (text.length > match.end) {
 -       // This will split out the rest of the text as simple text
 -       node.splitText(match.end)
 -       return
 -     }
 - 
 -     const prevSibling = node.getPreviousSibling()
 - 
 -     if ($isTextNode(prevSibling) && prevSibling.isTextEntity()) {
 -       replaceWithSimpleText(prevSibling)
 -       replaceWithSimpleText(node)
 -     }
 - 
 -     const nextSibling = node.getNextSibling()
 - 
 -     if ($isTextNode(nextSibling) && nextSibling.isTextEntity()) {
 -       replaceWithSimpleText(nextSibling) // This may have already been converted in the previous block
 - 
 -       if (isTargetNode(node))
 -         replaceWithSimpleText(node)
 -     }
 -   }
 - 
 -   const removePlainTextTransform = editor.registerNodeTransform(CustomTextNode, textNodeTransform)
 -   const removeReverseNodeTransform = editor.registerNodeTransform(targetNode, reverseNodeTransform)
 -   return [removePlainTextTransform, removeReverseNodeTransform]
 - }
 - 
 - export const decoratorTransform = (
 -   node: CustomTextNode,
 -   getMatch: (text: string) => null | EntityMatch,
 -   createNode: (textNode: TextNode) => LexicalNode,
 - ) => {
 -   if (!node.isSimpleText())
 -     return
 - 
 -   const prevSibling = node.getPreviousSibling()
 -   let text = node.getTextContent()
 -   let currentNode = node
 -   let match
 - 
 -   while (true) {
 -     match = getMatch(text)
 -     let nextText = match === null ? '' : text.slice(match.end)
 -     text = nextText
 - 
 -     if (nextText === '') {
 -       const nextSibling = currentNode.getNextSibling()
 - 
 -       if ($isTextNode(nextSibling)) {
 -         nextText = currentNode.getTextContent() + nextSibling.getTextContent()
 -         const nextMatch = getMatch(nextText)
 - 
 -         if (nextMatch === null) {
 -           nextSibling.markDirty()
 -           return
 -         }
 -         else if (nextMatch.start !== 0) {
 -           return
 -         }
 -       }
 -     }
 -     else {
 -       const nextMatch = getMatch(nextText)
 - 
 -       if (nextMatch !== null && nextMatch.start === 0)
 -         return
 -     }
 - 
 -     if (match === null)
 -       return
 - 
 -     if (match.start === 0 && $isTextNode(prevSibling) && prevSibling.isTextEntity())
 -       continue
 - 
 -     let nodeToReplace
 - 
 -     if (match.start === 0)
 -       [nodeToReplace, currentNode] = currentNode.splitText(match.end)
 -     else
 -       [, nodeToReplace, currentNode] = currentNode.splitText(match.start, match.end)
 - 
 -     const replacementNode = createNode(nodeToReplace)
 -     nodeToReplace.replace(replacementNode)
 - 
 -     if (currentNode == null)
 -       return
 -   }
 - }
 - 
 - function getFullMatchOffset(
 -   documentText: string,
 -   entryText: string,
 -   offset: number,
 - ): number {
 -   let triggerOffset = offset
 -   for (let i = triggerOffset; i <= entryText.length; i++) {
 -     if (documentText.slice(-i) === entryText.slice(0, i))
 -       triggerOffset = i
 -   }
 -   return triggerOffset
 - }
 - 
 - export function $splitNodeContainingQuery(match: MenuTextMatch): TextNode | null {
 -   const selection = $getSelection()
 -   if (!$isRangeSelection(selection) || !selection.isCollapsed())
 -     return null
 -   const anchor = selection.anchor
 -   if (anchor.type !== 'text')
 -     return null
 -   const anchorNode = anchor.getNode()
 -   if (!anchorNode.isSimpleText())
 -     return null
 -   const selectionOffset = anchor.offset
 -   const textContent = anchorNode.getTextContent().slice(0, selectionOffset)
 -   const characterOffset = match.replaceableString.length
 -   const queryOffset = getFullMatchOffset(
 -     textContent,
 -     match.matchingString,
 -     characterOffset,
 -   )
 -   const startOffset = selectionOffset - queryOffset
 -   if (startOffset < 0)
 -     return null
 -   let newNode
 -   if (startOffset === 0)
 -     [newNode] = anchorNode.splitText(selectionOffset)
 -   else
 -     [, newNode] = anchorNode.splitText(startOffset, selectionOffset)
 - 
 -   return newNode
 - }
 - 
 - export function textToEditorState(text: string) {
 -   const paragraph = text && (typeof text === 'string') ? text.split('\n') : ['']
 - 
 -   return JSON.stringify({
 -     root: {
 -       children: paragraph.map((p) => {
 -         return {
 -           children: [{
 -             detail: 0,
 -             format: 0,
 -             mode: 'normal',
 -             style: '',
 -             text: p,
 -             type: 'custom-text',
 -             version: 1,
 -           }],
 -           direction: 'ltr',
 -           format: '',
 -           indent: 0,
 -           type: 'paragraph',
 -           version: 1,
 -         }
 -       }),
 -       direction: 'ltr',
 -       format: '',
 -       indent: 0,
 -       type: 'root',
 -       version: 1,
 -     },
 -   })
 - }
 
 
  |