@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
JavaScript
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;
}