UNPKG

@lobehub/editor

Version:

A powerful and extensible rich text editor built on Meta's Lexical framework, providing a modern editing experience with React integration.

286 lines (272 loc) 14 kB
function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); } function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); } function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); } function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } } function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; } import { $createRangeSelection, $getSelection, $isLineBreakNode, $isRangeSelection, $isRootOrShadowRoot, $isTextNode, $setSelection } from 'lexical'; import { PUNCTUATION_OR_SPACE, getOpenTagStartIndex, isEqualSubString } from "../utils"; export function testElementTransformers(parentNode, anchorNode, anchorOffset, elementTransformers, fromTrigger) { var grandParentNode = parentNode.getParent(); if (!$isRootOrShadowRoot(grandParentNode) || parentNode.getFirstChild() !== anchorNode) { return false; } var textContent = anchorNode.getTextContent(); // Checking for anchorOffset position to prevent any checks for cases when caret is too far // from a line start to be a part of block-level markdown trigger. // // TODO: // Can have a quick check if caret is close enough to the beginning of the string (e.g. offset less than 10-20) // since otherwise it won't be a markdown shortcut, but tables are exception if (fromTrigger !== 'enter' && textContent[anchorOffset - 1] !== ' ') { return false; } var _iterator = _createForOfIteratorHelper(elementTransformers), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var _step$value = _step.value, regExp = _step$value.regExp, trigger = _step$value.trigger; var _match = textContent.match(regExp); if (fromTrigger === trigger && _match && _match[0].length === (fromTrigger === 'enter' || _match[0].endsWith(' ') ? anchorOffset : anchorOffset - 1)) { return true; } } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } return false; } export function runElementTransformers(parentNode, anchorNode, anchorOffset, elementTransformers, fromTrigger) { var grandParentNode = parentNode.getParent(); if (!$isRootOrShadowRoot(grandParentNode) || parentNode.getFirstChild() !== anchorNode) { return false; } var textContent = anchorNode.getTextContent(); // Checking for anchorOffset position to prevent any checks for cases when caret is too far // from a line start to be a part of block-level markdown trigger. // // TODO: // Can have a quick check if caret is close enough to the beginning of the string (e.g. offset less than 10-20) // since otherwise it won't be a markdown shortcut, but tables are exception if (fromTrigger !== 'enter' && textContent[anchorOffset - 1] !== ' ') { return false; } var _iterator2 = _createForOfIteratorHelper(elementTransformers), _step2; try { for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) { var _step2$value = _step2.value, regExp = _step2$value.regExp, replace = _step2$value.replace, trigger = _step2$value.trigger; var _match2 = textContent.match(regExp); if (fromTrigger === trigger && _match2 && _match2[0].length === (fromTrigger === 'enter' || _match2[0].endsWith(' ') ? anchorOffset : anchorOffset - 1)) { var nextSiblings = anchorNode.getNextSiblings(); var _anchorNode$splitText = anchorNode.splitText(anchorOffset), _anchorNode$splitText2 = _slicedToArray(_anchorNode$splitText, 2), leadingNode = _anchorNode$splitText2[0], remainderNode = _anchorNode$splitText2[1]; var siblings = remainderNode ? [remainderNode].concat(_toConsumableArray(nextSiblings)) : nextSiblings; if (replace(parentNode, siblings, _match2, false) !== false) { leadingNode.remove(); return true; } } } } catch (err) { _iterator2.e(err); } finally { _iterator2.f(); } return false; } export function runTextMatchTransformers(anchorNode, anchorOffset, transformersByTrigger) { var textContent = anchorNode.getTextContent(); var lastChar = textContent[anchorOffset - 1]; var transformers = transformersByTrigger[lastChar]; if (!transformers) { return false; } // If typing in the middle of content, remove the tail to do // reg exp match up to a string end (caret position) if (anchorOffset < textContent.length) { textContent = textContent.slice(0, anchorOffset); } var _iterator3 = _createForOfIteratorHelper(transformers), _step3; try { for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) { var transformer = _step3.value; if (!transformer.replace || !transformer.regExp) { continue; } var _match3 = textContent.match(transformer.regExp); if (_match3 === null) { continue; } var startIndex = _match3.index || 0; var endIndex = startIndex + _match3[0].length; var replaceNode = void 0; if (startIndex === 0) { var _anchorNode$splitText3 = anchorNode.splitText(endIndex); var _anchorNode$splitText4 = _slicedToArray(_anchorNode$splitText3, 1); replaceNode = _anchorNode$splitText4[0]; } else { var _anchorNode$splitText5 = anchorNode.splitText(startIndex, endIndex); var _anchorNode$splitText6 = _slicedToArray(_anchorNode$splitText5, 2); replaceNode = _anchorNode$splitText6[1]; } replaceNode.selectNext(0, 0); transformer.replace(replaceNode, _match3); return true; } } catch (err) { _iterator3.e(err); } finally { _iterator3.f(); } return false; } export function $runTextFormatTransformers(anchorNode, anchorOffset, textFormatTransformers) { var textContent = anchorNode.getTextContent(); var closeTagEndIndex = anchorOffset - 1; var closeChar = textContent[closeTagEndIndex]; // Quick check if we're possibly at the end of inline markdown style var matchers = textFormatTransformers[closeChar]; if (!matchers) { return false; } var _iterator4 = _createForOfIteratorHelper(matchers), _step4; try { for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) { var matcher = _step4.value; var tag = matcher.tag; var tagLength = tag.length; var closeTagStartIndex = closeTagEndIndex - tagLength + 1; // If tag is not single char check if rest of it matches with text content if (tagLength > 1 && !isEqualSubString(textContent, closeTagStartIndex, tag, 0, tagLength)) { continue; } // Space before closing tag cancels inline markdown if (textContent[closeTagStartIndex - 1] === ' ') { continue; } // Some tags can not be used within words, hence should have newline/space/punctuation after it var afterCloseTagChar = textContent[closeTagEndIndex + 1]; if (matcher.intraword === false && afterCloseTagChar && !PUNCTUATION_OR_SPACE.test(afterCloseTagChar)) { continue; } var closeNode = anchorNode; var openNode = closeNode; var openTagStartIndex = getOpenTagStartIndex(textContent, closeTagStartIndex, tag); // Go through text node siblings and search for opening tag // if haven't found it within the same text node as closing tag var sibling = openNode; while (openTagStartIndex < 0 && (sibling = sibling.getPreviousSibling())) { if ($isLineBreakNode(sibling)) { break; } if ($isTextNode(sibling)) { if (sibling.hasFormat('code')) { continue; } var siblingTextContent = sibling.getTextContent(); openNode = sibling; openTagStartIndex = getOpenTagStartIndex(siblingTextContent, siblingTextContent.length, tag); } } // Opening tag is not found if (openTagStartIndex < 0) { continue; } // No content between opening and closing tag if (openNode === closeNode && openTagStartIndex + tagLength === closeTagStartIndex) { continue; } // Checking longer tags for repeating chars (e.g. *** vs **) var prevOpenNodeText = openNode.getTextContent(); if (openTagStartIndex > 0 && prevOpenNodeText[openTagStartIndex - 1] === closeChar) { continue; } // Some tags can not be used within words, hence should have newline/space/punctuation before it var beforeOpenTagChar = prevOpenNodeText[openTagStartIndex - 1]; if (matcher.intraword === false && beforeOpenTagChar && !PUNCTUATION_OR_SPACE.test(beforeOpenTagChar)) { continue; } // Clean text from opening and closing tags (starting from closing tag // to prevent any offset shifts if we start from opening one) var prevCloseNodeText = closeNode.getTextContent(); var closeNodeText = prevCloseNodeText.slice(0, closeTagStartIndex) + prevCloseNodeText.slice(closeTagEndIndex + 1); closeNode.setTextContent(closeNodeText); var openNodeText = openNode === closeNode ? closeNodeText : prevOpenNodeText; openNode.setTextContent(openNodeText.slice(0, openTagStartIndex) + openNodeText.slice(openTagStartIndex + tagLength)); var _selection = $getSelection(); var nextSelection = $createRangeSelection(); $setSelection(nextSelection); // Adjust offset based on deleted chars var newOffset = closeTagEndIndex - tagLength * (openNode === closeNode ? 2 : 1) + 1; nextSelection.anchor.set(openNode.__key, openTagStartIndex, 'text'); nextSelection.focus.set(closeNode.__key, newOffset, 'text'); if (matcher.process) { matcher.process(nextSelection); return true; } else if (matcher.format) { // Apply formatting to selected text var _iterator5 = _createForOfIteratorHelper(matcher.format), _step5; try { for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) { var format = _step5.value; if (!nextSelection.hasFormat(format)) { nextSelection.formatText(format); } } // Collapse selection up to the focus point } catch (err) { _iterator5.e(err); } finally { _iterator5.f(); } nextSelection.anchor.set(nextSelection.focus.key, nextSelection.focus.offset, nextSelection.focus.type); // Remove formatting from collapsed selection var _iterator6 = _createForOfIteratorHelper(matcher.format), _step6; try { for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) { var _format = _step6.value; if (nextSelection.hasFormat(_format)) { nextSelection.toggleFormat(_format); } } } catch (err) { _iterator6.e(err); } finally { _iterator6.f(); } if ($isRangeSelection(_selection)) { nextSelection.format = _selection.format; } } else { // No format or process specified, nothing to do $setSelection(_selection); continue; } return true; } } catch (err) { _iterator4.e(err); } finally { _iterator4.f(); } return false; }