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.

198 lines (191 loc) 8.15 kB
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 _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; } 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; } import { $getSelection, $isRangeSelection, getDOMSelection } from 'lexical'; /** * Get the text content of the editor up to the anchor point of the selection. * Get the text content before the anchor point of the selection * @param selection Selection object from Lexical editor * @returns */ export function getTextUpToAnchor(selection) { var anchor = selection.anchor; if (anchor.type !== 'text') { return null; } var anchorNode = anchor.getNode(); if (!anchorNode.isSimpleText()) { return null; } var anchorOffset = anchor.offset; return anchorNode.getTextContent().slice(0, anchorOffset); } /** * * @param editor Lexical editor instance * Get the text content before the selection anchor point in the editor * @returns */ export function getQueryTextForSearch(editor) { var text = null; editor.getEditorState().read(function () { var selection = $getSelection(); if (!$isRangeSelection(selection)) { return; } text = getTextUpToAnchor(selection); }); return text; } export function tryToPositionRange(leadOffset, range, editorWindow) { var domSelection = getDOMSelection(editorWindow); if (domSelection === null || !domSelection.isCollapsed) { return false; } var anchorNode = domSelection.anchorNode; var startOffset = leadOffset; var endOffset = domSelection.anchorOffset; if (anchorNode === null || endOffset === null) { return false; } try { range.setStart(anchorNode, startOffset); range.setEnd(anchorNode, endOffset); } catch (_unused) { return false; } return true; } // Got from https://stackoverflow.com/a/42543908/2013580 export function getScrollParent(element, includeHidden) { // Return element itself on server side as fallback if (typeof document === 'undefined') { return element; } var style = getComputedStyle(element); var excludeStaticParent = style.position === 'absolute'; var overflowRegex = includeHidden ? /(auto|scroll|hidden)/ : /(auto|scroll)/; if (style.position === 'fixed') { return document.body; } for (var parent = element; parent = parent.parentElement;) { style = getComputedStyle(parent); if (excludeStaticParent && style.position === 'static') { continue; } if (overflowRegex.test(style.overflow + style.overflowY + style.overflowX)) { return parent; } } return document.body; } export var scrollIntoViewIfNeeded = function scrollIntoViewIfNeeded(target) { // Skip on server side if (typeof window === 'undefined' || typeof document === 'undefined') { return; } var typeaheadContainerNode = document.getElementById('typeahead-menu'); if (!typeaheadContainerNode) { return; } var typeaheadRect = typeaheadContainerNode.getBoundingClientRect(); if (typeaheadRect.top + typeaheadRect.height > window.innerHeight) { typeaheadContainerNode.scrollIntoView({ block: 'center' }); } if (typeaheadRect.top < 0) { typeaheadContainerNode.scrollIntoView({ block: 'center' }); } target.scrollIntoView({ block: 'nearest' }); }; export var PUNCTUATION = '\\.,\\+\\*\\?\\$\\@\\|#{}\\(\\)\\^\\-\\[\\]\\\\/!%\'"~=<>_:;'; export function getBasicTypeaheadTriggerMatch(trigger, _ref) { var _ref$minLength = _ref.minLength, minLength = _ref$minLength === void 0 ? 0 : _ref$minLength, _ref$maxLength = _ref.maxLength, maxLength = _ref$maxLength === void 0 ? 75 : _ref$maxLength, _ref$punctuation = _ref.punctuation, punctuation = _ref$punctuation === void 0 ? PUNCTUATION : _ref$punctuation, _ref$allowWhitespace = _ref.allowWhitespace, allowWhitespace = _ref$allowWhitespace === void 0 ? false : _ref$allowWhitespace; return function (text) { // When whitespace is not allowed, we need to ensure the regex stops at whitespace var validCharsSuffix = allowWhitespace ? '' : '\\s'; var validChars = '[^' + trigger + punctuation + validCharsSuffix + ']'; // Create regex that matches from trigger to either end of string OR whitespace (when not allowed) var regexPattern = allowWhitespace ? '(^|\\s|\\()(' + '[' + trigger + ']' + '((?:' + validChars + '){0,' + maxLength + '})' + ')$' : '(^|\\s|\\()(' + '[' + trigger + ']' + '((?:' + validChars + ')*?)' + ')(?=\\s|$)'; var TypeaheadTriggerRegex = new RegExp(regexPattern); var match = TypeaheadTriggerRegex.exec(text); if (match !== null) { var maybeLeadingWhitespace = match[1]; var matchingString = match[3]; // Check length constraints if (matchingString.length >= minLength && matchingString.length <= maxLength) { return { leadOffset: match.index + maybeLeadingWhitespace.length, matchingString: matchingString, replaceableString: match[2] }; } } return null; }; } /** * Walk backwards along user input and forward through entity title to try * and replace more of the user's text with entity. */ function getFullMatchOffset(documentText, entryText, offset) { var triggerOffset = offset; for (var i = triggerOffset; i <= entryText.length; i++) { if (documentText.slice(-i) === entryText.slice(0, Math.max(0, i))) { triggerOffset = i; } } return triggerOffset; } /** * Split Lexical TextNode and return a new TextNode only containing matched text. * Common use cases include: removing the node, replacing with a new node. */ export function $splitNodeContainingQuery(match) { var selection = $getSelection(); if (!$isRangeSelection(selection) || !selection.isCollapsed()) { return null; } var anchor = selection.anchor; if (anchor.type !== 'text') { return null; } var anchorNode = anchor.getNode(); if (!anchorNode.isSimpleText()) { return null; } var selectionOffset = anchor.offset; var textContent = anchorNode.getTextContent().slice(0, selectionOffset); var characterOffset = match.replaceableString.length; var queryOffset = getFullMatchOffset(textContent, match.matchingString, characterOffset); var startOffset = selectionOffset - queryOffset; if (startOffset < 0) { return null; } var newNode; if (startOffset === 0) { var _anchorNode$splitText = anchorNode.splitText(selectionOffset); var _anchorNode$splitText2 = _slicedToArray(_anchorNode$splitText, 1); newNode = _anchorNode$splitText2[0]; } else { var _anchorNode$splitText3 = anchorNode.splitText(startOffset, selectionOffset); var _anchorNode$splitText4 = _slicedToArray(_anchorNode$splitText3, 2); newNode = _anchorNode$splitText4[1]; } return newNode; }