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.

723 lines (711 loc) 29.5 kB
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } 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; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } function _get() { if (typeof Reflect !== "undefined" && Reflect.get) { _get = Reflect.get.bind(); } else { _get = function _get(target, property, receiver) { var base = _superPropBase(target, property); if (!base) return; var desc = Object.getOwnPropertyDescriptor(base, property); if (desc.get) { return desc.get.call(arguments.length < 3 ? target : receiver); } return desc.value; }; } return _get.apply(this, arguments); } function _superPropBase(object, property) { while (!Object.prototype.hasOwnProperty.call(object, property)) { object = _getPrototypeOf(object); if (object === null) break; } return object; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); Object.defineProperty(subClass, "prototype", { writable: false }); if (superClass) _setPrototypeOf(subClass, superClass); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } return _assertThisInitialized(self); } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } } function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : String(i); } function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } /* eslint-disable @typescript-eslint/no-use-before-define */ import { $findMatchingParent, addClassNamesToElement, isHTMLAnchorElement } from '@lexical/utils'; import { $applyNodeReplacement, $getSelection, $isElementNode, $isNodeSelection, $isRangeSelection, $normalizeSelection__EXPERIMENTAL, $setSelection, ElementNode, createCommand } from 'lexical'; import { assert } from "../../../editor-kernel/utils"; import { createDebugLogger } from "../../../utils/debug"; var logger = createDebugLogger('plugin', 'link'); var SUPPORTED_URL_PROTOCOLS = new Set(['http:', 'https:', 'mailto:', 'sms:', 'tel:']); export var HOVER_LINK_COMMAND = createCommand('HOVER_LINK_COMMAND'); export var HOVER_OUT_LINK_COMMAND = createCommand('HOVER_OUT_LINK_COMMAND'); /** @noInheritDoc */ export var LinkNode = /*#__PURE__*/function (_ElementNode) { _inherits(LinkNode, _ElementNode); var _super = _createSuper(LinkNode); function LinkNode() { var _this; var url = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; var attributes = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var key = arguments.length > 2 ? arguments[2] : undefined; _classCallCheck(this, LinkNode); _this = _super.call(this, key); /** @internal */ _defineProperty(_assertThisInitialized(_this), "__url", void 0); /** @internal */ _defineProperty(_assertThisInitialized(_this), "__target", void 0); /** @internal */ _defineProperty(_assertThisInitialized(_this), "__rel", void 0); /** @internal */ _defineProperty(_assertThisInitialized(_this), "__title", void 0); var _attributes$target = attributes.target, target = _attributes$target === void 0 ? null : _attributes$target, _attributes$rel = attributes.rel, rel = _attributes$rel === void 0 ? null : _attributes$rel, _attributes$title = attributes.title, title = _attributes$title === void 0 ? null : _attributes$title; _this.__url = url; _this.__target = target; _this.__rel = rel; _this.__title = title; return _this; } _createClass(LinkNode, [{ key: "createDOM", value: function createDOM(config, editor) { var _this2 = this; logger.debug('🔍 config', config); var element = document.createElement('a'); this.updateLinkDOM(null, element, config); addClassNamesToElement(element, config.theme.link); element.addEventListener('mouseenter', function (event) { if (event.target instanceof HTMLElement) { event.target.classList.add('hover'); editor.dispatchCommand(HOVER_LINK_COMMAND, { event: event, linkNode: _this2 }); } }); element.addEventListener('mouseleave', function (event) { if (event.target instanceof HTMLElement) { event.target.classList.remove('hover'); editor.dispatchCommand(HOVER_OUT_LINK_COMMAND, { event: event }); } }); return element; } }, { key: "updateLinkDOM", value: function updateLinkDOM(prevNode, anchor, // eslint-disable-next-line @typescript-eslint/no-unused-vars _config) { if (isHTMLAnchorElement(anchor)) { if (!prevNode || prevNode.__url !== this.__url) { anchor.href = this.sanitizeUrl(this.__url); } var _iterator = _createForOfIteratorHelper(['target', 'rel', 'title']), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var attr = _step.value; var key = "__".concat(attr); var value = this[key]; if (!prevNode || prevNode[key] !== value) { if (value) { anchor[attr] = value; } else { anchor.removeAttribute(attr); } } } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } } } }, { key: "updateDOM", value: function updateDOM(prevNode, anchor, config) { this.updateLinkDOM(prevNode, anchor, config); return false; } }, { key: "updateFromJSON", value: function updateFromJSON(serializedNode) { return _get(_getPrototypeOf(LinkNode.prototype), "updateFromJSON", this).call(this, serializedNode).setURL(serializedNode.url).setRel(serializedNode.rel || null).setTarget(serializedNode.target || null).setTitle(serializedNode.title || null); } }, { key: "sanitizeUrl", value: function sanitizeUrl(url) { // eslint-disable-next-line no-param-reassign url = formatUrl(url); try { var parsedUrl = new URL(formatUrl(url)); // eslint-disable-next-line no-script-url if (!SUPPORTED_URL_PROTOCOLS.has(parsedUrl.protocol)) { return 'about:blank'; } } catch (_unused) { return url; } return url; } }, { key: "exportJSON", value: function exportJSON() { return _objectSpread(_objectSpread({}, _get(_getPrototypeOf(LinkNode.prototype), "exportJSON", this).call(this)), {}, { rel: this.getRel(), target: this.getTarget(), title: this.getTitle(), url: this.getURL() }); } }, { key: "getURL", value: function getURL() { return this.getLatest().__url; } }, { key: "setURL", value: function setURL(url) { var writable = this.getWritable(); writable.__url = url; return writable; } }, { key: "getTarget", value: function getTarget() { return this.getLatest().__target; } }, { key: "setTarget", value: function setTarget(target) { var writable = this.getWritable(); writable.__target = target; return writable; } }, { key: "getRel", value: function getRel() { return this.getLatest().__rel; } }, { key: "setRel", value: function setRel(rel) { var writable = this.getWritable(); writable.__rel = rel; return writable; } }, { key: "getTitle", value: function getTitle() { return this.getLatest().__title; } }, { key: "setTitle", value: function setTitle(title) { var writable = this.getWritable(); writable.__title = title; return writable; } }, { key: "insertNewAfter", value: function insertNewAfter(_) { var restoreSelection = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; var linkNode = $createLinkNode(this.__url, { rel: this.__rel, target: this.__target, title: this.__title }); this.insertAfter(linkNode, restoreSelection); return linkNode; } }, { key: "canInsertTextBefore", value: function canInsertTextBefore() { return false; } }, { key: "canInsertTextAfter", value: function canInsertTextAfter() { return false; } }, { key: "canBeEmpty", value: function canBeEmpty() { return false; } }, { key: "isInline", value: function isInline() { return true; } }, { key: "extractWithChild", value: function extractWithChild(child, selection) { if (!$isRangeSelection(selection)) { return false; } var anchorNode = selection.anchor.getNode(); var focusNode = selection.focus.getNode(); return this.isParentOf(anchorNode) && this.isParentOf(focusNode) && selection.getTextContent().length > 0; } }, { key: "isEmailURI", value: function isEmailURI() { return this.__url.startsWith('mailto:'); } }, { key: "isWebSiteURI", value: function isWebSiteURI() { return this.__url.startsWith('https://') || this.__url.startsWith('http://'); } }], [{ key: "getType", value: function getType() { return 'link'; } }, { key: "clone", value: function clone(node) { return new LinkNode(node.__url, { rel: node.__rel, target: node.__target, title: node.__title }, node.__key); } }, { key: "importDOM", value: function importDOM() { return { a: function a() { return { conversion: $convertAnchorElement, priority: 1 }; } }; } }, { key: "importJSON", value: function importJSON(serializedNode) { return $createLinkNode().updateFromJSON(serializedNode); } }]); return LinkNode; }(ElementNode); function $convertAnchorElement(domNode) { var node = null; if (isHTMLAnchorElement(domNode)) { var content = domNode.textContent; if (content !== null && content !== '' || domNode.children.length > 0) { node = $createLinkNode(domNode.getAttribute('href') || '', { rel: domNode.getAttribute('rel'), target: domNode.getAttribute('target'), title: domNode.getAttribute('title') }); } } return { node: node }; } /** * Takes a URL and creates a LinkNode. * @param url - The URL the LinkNode should direct to. * @param attributes - Optional HTML a tag attributes \\{ target, rel, title \\} * @returns The LinkNode. */ export function $createLinkNode() { var url = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; var attributes = arguments.length > 1 ? arguments[1] : undefined; return $applyNodeReplacement(new LinkNode(url, attributes)); } /** * Determines if node is a LinkNode. * @param node - The node to be checked. * @returns true if node is a LinkNode, false otherwise. */ export function $isLinkNode(node) { return node instanceof LinkNode; } // Custom node type to override `canInsertTextAfter` that will // allow typing within the link export var AutoLinkNode = /*#__PURE__*/function (_LinkNode) { _inherits(AutoLinkNode, _LinkNode); var _super2 = _createSuper(AutoLinkNode); function AutoLinkNode() { var _this3; var url = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; var attributes = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var key = arguments.length > 2 ? arguments[2] : undefined; _classCallCheck(this, AutoLinkNode); _this3 = _super2.call(this, url, attributes, key); /** @internal */ /** Indicates whether the autolink was ever unlinked. **/ _defineProperty(_assertThisInitialized(_this3), "__isUnlinked", void 0); _this3.__isUnlinked = attributes.isUnlinked !== undefined && attributes.isUnlinked !== null ? attributes.isUnlinked : false; return _this3; } _createClass(AutoLinkNode, [{ key: "getIsUnlinked", value: function getIsUnlinked() { return this.__isUnlinked; } }, { key: "setIsUnlinked", value: function setIsUnlinked(value) { var self = this.getWritable(); self.__isUnlinked = value; return self; } }, { key: "createDOM", value: function createDOM(config, editor) { logger.debug('🔍 config', config); if (this.__isUnlinked) { return document.createElement('span'); } else { return _get(_getPrototypeOf(AutoLinkNode.prototype), "createDOM", this).call(this, config, editor); } } }, { key: "updateDOM", value: function updateDOM(prevNode, anchor, config) { return _get(_getPrototypeOf(AutoLinkNode.prototype), "updateDOM", this).call(this, prevNode, anchor, config) || prevNode.__isUnlinked !== this.__isUnlinked; } }, { key: "updateFromJSON", value: function updateFromJSON(serializedNode) { return _get(_getPrototypeOf(AutoLinkNode.prototype), "updateFromJSON", this).call(this, serializedNode).setIsUnlinked(serializedNode.isUnlinked || false); } }, { key: "exportJSON", value: function exportJSON() { return _objectSpread(_objectSpread({}, _get(_getPrototypeOf(AutoLinkNode.prototype), "exportJSON", this).call(this)), {}, { isUnlinked: this.__isUnlinked }); } }, { key: "insertNewAfter", value: function insertNewAfter(selection) { var restoreSelection = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; var element = this.getParentOrThrow().insertNewAfter(selection, restoreSelection); if ($isElementNode(element)) { var linkNode = $createAutoLinkNode(this.__url, { isUnlinked: this.__isUnlinked, rel: this.__rel, target: this.__target, title: this.__title }); element.append(linkNode); return linkNode; } return null; } }], [{ key: "getType", value: function getType() { return 'autolink'; } }, { key: "clone", value: function clone(node) { return new AutoLinkNode(node.__url, { isUnlinked: node.__isUnlinked, rel: node.__rel, target: node.__target, title: node.__title }, node.__key); } }, { key: "importJSON", value: function importJSON(serializedNode) { return $createAutoLinkNode().updateFromJSON(serializedNode); } }, { key: "importDOM", value: function importDOM() { // TODO: Should link node should handle the import over autolink? return null; } }]); return AutoLinkNode; }(LinkNode); /** * Takes a URL and creates an AutoLinkNode. AutoLinkNodes are generally automatically generated * during typing, which is especially useful when a button to generate a LinkNode is not practical. * @param url - The URL the LinkNode should direct to. * @param attributes - Optional HTML a tag attributes. \\{ target, rel, title \\} * @returns The LinkNode. */ export function $createAutoLinkNode() { var url = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; var attributes = arguments.length > 1 ? arguments[1] : undefined; return $applyNodeReplacement(new AutoLinkNode(url, attributes)); } /** * Determines if node is an AutoLinkNode. * @param node - The node to be checked. * @returns true if node is an AutoLinkNode, false otherwise. */ export function $isAutoLinkNode(node) { return node instanceof AutoLinkNode; } export var TOGGLE_LINK_COMMAND = createCommand('TOGGLE_LINK_COMMAND'); function $getPointNode(point, offset) { if (point.type === 'element') { var node = point.getNode(); assert($isElementNode(node), '$getPointNode: element point is not an ElementNode'); var childNode = node.getChildren()[point.offset + offset]; return childNode || null; } return null; } /** * Preserve the logical start/end of a RangeSelection in situations where * the point is an element that may be reparented in the callback. * * @param $fn The function to run * @returns The result of the callback */ function $withSelectedNodes($fn) { var initialSelection = $getSelection(); if (!$isRangeSelection(initialSelection)) { return $fn(); } var normalized = $normalizeSelection__EXPERIMENTAL(initialSelection); var isBackwards = normalized.isBackward(); var anchorNode = $getPointNode(normalized.anchor, isBackwards ? -1 : 0); var focusNode = $getPointNode(normalized.focus, isBackwards ? 0 : -1); var rval = $fn(); if (anchorNode || focusNode) { var updatedSelection = $getSelection(); if ($isRangeSelection(updatedSelection)) { var finalSelection = updatedSelection.clone(); if (anchorNode) { var anchorParent = anchorNode.getParent(); if (anchorParent) { finalSelection.anchor.set(anchorParent.getKey(), anchorNode.getIndexWithinParent() + (isBackwards ? 1 : 0), 'element'); } } if (focusNode) { var focusParent = focusNode.getParent(); if (focusParent) { finalSelection.focus.set(focusParent.getKey(), focusNode.getIndexWithinParent() + (isBackwards ? 0 : 1), 'element'); } } $setSelection($normalizeSelection__EXPERIMENTAL(finalSelection)); } } return rval; } /** * Generates or updates a LinkNode. It can also delete a LinkNode if the URL is null, * but saves any children and brings them up to the parent node. * @param url - The URL the link directs to. * @param attributes - Optional HTML a tag attributes. \\{ target, rel, title \\} */ export function $toggleLink(url) { var attributes = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var target = attributes.target, title = attributes.title; var rel = attributes.rel === undefined ? 'noreferrer' : attributes.rel; var selection = $getSelection(); if (selection === null || !$isRangeSelection(selection) && !$isNodeSelection(selection)) { return; } if ($isNodeSelection(selection)) { var _nodes = selection.getNodes(); if (_nodes.length === 0) { return; } // Handle all selected nodes _nodes.forEach(function (node) { if (url === null) { // Remove link var linkParent = $findMatchingParent(node, function (parent) { return !$isAutoLinkNode(parent) && $isLinkNode(parent); }); if (linkParent) { linkParent.insertBefore(node); if (linkParent.getChildren().length === 0) { linkParent.remove(); } } } else { // Add/Update link var existingLink = $findMatchingParent(node, function (parent) { return !$isAutoLinkNode(parent) && $isLinkNode(parent); }); if (existingLink) { existingLink.setURL(url); if (target !== undefined) { existingLink.setTarget(target); } if (rel !== undefined) { existingLink.setRel(rel); } } else { var linkNode = $createLinkNode(url, { rel: rel, target: target }); node.insertBefore(linkNode); linkNode.append(node); } } }); return; } // Handle RangeSelection var nodes = selection.extract(); if (url === null) { // Remove LinkNodes nodes.forEach(function (node) { var parentLink = $findMatchingParent(node, function (parent) { return !$isAutoLinkNode(parent) && $isLinkNode(parent); }); if (parentLink) { var children = parentLink.getChildren(); var _iterator2 = _createForOfIteratorHelper(children), _step2; try { for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) { var child = _step2.value; parentLink.insertBefore(child); } } catch (err) { _iterator2.e(err); } finally { _iterator2.f(); } parentLink.remove(); } }); return; } var updatedNodes = new Set(); var updateLinkNode = function updateLinkNode(linkNode) { if (updatedNodes.has(linkNode.getKey())) { return; } updatedNodes.add(linkNode.getKey()); linkNode.setURL(url); if (target !== undefined) { linkNode.setTarget(target); } if (rel !== undefined) { linkNode.setRel(rel); } if (title !== undefined) { linkNode.setTitle(title); } }; // Add or merge LinkNodes if (nodes.length === 1) { var firstNode = nodes[0]; // if the first node is a LinkNode or if its // parent is a LinkNode, we update the URL, target and rel. var linkNode = $getAncestor(firstNode, $isLinkNode); if (linkNode !== null) { return updateLinkNode(linkNode); } } $withSelectedNodes(function () { var linkNode = null; var _iterator3 = _createForOfIteratorHelper(nodes), _step3; try { for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) { var node = _step3.value; if (!node.isAttached()) { continue; } var parentLinkNode = $getAncestor(node, $isLinkNode); if (parentLinkNode) { updateLinkNode(parentLinkNode); continue; } if ($isElementNode(node)) { if (!node.isInline()) { // Ignore block nodes, if there are any children we will see them // later and wrap in a new LinkNode continue; } if ($isLinkNode(node)) { // If it's not an autolink node and we don't already have a LinkNode // in this block then we can update it and re-use it if (!$isAutoLinkNode(node) && (linkNode === null || !linkNode.getParentOrThrow().isParentOf(node))) { updateLinkNode(node); linkNode = node; continue; } // Unwrap LinkNode, we already have one or it's an AutoLinkNode var _iterator4 = _createForOfIteratorHelper(node.getChildren()), _step4; try { for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) { var child = _step4.value; node.insertBefore(child); } } catch (err) { _iterator4.e(err); } finally { _iterator4.f(); } node.remove(); continue; } } var prevLinkNode = node.getPreviousSibling(); if ($isLinkNode(prevLinkNode) && prevLinkNode.is(linkNode)) { prevLinkNode.append(node); continue; } linkNode = $createLinkNode(url, { rel: rel, target: target, title: title }); node.insertAfter(linkNode); linkNode.append(node); } } catch (err) { _iterator3.e(err); } finally { _iterator3.f(); } }); } /** @deprecated renamed to {@link $toggleLink} by @lexical/eslint-plugin rules-of-lexical */ export var toggleLink = $toggleLink; function $getAncestor(node, predicate) { var parent = node; while (parent !== null && parent.getParent() !== null && !predicate(parent)) { parent = parent.getParentOrThrow(); } return predicate(parent) ? parent : null; } var PHONE_NUMBER_REGEX = /^\+?[\d\s()-]{5,}$/; /** * Formats a URL string by adding appropriate protocol if missing * * @param url - URL to format * @returns Formatted URL with appropriate protocol */ export function formatUrl(url) { // Check if URL already has a protocol if (/^[a-z][\d+.a-z-]*:/i.test(url)) { // URL already has a protocol, leave it as is return url; } // Check if it's a relative path (starting with '/', '.', or '#') else if (/^[#./]/.test(url)) { // Relative path, leave it as is return url; } // Check for email address else if (url.includes('@')) { return "mailto:".concat(url); } // Check for phone number else if (PHONE_NUMBER_REGEX.test(url)) { return "tel:".concat(url); } return url; }