@lobehub/editor 1.19.1 → 1.20.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/es/index.d.ts +2 -0
  2. package/es/index.js +4 -0
  3. package/es/plugins/link-highlight/command/index.d.ts +3 -0
  4. package/es/plugins/link-highlight/command/index.js +46 -0
  5. package/es/plugins/link-highlight/index.d.ts +3 -0
  6. package/es/plugins/link-highlight/index.js +4 -0
  7. package/es/plugins/link-highlight/node/link-highlight.d.ts +29 -0
  8. package/es/plugins/link-highlight/node/link-highlight.js +223 -0
  9. package/es/plugins/link-highlight/plugin/index.d.ts +15 -0
  10. package/es/plugins/link-highlight/plugin/index.js +187 -0
  11. package/es/plugins/link-highlight/plugin/registry.d.ts +6 -0
  12. package/es/plugins/link-highlight/plugin/registry.js +61 -0
  13. package/es/plugins/link-highlight/react/ReactLinkHighlightPlugin.d.ts +4 -0
  14. package/es/plugins/link-highlight/react/ReactLinkHighlightPlugin.js +35 -0
  15. package/es/plugins/link-highlight/react/index.d.ts +1 -0
  16. package/es/plugins/link-highlight/react/index.js +1 -0
  17. package/es/plugins/link-highlight/react/style.d.ts +3 -0
  18. package/es/plugins/link-highlight/react/style.js +10 -0
  19. package/es/plugins/link-highlight/react/type.d.ts +13 -0
  20. package/es/plugins/link-highlight/react/type.js +1 -0
  21. package/es/plugins/link-highlight/utils/index.d.ts +17 -0
  22. package/es/plugins/link-highlight/utils/index.js +43 -0
  23. package/es/plugins/markdown/index.d.ts +1 -0
  24. package/es/plugins/markdown/index.js +2 -1
  25. package/es/plugins/markdown/plugin/index.js +13 -2
  26. package/es/plugins/markdown/utils/url-validator.d.ts +4 -0
  27. package/es/plugins/markdown/utils/url-validator.js +6 -0
  28. package/es/react/hooks/useEditorState/index.js +46 -6
  29. package/es/utils/url.d.ts +15 -0
  30. package/es/utils/url.js +51 -0
  31. package/package.json +1 -1
package/es/index.d.ts CHANGED
@@ -7,6 +7,7 @@ export * from './plugins/file';
7
7
  export * from './plugins/hr';
8
8
  export * from './plugins/image';
9
9
  export * from './plugins/link';
10
+ export * from './plugins/link-highlight';
10
11
  export * from './plugins/list';
11
12
  export * from './plugins/markdown';
12
13
  export * from './plugins/math';
@@ -17,6 +18,7 @@ export * from './plugins/upload';
17
18
  export type { IEditor } from './types';
18
19
  export * from './types/hotkey';
19
20
  export { getHotkeyById } from './utils/hotkey/registerHotkey';
21
+ export { isPureUrl, isValidUrl } from './utils/url';
20
22
  export { browserDebug, createDebugLogger, debugLogger, debugLoggers, devConsole, prodSafeLogger, } from './utils/debug';
21
23
  export { Kernel } from './editor-kernel/kernel';
22
24
  /**
package/es/index.js CHANGED
@@ -7,6 +7,7 @@ export * from "./plugins/file";
7
7
  export * from "./plugins/hr";
8
8
  export * from "./plugins/image";
9
9
  export * from "./plugins/link";
10
+ export * from "./plugins/link-highlight";
10
11
  export * from "./plugins/list";
11
12
  export * from "./plugins/markdown";
12
13
  export * from "./plugins/math";
@@ -18,6 +19,9 @@ export * from "./plugins/upload";
18
19
  export * from "./types/hotkey";
19
20
  export { getHotkeyById } from "./utils/hotkey/registerHotkey";
20
21
 
22
+ // URL utilities
23
+ export { isPureUrl, isValidUrl } from "./utils/url";
24
+
21
25
  // Debug utilities
22
26
  export { browserDebug, createDebugLogger, debugLogger, debugLoggers, devConsole, prodSafeLogger } from "./utils/debug";
23
27
 
@@ -0,0 +1,3 @@
1
+ import { LexicalEditor } from 'lexical';
2
+ export declare const INSERT_LINK_HIGHLIGHT_COMMAND: import("lexical").LexicalCommand<undefined>;
3
+ export declare function registerLinkHighlightCommand(editor: LexicalEditor): () => void;
@@ -0,0 +1,46 @@
1
+ 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; } } }; }
2
+ 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); }
3
+ 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; }
4
+ import { $getSelection, $insertNodes, $isRangeSelection, COMMAND_PRIORITY_EDITOR, createCommand } from 'lexical';
5
+ import { $createCursorNode } from "../../common/node/cursor";
6
+ import { $createLinkHighlightNode, $isLinkHighlightNode, getLinkHighlightNode } from "../node/link-highlight";
7
+ export var INSERT_LINK_HIGHLIGHT_COMMAND = createCommand('INSERT_LINK_HIGHLIGHT_COMMAND');
8
+ export function registerLinkHighlightCommand(editor) {
9
+ return editor.registerCommand(INSERT_LINK_HIGHLIGHT_COMMAND, function () {
10
+ editor.update(function () {
11
+ var selection = $getSelection();
12
+ if (!selection || !$isRangeSelection(selection)) {
13
+ return false;
14
+ }
15
+ var focusNode = selection.focus.getNode();
16
+ var anchorNode = selection.anchor.getNode();
17
+ var linkHighlight = getLinkHighlightNode(focusNode);
18
+ if (linkHighlight !== getLinkHighlightNode(anchorNode)) {
19
+ return false;
20
+ }
21
+ if ($isLinkHighlightNode(linkHighlight)) {
22
+ // Remove link highlight formatting
23
+ var _iterator = _createForOfIteratorHelper(linkHighlight.getChildren().slice(0)),
24
+ _step;
25
+ try {
26
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
27
+ var node = _step.value;
28
+ linkHighlight.insertBefore(node);
29
+ }
30
+ } catch (err) {
31
+ _iterator.e(err);
32
+ } finally {
33
+ _iterator.f();
34
+ }
35
+ linkHighlight.remove();
36
+ return true;
37
+ }
38
+
39
+ // Create link highlight with selected text
40
+ var linkHighlightNode = $createLinkHighlightNode(selection.getTextContent());
41
+ $insertNodes([linkHighlightNode, $createCursorNode()]);
42
+ linkHighlightNode.select();
43
+ });
44
+ return true;
45
+ }, COMMAND_PRIORITY_EDITOR);
46
+ }
@@ -0,0 +1,3 @@
1
+ export * from './command';
2
+ export * from './plugin';
3
+ export * from './react';
@@ -0,0 +1,4 @@
1
+ export * from "./command";
2
+ export * from "./plugin";
3
+ export * from "./react";
4
+ // Note: utils are internal only and not exported to avoid conflicts with other plugins
@@ -0,0 +1,29 @@
1
+ import { EditorConfig, LexicalEditor, LexicalNode, SerializedElementNode } from 'lexical';
2
+ import { CardLikeElementNode } from "../../common/node/cursor";
3
+ export type SerializedLinkHighlightNode = SerializedElementNode;
4
+ export declare class LinkHighlightNode extends CardLikeElementNode {
5
+ static getType(): string;
6
+ static clone(node: LinkHighlightNode): LinkHighlightNode;
7
+ static importJSON(serializedNode: SerializedLinkHighlightNode): LinkHighlightNode;
8
+ createDOM(config: EditorConfig, editor: LexicalEditor): HTMLElement;
9
+ /**
10
+ * Format URL to ensure it has proper protocol
11
+ */
12
+ private formatUrl;
13
+ getDOMSlot(element: HTMLElement): import("lexical").ElementDOMSlot<HTMLElement>;
14
+ canBeEmpty(): boolean;
15
+ isCardLike(): boolean;
16
+ isInline(): boolean;
17
+ canIndent(): boolean;
18
+ canInsertTextBefore(): boolean;
19
+ canInsertTextAfter(): boolean;
20
+ updateDOM(prevNode: LinkHighlightNode, dom: HTMLElement, config: EditorConfig): boolean;
21
+ /**
22
+ * Get the URL from the text content
23
+ */
24
+ getURL(): string;
25
+ }
26
+ export declare function $createLinkHighlightNode(textContent?: string): LinkHighlightNode;
27
+ export declare function $isLinkHighlightNode(node: unknown): node is LinkHighlightNode;
28
+ export declare function getLinkHighlightNode(node: LexicalNode): LexicalNode | null;
29
+ export declare function $isSelectionInLinkHighlight(editor: LexicalEditor): boolean;
@@ -0,0 +1,223 @@
1
+ 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); }
2
+ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
3
+ 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); } }
4
+ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
5
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : String(i); }
6
+ 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); }
7
+ 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); }
8
+ function _superPropBase(object, property) { while (!Object.prototype.hasOwnProperty.call(object, property)) { object = _getPrototypeOf(object); if (object === null) break; } return object; }
9
+ 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); }
10
+ function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
11
+ 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); }; }
12
+ 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); }
13
+ function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
14
+ 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; } }
15
+ function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
16
+ /* eslint-disable @typescript-eslint/no-use-before-define */
17
+ import { addClassNamesToElement } from '@lexical/utils';
18
+ import { $applyNodeReplacement, $createTextNode, $getSelection, $isNodeSelection, $isRangeSelection } from 'lexical';
19
+ import { $createCursorNode, $isCursorNode, CardLikeElementNode } from "../../common/node/cursor";
20
+ export var LinkHighlightNode = /*#__PURE__*/function (_CardLikeElementNode) {
21
+ _inherits(LinkHighlightNode, _CardLikeElementNode);
22
+ var _super = _createSuper(LinkHighlightNode);
23
+ function LinkHighlightNode() {
24
+ _classCallCheck(this, LinkHighlightNode);
25
+ return _super.apply(this, arguments);
26
+ }
27
+ _createClass(LinkHighlightNode, [{
28
+ key: "createDOM",
29
+ value: function createDOM(config, editor) {
30
+ var _this = this;
31
+ var element = document.createElement('span');
32
+ // eslint-disable-next-line unicorn/prefer-dom-node-dataset
33
+ element.setAttribute('data-lexical-key', this.getKey());
34
+ var childContainer = document.createElement('ne-content');
35
+ element.append(childContainer);
36
+ addClassNamesToElement(element, config.theme.linkHighlight);
37
+
38
+ // Add click handler to open link in new window
39
+ element.addEventListener('click', function (event) {
40
+ // Only handle left click without modifier keys
41
+ if (event.button === 0 && !event.metaKey && !event.ctrlKey && !event.shiftKey) {
42
+ event.preventDefault();
43
+ event.stopPropagation();
44
+
45
+ // Read URL in editor context
46
+ editor.read(function () {
47
+ var url = _this.getURL();
48
+ if (url) {
49
+ // Format URL to ensure it has proper protocol
50
+ var formattedUrl = _this.formatUrl(url);
51
+ window.open(formattedUrl, '_blank', 'noopener,noreferrer');
52
+ }
53
+ });
54
+ }
55
+ });
56
+ return element;
57
+ }
58
+
59
+ /**
60
+ * Format URL to ensure it has proper protocol
61
+ */
62
+ }, {
63
+ key: "formatUrl",
64
+ value: function formatUrl(url) {
65
+ // Check if URL already has a protocol
66
+ if (/^[a-z][\d+.a-z-]*:/i.test(url)) {
67
+ return url;
68
+ }
69
+ // Check if it's a relative path
70
+ if (/^[#./]/.test(url)) {
71
+ return url;
72
+ }
73
+ // Check for email address
74
+ if (url.includes('@') && !url.startsWith('mailto:')) {
75
+ return "mailto:".concat(url);
76
+ }
77
+ // Check for phone number
78
+ if (/^\+?[\d\s()-]{5,}$/.test(url)) {
79
+ return "tel:".concat(url);
80
+ }
81
+ // Default to https
82
+ if (url.startsWith('www.')) {
83
+ return "https://".concat(url);
84
+ }
85
+ // If no protocol, assume https
86
+ if (!url.includes('://')) {
87
+ return "https://".concat(url);
88
+ }
89
+ return url;
90
+ }
91
+ }, {
92
+ key: "getDOMSlot",
93
+ value: function getDOMSlot(element) {
94
+ var neContent = element.querySelector('ne-content');
95
+ if (!neContent) {
96
+ throw new Error('LinkHighlightNode: ne-content not found');
97
+ }
98
+ return _get(_getPrototypeOf(LinkHighlightNode.prototype), "getDOMSlot", this).call(this, element).withElement(neContent);
99
+ }
100
+ }, {
101
+ key: "canBeEmpty",
102
+ value: function canBeEmpty() {
103
+ return false;
104
+ }
105
+ }, {
106
+ key: "isCardLike",
107
+ value: function isCardLike() {
108
+ return true;
109
+ }
110
+ }, {
111
+ key: "isInline",
112
+ value: function isInline() {
113
+ return true;
114
+ }
115
+ }, {
116
+ key: "canIndent",
117
+ value: function canIndent() {
118
+ return false;
119
+ }
120
+ }, {
121
+ key: "canInsertTextBefore",
122
+ value: function canInsertTextBefore() {
123
+ return true;
124
+ }
125
+ }, {
126
+ key: "canInsertTextAfter",
127
+ value: function canInsertTextAfter() {
128
+ return true;
129
+ }
130
+ }, {
131
+ key: "updateDOM",
132
+ value: function updateDOM(prevNode, dom, config) {
133
+ // Update the class names if theme has changed
134
+ var prevTheme = prevNode ? prevNode : null;
135
+ if (prevTheme !== this) {
136
+ addClassNamesToElement(dom, config.theme.linkHighlight);
137
+ }
138
+ return false;
139
+ }
140
+
141
+ /**
142
+ * Get the URL from the text content
143
+ */
144
+ }, {
145
+ key: "getURL",
146
+ value: function getURL() {
147
+ return this.getTextContent().trim();
148
+ }
149
+ }], [{
150
+ key: "getType",
151
+ value: function getType() {
152
+ return 'linkHighlight';
153
+ }
154
+ }, {
155
+ key: "clone",
156
+ value: function clone(node) {
157
+ return new LinkHighlightNode(node.__key);
158
+ }
159
+ }, {
160
+ key: "importJSON",
161
+ value: function importJSON(serializedNode) {
162
+ return $createLinkHighlightNode().updateFromJSON(serializedNode);
163
+ }
164
+ }]);
165
+ return LinkHighlightNode;
166
+ }(CardLikeElementNode);
167
+ export function $createLinkHighlightNode(textContent) {
168
+ var linkHighlightNode = $applyNodeReplacement(new LinkHighlightNode());
169
+ var cursorNode = $createCursorNode();
170
+ linkHighlightNode.append(cursorNode);
171
+ if (textContent) {
172
+ linkHighlightNode.append($createTextNode(textContent));
173
+ }
174
+ return linkHighlightNode;
175
+ }
176
+ export function $isLinkHighlightNode(node) {
177
+ return node instanceof LinkHighlightNode;
178
+ }
179
+ export function getLinkHighlightNode(node) {
180
+ if ($isCursorNode(node)) {
181
+ var parent = node.getParent();
182
+ if ($isLinkHighlightNode(parent)) {
183
+ return parent;
184
+ }
185
+ if ($isLinkHighlightNode(node.getNextSibling())) {
186
+ return node.getNextSibling();
187
+ }
188
+ if ($isLinkHighlightNode(node.getPreviousSibling())) {
189
+ return node.getPreviousSibling();
190
+ }
191
+ return null;
192
+ }
193
+ if ($isLinkHighlightNode(node.getParent())) {
194
+ return node.getParent();
195
+ }
196
+ return null;
197
+ }
198
+ export function $isSelectionInLinkHighlight(editor) {
199
+ return editor.read(function () {
200
+ var selection = $getSelection();
201
+ if (!selection) {
202
+ return false;
203
+ }
204
+ if ($isRangeSelection(selection)) {
205
+ var focusNode = selection.focus.getNode();
206
+ var anchorNode = selection.anchor.getNode();
207
+ var linkHighlight = getLinkHighlightNode(focusNode);
208
+ if (linkHighlight !== getLinkHighlightNode(anchorNode)) {
209
+ return false;
210
+ }
211
+ if ($isLinkHighlightNode(linkHighlight)) {
212
+ return true;
213
+ }
214
+ return false;
215
+ } else if ($isNodeSelection(selection)) {
216
+ var nodes = selection.getNodes();
217
+ if (nodes.length === 1 && $isLinkHighlightNode(nodes[0])) {
218
+ return true;
219
+ }
220
+ }
221
+ return false;
222
+ });
223
+ }
@@ -0,0 +1,15 @@
1
+ import type { IEditorPluginConstructor } from "../../../types";
2
+ export interface LinkHighlightPluginOptions {
3
+ enableHotkey?: boolean;
4
+ /**
5
+ * Enable auto-highlight when pasting URLs
6
+ * @default true
7
+ */
8
+ enablePasteAutoHighlight?: boolean;
9
+ theme?: string;
10
+ /**
11
+ * Custom URL validation regex
12
+ */
13
+ urlRegex?: RegExp;
14
+ }
15
+ export declare const LinkHighlightPlugin: IEditorPluginConstructor<LinkHighlightPluginOptions>;
@@ -0,0 +1,187 @@
1
+ var _class;
2
+ 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); }
3
+ function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
4
+ 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."); }
5
+ 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); }
6
+ 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; }
7
+ 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; } }
8
+ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
9
+ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
10
+ 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); } }
11
+ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
12
+ 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); }
13
+ function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
14
+ 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); }; }
15
+ 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); }
16
+ function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
17
+ 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; } }
18
+ function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
19
+ 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; }
20
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : String(i); }
21
+ 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); }
22
+ import { $getSelection, $isRangeSelection, COMMAND_PRIORITY_NORMAL, PASTE_COMMAND } from 'lexical';
23
+ import { INodeHelper } from "../../../editor-kernel/inode/helper";
24
+ import { KernelPlugin } from "../../../editor-kernel/plugin";
25
+ import { $createCursorNode, cursorNodeSerialized } from "../../common/node/cursor";
26
+ import { IMarkdownShortCutService, MARKDOWN_READER_LEVEL_HIGH } from "../../markdown/service/shortcut";
27
+ import { createDebugLogger } from "../../../utils/debug";
28
+ import { registerLinkHighlightCommand } from "../command";
29
+ import { $createLinkHighlightNode, LinkHighlightNode } from "../node/link-highlight";
30
+ import { isValidUrl } from "../utils";
31
+ import { registerLinkHighlight } from "./registry";
32
+ export var LinkHighlightPlugin = (_class = /*#__PURE__*/function (_KernelPlugin) {
33
+ _inherits(LinkHighlightPlugin, _KernelPlugin);
34
+ var _super = _createSuper(LinkHighlightPlugin);
35
+ function LinkHighlightPlugin(kernel, config) {
36
+ var _this;
37
+ _classCallCheck(this, LinkHighlightPlugin);
38
+ _this = _super.call(this);
39
+ _defineProperty(_assertThisInitialized(_this), "logger", createDebugLogger('plugin', 'link-highlight'));
40
+ _defineProperty(_assertThisInitialized(_this), "urlRegex", void 0);
41
+ _this.kernel = kernel;
42
+ _this.config = config;
43
+ kernel.registerNodes([LinkHighlightNode]);
44
+ kernel.registerThemes({
45
+ linkHighlight: (config === null || config === void 0 ? void 0 : config.theme) || 'editor-link-highlight'
46
+ });
47
+
48
+ // Default URL regex that matches http(s), mailto, tel, etc.
49
+ _this.urlRegex = (config === null || config === void 0 ? void 0 : config.urlRegex) || /^(?:https?:\/\/|mailto:|tel:)[^\s"<>[\\\]^`{|}]+|^www\.[^\s"<>[\\\]^`{|}]+/i;
50
+ _this.logger.debug('LinkHighlightPlugin initialized');
51
+ return _this;
52
+ }
53
+ _createClass(LinkHighlightPlugin, [{
54
+ key: "onInit",
55
+ value: function onInit(editor) {
56
+ var _this$config,
57
+ _this$config2,
58
+ _this2 = this;
59
+ this.register(registerLinkHighlightCommand(editor));
60
+ this.register(registerLinkHighlight(editor, this.kernel, {
61
+ enableHotkey: (_this$config = this.config) === null || _this$config === void 0 ? void 0 : _this$config.enableHotkey
62
+ }));
63
+
64
+ // Register paste handler for auto-highlighting URLs
65
+ if (((_this$config2 = this.config) === null || _this$config2 === void 0 ? void 0 : _this$config2.enablePasteAutoHighlight) !== false) {
66
+ this.register(editor.registerCommand(PASTE_COMMAND, function (payload) {
67
+ var clipboardData = payload.clipboardData;
68
+ if (clipboardData && clipboardData.types && clipboardData.types.length === 1 && clipboardData.types[0] === 'text/plain') {
69
+ var data = clipboardData.getData('text/plain').trim();
70
+ // Check if the pasted content is a valid URL
71
+ if (_this2.urlRegex.test(data) && isValidUrl(data)) {
72
+ payload.stopImmediatePropagation();
73
+ payload.preventDefault();
74
+ _this2.logger.debug('Auto-highlighting pasted URL:', data);
75
+
76
+ // Insert LinkHighlightNode directly
77
+ editor.update(function () {
78
+ var selection = $getSelection();
79
+ if ($isRangeSelection(selection)) {
80
+ var linkHighlightNode = $createLinkHighlightNode(data);
81
+ var cursorNode = $createCursorNode();
82
+ selection.insertNodes([linkHighlightNode, cursorNode]);
83
+ }
84
+ });
85
+ return true;
86
+ }
87
+ }
88
+ return false;
89
+ }, COMMAND_PRIORITY_NORMAL));
90
+ }
91
+ var markdownService = this.kernel.requireService(IMarkdownShortCutService);
92
+ if (!markdownService) {
93
+ return;
94
+ }
95
+
96
+ // Register markdown writer for <link> format
97
+ markdownService.registerMarkdownWriter(LinkHighlightNode.getType(), function (ctx, node) {
98
+ ctx.appendLine("<".concat(node.getTextContent(), ">"));
99
+ return true;
100
+ });
101
+
102
+ // Register markdown shortcut for auto-link syntax: <url>
103
+ // Matches URLs wrapped in angle brackets like <https://example.com>
104
+ markdownService.registerMarkdownShortCut({
105
+ regExp: /<((?:https?:\/\/|mailto:|tel:|www\.)[^\s"<>[\\\]^`{|}]+)>\s?$/,
106
+ replace: function replace(textNode, match) {
107
+ var _match = _slicedToArray(match, 2),
108
+ url = _match[1];
109
+ if (!url || !isValidUrl(url)) {
110
+ return;
111
+ }
112
+ _this2.logger.debug('Converting markdown auto-link to LinkHighlightNode:', url);
113
+ var linkHighlightNode = $createLinkHighlightNode(url);
114
+ var cursorNode = $createCursorNode();
115
+ textNode.replace(linkHighlightNode);
116
+ linkHighlightNode.insertAfter(cursorNode);
117
+ return undefined;
118
+ },
119
+ trigger: '>',
120
+ type: 'text-match'
121
+ });
122
+
123
+ // Register HTML reader to handle <url> syntax that might be parsed as HTML
124
+ markdownService.registerMarkdownReader('html', function (node) {
125
+ var htmlValue = node.value || '';
126
+
127
+ // Check if this looks like an auto-link: <url>
128
+ var match = htmlValue.match(/^<((?:https?:\/\/|mailto:|tel:|www\.)[^\s"<>[\\\]^`{|}]+)>$/);
129
+ if (match) {
130
+ var url = match[1].replaceAll(/[\u200B-\u200D\u2060\uFEFF]/g, '');
131
+ _this2.logger.debug('Converting HTML auto-link to LinkHighlightNode:', url);
132
+ return [INodeHelper.createElementNode('linkHighlight', {
133
+ children: [cursorNodeSerialized, INodeHelper.createTextNode(url, {})],
134
+ direction: 'ltr',
135
+ format: '',
136
+ indent: 0,
137
+ type: 'linkHighlight',
138
+ version: 1
139
+ }), cursorNodeSerialized];
140
+ }
141
+
142
+ // Not an auto-link, let other handlers process it
143
+ return false;
144
+ }, MARKDOWN_READER_LEVEL_HIGH);
145
+
146
+ // Register markdown reader for 'link' type (auto-links like <url>)
147
+ // Use HIGH priority to handle auto-links before standard Link plugin
148
+ markdownService.registerMarkdownReader('link', function (node, children) {
149
+ // Check if this is an auto-link (URL and text are the same)
150
+ var url = node.url || '';
151
+
152
+ // Check if we have text children
153
+ if (!children || children.length === 0) {
154
+ return false;
155
+ }
156
+
157
+ // Get text content from children
158
+ var textContent = children.filter(function (child) {
159
+ return child.type === 'text';
160
+ }).map(function (child) {
161
+ return child.text || '';
162
+ }).join('');
163
+
164
+ // If text matches URL exactly (auto-link syntax), convert to LinkHighlightNode
165
+ if (textContent === url) {
166
+ _this2.logger.debug('Converting markdown auto-link to LinkHighlightNode:', url);
167
+ // Return array with LinkHighlightNode and trailing cursor
168
+ // Structure matches CodeNode: [node with internal cursor + text, external cursor]
169
+ return [INodeHelper.createElementNode('linkHighlight', {
170
+ children: [cursorNodeSerialized, INodeHelper.createTextNode(url, {})],
171
+ direction: 'ltr',
172
+ format: '',
173
+ indent: 0,
174
+ type: 'linkHighlight',
175
+ version: 1
176
+ }), cursorNodeSerialized];
177
+ }
178
+
179
+ // Otherwise, let standard Link plugin handle it
180
+ return false;
181
+ }, MARKDOWN_READER_LEVEL_HIGH // High priority to intercept auto-links
182
+ );
183
+ this.logger.debug('LinkHighlightPlugin initialized with markdown support');
184
+ }
185
+ }]);
186
+ return LinkHighlightPlugin;
187
+ }(KernelPlugin), _defineProperty(_class, "pluginName", 'LinkHighlightPlugin'), _class);
@@ -0,0 +1,6 @@
1
+ import { LexicalEditor } from 'lexical';
2
+ import { IEditorKernel } from "../../../types";
3
+ export interface LinkHighlightRegistryOptions {
4
+ enableHotkey?: boolean;
5
+ }
6
+ export declare function registerLinkHighlight(editor: LexicalEditor, kernel: IEditorKernel, options?: LinkHighlightRegistryOptions): () => void;
@@ -0,0 +1,61 @@
1
+ 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; } } }; }
2
+ 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); }
3
+ 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; }
4
+ import { mergeRegister } from '@lexical/utils';
5
+ import { $getNodeByKey } from 'lexical';
6
+ import { $createCursorNode } from "../../common/node/cursor";
7
+ import { HotkeyEnum } from "../../../types/hotkey";
8
+ import { INSERT_LINK_HIGHLIGHT_COMMAND } from "../command";
9
+ import { LinkHighlightNode } from "../node/link-highlight";
10
+ export function registerLinkHighlight(editor, kernel, options) {
11
+ var _ref = options || {},
12
+ _ref$enableHotkey = _ref.enableHotkey,
13
+ enableHotkey = _ref$enableHotkey === void 0 ? true : _ref$enableHotkey;
14
+ return mergeRegister(
15
+ // Update listener to ensure cursor node before first LinkHighlightNode
16
+ editor.registerUpdateListener(function (_ref2) {
17
+ var mutatedNodes = _ref2.mutatedNodes;
18
+ var linkHighlightChanged = mutatedNodes === null || mutatedNodes === void 0 ? void 0 : mutatedNodes.get(LinkHighlightNode);
19
+ var keys = (linkHighlightChanged === null || linkHighlightChanged === void 0 ? void 0 : linkHighlightChanged.keys()) || [];
20
+ var needAddBefore = new Set();
21
+ editor.read(function () {
22
+ var _iterator = _createForOfIteratorHelper(keys),
23
+ _step;
24
+ try {
25
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
26
+ var key = _step.value;
27
+ var node = $getNodeByKey(key);
28
+ if (!node) {
29
+ return;
30
+ }
31
+ var parent = node.getParent();
32
+ if ((parent === null || parent === void 0 ? void 0 : parent.getFirstChild()) === node) {
33
+ needAddBefore.add(node);
34
+ }
35
+ }
36
+ } catch (err) {
37
+ _iterator.e(err);
38
+ } finally {
39
+ _iterator.f();
40
+ }
41
+ });
42
+ if (needAddBefore.size > 0) {
43
+ editor.update(function () {
44
+ needAddBefore.forEach(function (node) {
45
+ var prev = node.getPreviousSibling();
46
+ if (!prev) {
47
+ node.insertBefore($createCursorNode());
48
+ }
49
+ });
50
+ });
51
+ }
52
+ }),
53
+ // Hotkey for toggling link highlight (Ctrl+K or Cmd+K)
54
+ kernel.registerHotkey(HotkeyEnum.Link, function () {
55
+ return editor.dispatchCommand(INSERT_LINK_HIGHLIGHT_COMMAND, undefined);
56
+ }, {
57
+ enabled: enableHotkey,
58
+ preventDefault: true,
59
+ stopPropagation: true
60
+ }));
61
+ }
@@ -0,0 +1,4 @@
1
+ import { type FC } from 'react';
2
+ import { ReactLinkHighlightPluginProps } from './type';
3
+ declare const ReactLinkHighlightPlugin: FC<ReactLinkHighlightPluginProps>;
4
+ export default ReactLinkHighlightPlugin;
@@ -0,0 +1,35 @@
1
+ function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
2
+ 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."); }
3
+ 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); }
4
+ 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; }
5
+ 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; } }
6
+ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
7
+ import { useLayoutEffect } from 'react';
8
+ import { useLexicalComposerContext } from "../../../editor-kernel/react/react-context";
9
+ import { MarkdownPlugin } from "../../markdown/plugin";
10
+ import { LinkHighlightPlugin } from "../plugin";
11
+ import { useStyles } from "./style";
12
+ var ReactLinkHighlightPlugin = function ReactLinkHighlightPlugin(_ref) {
13
+ var className = _ref.className,
14
+ _ref$enableHotkey = _ref.enableHotkey,
15
+ enableHotkey = _ref$enableHotkey === void 0 ? true : _ref$enableHotkey,
16
+ _ref$enablePasteAutoH = _ref.enablePasteAutoHighlight,
17
+ enablePasteAutoHighlight = _ref$enablePasteAutoH === void 0 ? true : _ref$enablePasteAutoH;
18
+ var _useLexicalComposerCo = useLexicalComposerContext(),
19
+ _useLexicalComposerCo2 = _slicedToArray(_useLexicalComposerCo, 1),
20
+ editor = _useLexicalComposerCo2[0];
21
+ var _useStyles = useStyles(),
22
+ cx = _useStyles.cx,
23
+ styles = _useStyles.styles;
24
+ useLayoutEffect(function () {
25
+ editor.registerPlugin(MarkdownPlugin);
26
+ editor.registerPlugin(LinkHighlightPlugin, {
27
+ enableHotkey: enableHotkey,
28
+ enablePasteAutoHighlight: enablePasteAutoHighlight,
29
+ theme: cx(styles.linkHighlight, className)
30
+ });
31
+ }, [className, cx, enableHotkey, enablePasteAutoHighlight, editor, styles.linkHighlight]);
32
+ return null;
33
+ };
34
+ ReactLinkHighlightPlugin.displayName = 'ReactLinkHighlightPlugin';
35
+ export default ReactLinkHighlightPlugin;
@@ -0,0 +1 @@
1
+ export { default as ReactLinkHighlightPlugin } from './ReactLinkHighlightPlugin';
@@ -0,0 +1 @@
1
+ export { default as ReactLinkHighlightPlugin } from "./ReactLinkHighlightPlugin";
@@ -0,0 +1,3 @@
1
+ export declare const useStyles: (props?: unknown) => import("antd-style").ReturnStyles<{
2
+ linkHighlight: import("antd-style").SerializedStyles;
3
+ }>;
@@ -0,0 +1,10 @@
1
+ var _templateObject;
2
+ function _taggedTemplateLiteral(strings, raw) { if (!raw) { raw = strings.slice(0); } return Object.freeze(Object.defineProperties(strings, { raw: { value: Object.freeze(raw) } })); }
3
+ import { createStyles } from 'antd-style';
4
+ export var useStyles = createStyles(function (_ref) {
5
+ var css = _ref.css,
6
+ token = _ref.token;
7
+ return {
8
+ linkHighlight: css(_templateObject || (_templateObject = _taggedTemplateLiteral(["\n cursor: pointer;\n\n display: inline;\n\n padding: 2px;\n\n color: ", ";\n text-decoration: none;\n\n transition: all 0.2s ease;\n\n &:hover {\n color: ", ";\n text-decoration: underline;\n }\n\n ne-content {\n display: inline;\n }\n "])), token.colorLink, token.colorLinkHover)
9
+ };
10
+ });
@@ -0,0 +1,13 @@
1
+ export interface ReactLinkHighlightPluginProps {
2
+ className?: string;
3
+ /**
4
+ * Enable keyboard shortcut (Ctrl+K / Cmd+K)
5
+ * @default true
6
+ */
7
+ enableHotkey?: boolean;
8
+ /**
9
+ * Enable auto-highlight when pasting URLs
10
+ * @default true
11
+ */
12
+ enablePasteAutoHighlight?: boolean;
13
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,17 @@
1
+ export { isValidUrl } from "../../../utils/url";
2
+ /**
3
+ * Sanitizes a URL to ensure it's safe to use
4
+ * @param url - The URL to sanitize
5
+ * @returns The sanitized URL
6
+ */
7
+ export declare function sanitizeUrl(url: string): string;
8
+ /**
9
+ * Extracts URL from text
10
+ * @param text - The text to extract URL from
11
+ * @returns The extracted URL information or null
12
+ */
13
+ export declare function extractUrlFromText(text: string): {
14
+ index: number;
15
+ length: number;
16
+ url: string;
17
+ } | null;
@@ -0,0 +1,43 @@
1
+ // Re-export shared URL validation from utils
2
+ export { isValidUrl } from "../../../utils/url";
3
+
4
+ // URL validation regex that matches common URL patterns for extraction
5
+ var urlRegExp = new RegExp(/((([A-Za-z]{3,9}:(?:\/\/)?)(?:[\w$&+,:;=-]+@)?[\d.A-Za-z-]+|(?:www.|[\w$&+,:;=-]+@)[\d.A-Za-z-]+)((?:\/[%+./~\w-_]*)?\??[\w%&+.;=@-]*#?\w*)?)/);
6
+
7
+ /**
8
+ * Sanitizes a URL to ensure it's safe to use
9
+ * @param url - The URL to sanitize
10
+ * @returns The sanitized URL
11
+ */
12
+ export function sanitizeUrl(url) {
13
+ var SUPPORTED_URL_PROTOCOLS = new Set(['http:', 'https:', 'mailto:', 'sms:', 'tel:']);
14
+ try {
15
+ var parsedUrl = new URL(url);
16
+ if (!SUPPORTED_URL_PROTOCOLS.has(parsedUrl.protocol)) {
17
+ return 'about:blank';
18
+ }
19
+ } catch (_unused) {
20
+ return url;
21
+ }
22
+ return url;
23
+ }
24
+
25
+ /**
26
+ * Extracts URL from text
27
+ * @param text - The text to extract URL from
28
+ * @returns The extracted URL information or null
29
+ */
30
+ export function extractUrlFromText(text) {
31
+ var _index;
32
+ var match = urlRegExp.exec(text);
33
+ if (!match) return null;
34
+ var raw = match[0];
35
+ var start = (_index = match.index) !== null && _index !== void 0 ? _index : text.indexOf(raw);
36
+ // Trim trailing punctuation that often follows inline links
37
+ var trimmed = raw.replace(/[\),\.:;\]]+$/, '');
38
+ return {
39
+ index: start,
40
+ length: trimmed.length,
41
+ url: trimmed
42
+ };
43
+ }
@@ -5,3 +5,4 @@ export type { MARKDOWN_READER_LEVEL } from './service/shortcut';
5
5
  export { IMarkdownShortCutService, MARKDOWN_READER_LEVEL_HIGH, MARKDOWN_READER_LEVEL_NORMAL, MARKDOWN_WRITER_LEVEL_MAX, } from './service/shortcut';
6
6
  export { isPunctuationChar } from './utils';
7
7
  export { detectCodeLanguage, detectLanguage } from './utils/detectLanguage';
8
+ export { isPureUrl, isValidUrl } from './utils/url-validator';
@@ -3,4 +3,5 @@ export { MarkdownPlugin } from "./plugin";
3
3
  export { default as ReactMarkdownPlugin } from "./react";
4
4
  export { IMarkdownShortCutService, MARKDOWN_READER_LEVEL_HIGH, MARKDOWN_READER_LEVEL_NORMAL, MARKDOWN_WRITER_LEVEL_MAX } from "./service/shortcut";
5
5
  export { isPunctuationChar } from "./utils";
6
- export { detectCodeLanguage, detectLanguage } from "./utils/detectLanguage";
6
+ export { detectCodeLanguage, detectLanguage } from "./utils/detectLanguage";
7
+ export { isPureUrl, isValidUrl } from "./utils/url-validator";
@@ -23,6 +23,7 @@ import MarkdownDataSource from "../data-source/markdown-data-source";
23
23
  import { IMarkdownShortCutService, MarkdownShortCutService } from "../service/shortcut";
24
24
  import { canContainTransformableMarkdown } from "../utils";
25
25
  import { detectCodeLanguage, detectLanguage } from "../utils/detectLanguage";
26
+ import { isValidUrl as isValidLinkUrl } from "../utils/url-validator";
26
27
  export var MarkdownPlugin = (_class = /*#__PURE__*/function (_KernelPlugin) {
27
28
  _inherits(MarkdownPlugin, _KernelPlugin);
28
29
  var _super = _createSuper(MarkdownPlugin);
@@ -126,8 +127,10 @@ export var MarkdownPlugin = (_class = /*#__PURE__*/function (_KernelPlugin) {
126
127
  var clipboardData = event.clipboardData;
127
128
  if (!clipboardData) return false;
128
129
 
129
- // Get clipboard content
130
- var text = clipboardData.getData('text/plain').trimEnd();
130
+ // Get clipboard content and clean BOM/zero-width characters
131
+ var rawText = clipboardData.getData('text/plain').trimEnd();
132
+ // Remove BOM, zero-width spaces, and other invisible characters
133
+ var text = rawText.replaceAll(/[\u200B-\u200D\u2060\uFEFF]/g, '');
131
134
  var html = clipboardData.getData('text/html').trimEnd();
132
135
 
133
136
  // If there's no text content, let Lexical handle it
@@ -139,6 +142,13 @@ export var MarkdownPlugin = (_class = /*#__PURE__*/function (_KernelPlugin) {
139
142
  textLength: text.length
140
143
  });
141
144
 
145
+ // Check if the pasted content is a pure URL
146
+ // If so, let Link/LinkHighlight plugins handle it
147
+ if (clipboardData.types.length === 1 && clipboardData.types[0] === 'text/plain' && isValidLinkUrl(text)) {
148
+ _this2.logger.debug('pure URL detected, letting Link/LinkHighlight plugins handle');
149
+ return false; // Let other plugins handle URL paste
150
+ }
151
+
142
152
  // Check if cursor is inside code block or inline code
143
153
  // If so, always paste as plain text
144
154
  var isInCodeBlock = editor.read(function () {
@@ -206,6 +216,7 @@ export var MarkdownPlugin = (_class = /*#__PURE__*/function (_KernelPlugin) {
206
216
  }
207
217
 
208
218
  // Check if markdown paste formatting is enabled (default: true)
219
+ // Note: URL pasting is handled by Link/LinkHighlight plugins themselves
209
220
 
210
221
  // Check if content is code (JSON, SQL, etc.) and should be inserted as code block
211
222
  var codeInfo = _this2.detectCodeContent(text);
@@ -0,0 +1,4 @@
1
+ /**
2
+ * URL validation utilities for detecting and validating URLs in pasted content
3
+ */
4
+ export { isPureUrl, isValidUrl } from "../../../utils/url";
@@ -0,0 +1,6 @@
1
+ /**
2
+ * URL validation utilities for detecting and validating URLs in pasted content
3
+ */
4
+
5
+ // Re-export shared URL utilities from common utils
6
+ export { isPureUrl, isValidUrl } from "../../../utils/url";
@@ -15,6 +15,8 @@ import { INSERT_CODEINLINE_COMMAND } from "../../../plugins/code";
15
15
  import { $isSelectionInCodeInline } from "../../../plugins/code/node/code";
16
16
  import { UPDATE_CODEBLOCK_LANG } from "../../../plugins/codeblock";
17
17
  import { $isRootTextContentEmpty } from "../../../plugins/common/utils";
18
+ import { INSERT_LINK_HIGHLIGHT_COMMAND } from "../../../plugins/link-highlight/command";
19
+ import { $isLinkHighlightNode } from "../../../plugins/link-highlight/node/link-highlight";
18
20
  import { $isLinkNode, TOGGLE_LINK_COMMAND, formatUrl } from "../../../plugins/link/node/LinkNode";
19
21
  import { extractUrlFromText, sanitizeUrl, validateUrl } from "../../../plugins/link/utils";
20
22
  import { INSERT_CHECK_LIST_COMMAND } from "../../../plugins/list";
@@ -124,7 +126,8 @@ export function useEditorState(editor) {
124
126
  var elementDOM = editor === null || editor === void 0 || (_editor$getLexicalEdi = editor.getLexicalEditor()) === null || _editor$getLexicalEdi === void 0 ? void 0 : _editor$getLexicalEdi.getElementByKey(elementKey);
125
127
  var node = getSelectedNode(selection);
126
128
  var parent = node.getParent();
127
- setIsLink($isLinkNode(parent) || $isLinkNode(node));
129
+ // Check for both Link and LinkHighlight nodes
130
+ setIsLink($isLinkNode(parent) || $isLinkNode(node) || $isLinkHighlightNode(parent) || $isLinkHighlightNode(node));
128
131
  var isCodeBlock = $isCodeNode(element) && $isCodeNode(focusElement) && elementKey === focusElement.getKey();
129
132
  setIsInCodeblok(isCodeBlock);
130
133
  setCodeblockLang(isCodeBlock ? element.getLanguage() : '');
@@ -258,7 +261,49 @@ export function useEditorState(editor) {
258
261
  var insertLink = useCallback(function () {
259
262
  var lexical = editor === null || editor === void 0 ? void 0 : editor.getLexicalEditor();
260
263
  if (!lexical) return;
264
+
265
+ // Detect which link type we're currently in
266
+ var inLinkNode = false;
267
+ var inLinkHighlightNode = false;
268
+ lexical.getEditorState().read(function () {
269
+ var selection = $getSelection();
270
+ if ($isRangeSelection(selection)) {
271
+ var node = getSelectedNode(selection);
272
+ var parent = node.getParent();
273
+ if ($isLinkNode(parent) || $isLinkNode(node)) {
274
+ inLinkNode = true;
275
+ }
276
+ if ($isLinkHighlightNode(parent) || $isLinkHighlightNode(node)) {
277
+ inLinkHighlightNode = true;
278
+ }
279
+ }
280
+ });
281
+
282
+ // If inside LinkHighlightNode, toggle it
283
+ if (inLinkHighlightNode) {
284
+ lexical.dispatchCommand(INSERT_LINK_HIGHLIGHT_COMMAND, undefined);
285
+ setIsLink(false);
286
+ return;
287
+ }
288
+
289
+ // If inside LinkNode, toggle it with standard Link plugin
290
+ if (inLinkNode) {
291
+ setIsLink(false);
292
+ lexical.dispatchCommand(TOGGLE_LINK_COMMAND, null);
293
+ return;
294
+ }
295
+
296
+ // Not in any link - try to insert new link
297
+ // Try LinkHighlight first, then fall back to standard Link if not handled
261
298
  if (!isLink) {
299
+ // First try INSERT_LINK_HIGHLIGHT_COMMAND
300
+ var linkHighlightHandled = lexical.dispatchCommand(INSERT_LINK_HIGHLIGHT_COMMAND, undefined);
301
+ if (linkHighlightHandled) {
302
+ setIsLink(true);
303
+ return;
304
+ }
305
+
306
+ // Fall back to standard Link plugin
262
307
  var nextUrl = sanitizeUrl('https://');
263
308
  var expandTo = null;
264
309
  lexical.getEditorState().read(function () {
@@ -283,7 +328,6 @@ export function useEditorState(editor) {
283
328
  }
284
329
  });
285
330
  setIsLink(true);
286
- console.log(nextUrl);
287
331
  lexical.update(function () {
288
332
  if (expandTo) {
289
333
  var selection = $getSelection();
@@ -294,12 +338,8 @@ export function useEditorState(editor) {
294
338
  selection.focus.set(anchorNode.getKey(), expandTo.index + expandTo.length, 'text');
295
339
  }
296
340
  }
297
- console.log(nextUrl);
298
341
  lexical.dispatchCommand(TOGGLE_LINK_COMMAND, validateUrl(nextUrl) ? nextUrl : sanitizeUrl('https://'));
299
342
  });
300
- } else {
301
- setIsLink(false);
302
- lexical.dispatchCommand(TOGGLE_LINK_COMMAND, null);
303
343
  }
304
344
  }, [editor, isLink]);
305
345
  var insertMath = useCallback(function () {
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Shared URL validation utilities
3
+ */
4
+ /**
5
+ * Validates if a string is a valid URL
6
+ * @param url - The URL to validate
7
+ * @returns true if the URL is valid, false otherwise
8
+ */
9
+ export declare function isValidUrl(url: string): boolean;
10
+ /**
11
+ * Checks if text is a pure URL (single URL without other text)
12
+ * @param text - The text to check
13
+ * @returns true if text is a pure URL, false otherwise
14
+ */
15
+ export declare function isPureUrl(text: string): boolean;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Shared URL validation utilities
3
+ */
4
+
5
+ // URL validation regex that matches common URL patterns
6
+ var URL_REGEX = new RegExp(/^(?:(?:https?|ftp):\/\/)?(?:www\.)?[\dA-Za-z][\w#%+./:=?@~-]*[\w#%+/=@~-]$/);
7
+
8
+ /**
9
+ * Validates if a string is a valid URL
10
+ * @param url - The URL to validate
11
+ * @returns true if the URL is valid, false otherwise
12
+ */
13
+ export function isValidUrl(url) {
14
+ if (!url || typeof url !== 'string') return false;
15
+ var trimmed = url.trim();
16
+
17
+ // Check if it matches URL pattern
18
+ if (URL_REGEX.test(trimmed)) {
19
+ return true;
20
+ }
21
+
22
+ // Check if it's a valid URL with protocol
23
+ try {
24
+ var urlObj = new URL(trimmed);
25
+ return !!urlObj.protocol && !!urlObj.hostname;
26
+ } catch (_unused) {
27
+ // Not a valid URL
28
+ }
29
+
30
+ // Check if it looks like a domain (e.g., "google.com")
31
+ if (/^[\da-z][\w#%+./:=?@~-]*\.[\w#%+/:=?@~-]+$/i.test(trimmed)) {
32
+ return true;
33
+ }
34
+ return false;
35
+ }
36
+
37
+ /**
38
+ * Checks if text is a pure URL (single URL without other text)
39
+ * @param text - The text to check
40
+ * @returns true if text is a pure URL, false otherwise
41
+ */
42
+ export function isPureUrl(text) {
43
+ if (!text || typeof text !== 'string') return false;
44
+ var trimmed = text.trim();
45
+
46
+ // Check if it's a single line
47
+ if (trimmed.includes('\n')) return false;
48
+
49
+ // Check if it's a valid URL
50
+ return isValidUrl(trimmed);
51
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/editor",
3
- "version": "1.19.1",
3
+ "version": "1.20.1",
4
4
  "description": "A powerful and extensible rich text editor built on Meta's Lexical framework, providing a modern editing experience with React integration.",
5
5
  "keywords": [
6
6
  "lobehub",