@mx-space/cli 0.1.0
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.
- package/LICENSE +33 -0
- package/README.md +375 -0
- package/ROADMAP.md +42 -0
- package/bin/mxs.cjs +40 -0
- package/dist/Lexical.dev-DpU35QTu.mjs +10992 -0
- package/dist/Lexical.dev-DpU35QTu.mjs.map +1 -0
- package/dist/Lexical.node-gYNDt_p9.mjs +247 -0
- package/dist/Lexical.node-gYNDt_p9.mjs.map +1 -0
- package/dist/Lexical.prod-t4c3bHV3.mjs +5579 -0
- package/dist/Lexical.prod-t4c3bHV3.mjs.map +1 -0
- package/dist/LexicalClipboard.dev-CdeeWCIq.mjs +496 -0
- package/dist/LexicalClipboard.dev-CdeeWCIq.mjs.map +1 -0
- package/dist/LexicalClipboard.node-0e9GBL1g.mjs +26 -0
- package/dist/LexicalClipboard.node-0e9GBL1g.mjs.map +1 -0
- package/dist/LexicalClipboard.prod-CsCWYmV7.mjs +293 -0
- package/dist/LexicalClipboard.prod-CsCWYmV7.mjs.map +1 -0
- package/dist/LexicalCodeCore.dev-haI2cqH8.mjs +539 -0
- package/dist/LexicalCodeCore.dev-haI2cqH8.mjs.map +1 -0
- package/dist/LexicalCodeCore.prod-D_k7zuE5.mjs +383 -0
- package/dist/LexicalCodeCore.prod-D_k7zuE5.mjs.map +1 -0
- package/dist/LexicalDragon.dev-CAggdFh_.mjs +90 -0
- package/dist/LexicalDragon.dev-CAggdFh_.mjs.map +1 -0
- package/dist/LexicalDragon.node-QMR_wqJg.mjs +15 -0
- package/dist/LexicalDragon.node-QMR_wqJg.mjs.map +1 -0
- package/dist/LexicalDragon.prod-OssQhwS-.mjs +61 -0
- package/dist/LexicalDragon.prod-OssQhwS-.mjs.map +1 -0
- package/dist/LexicalExtension.dev-CtDZiMCU.mjs +1665 -0
- package/dist/LexicalExtension.dev-CtDZiMCU.mjs.map +1 -0
- package/dist/LexicalExtension.node-BM0gh8zu.mjs +51 -0
- package/dist/LexicalExtension.node-BM0gh8zu.mjs.map +1 -0
- package/dist/LexicalExtension.prod-Ug6emhEO.mjs +971 -0
- package/dist/LexicalExtension.prod-Ug6emhEO.mjs.map +1 -0
- package/dist/LexicalHeadless.dev-D4yQpl_s.mjs +45 -0
- package/dist/LexicalHeadless.dev-D4yQpl_s.mjs.map +1 -0
- package/dist/LexicalHeadless.prod-jeN65B3k.mjs +31 -0
- package/dist/LexicalHeadless.prod-jeN65B3k.mjs.map +1 -0
- package/dist/LexicalHtml.dev-BdU1_ITQ.mjs +686 -0
- package/dist/LexicalHtml.dev-BdU1_ITQ.mjs.map +1 -0
- package/dist/LexicalHtml.node-DLtAh00E.mjs +26 -0
- package/dist/LexicalHtml.node-DLtAh00E.mjs.map +1 -0
- package/dist/LexicalHtml.prod-DdLE1HHT.mjs +432 -0
- package/dist/LexicalHtml.prod-DdLE1HHT.mjs.map +1 -0
- package/dist/LexicalLink.dev-DK87Biuy.mjs +1084 -0
- package/dist/LexicalLink.dev-DK87Biuy.mjs.map +1 -0
- package/dist/LexicalLink.prod-fssuv6Sf.mjs +717 -0
- package/dist/LexicalLink.prod-fssuv6Sf.mjs.map +1 -0
- package/dist/LexicalList.dev-BJ4dr_rk.mjs +1352 -0
- package/dist/LexicalList.dev-BJ4dr_rk.mjs.map +1 -0
- package/dist/LexicalList.prod-jWBbaWfi.mjs +841 -0
- package/dist/LexicalList.prod-jWBbaWfi.mjs.map +1 -0
- package/dist/LexicalMarkdown.dev-C8e-eh6y.mjs +1207 -0
- package/dist/LexicalMarkdown.dev-C8e-eh6y.mjs.map +1 -0
- package/dist/LexicalMarkdown.prod-k73tZVb2.mjs +756 -0
- package/dist/LexicalMarkdown.prod-k73tZVb2.mjs.map +1 -0
- package/dist/LexicalRichText.dev-CrSqq3K-.mjs +645 -0
- package/dist/LexicalRichText.dev-CrSqq3K-.mjs.map +1 -0
- package/dist/LexicalRichText.node-B7k7wlQz.mjs +106 -0
- package/dist/LexicalRichText.node-B7k7wlQz.mjs.map +1 -0
- package/dist/LexicalRichText.prod-CqvyxQ9m.mjs +429 -0
- package/dist/LexicalRichText.prod-CqvyxQ9m.mjs.map +1 -0
- package/dist/LexicalSelection.dev-sNOHKHe_.mjs +725 -0
- package/dist/LexicalSelection.dev-sNOHKHe_.mjs.map +1 -0
- package/dist/LexicalSelection.node-Di7GwBCZ.mjs +38 -0
- package/dist/LexicalSelection.node-Di7GwBCZ.mjs.map +1 -0
- package/dist/LexicalSelection.prod-SWllPJop.mjs +388 -0
- package/dist/LexicalSelection.prod-SWllPJop.mjs.map +1 -0
- package/dist/LexicalTable.dev-CQkLHXJx.mjs +3262 -0
- package/dist/LexicalTable.dev-CQkLHXJx.mjs.map +1 -0
- package/dist/LexicalTable.prod-BtqCwKSi.mjs +2218 -0
- package/dist/LexicalTable.prod-BtqCwKSi.mjs.map +1 -0
- package/dist/LexicalUtils.dev-DiUpsXE8.mjs +850 -0
- package/dist/LexicalUtils.dev-DiUpsXE8.mjs.map +1 -0
- package/dist/LexicalUtils.node-CmK462d1.mjs +66 -0
- package/dist/LexicalUtils.node-CmK462d1.mjs.map +1 -0
- package/dist/LexicalUtils.prod-bWwV4GAF.mjs +377 -0
- package/dist/LexicalUtils.prod-bWwV4GAF.mjs.map +1 -0
- package/dist/auth-DIFZzoMm.mjs +211 -0
- package/dist/auth-DIFZzoMm.mjs.map +1 -0
- package/dist/bin/mxs.d.mts +927 -0
- package/dist/bin/mxs.mjs +3249 -0
- package/dist/bin/mxs.mjs.map +1 -0
- package/dist/chunk-hT5z_Zn9.mjs +35 -0
- package/dist/config-store-DIu7x0ao.mjs +121 -0
- package/dist/config-store-DIu7x0ao.mjs.map +1 -0
- package/dist/create-B2AOQL1e.mjs +24 -0
- package/dist/create-B2AOQL1e.mjs.map +1 -0
- package/dist/create-CwjGcv2n.mjs +23 -0
- package/dist/create-CwjGcv2n.mjs.map +1 -0
- package/dist/create-DT1l2hue.mjs +23 -0
- package/dist/create-DT1l2hue.mjs.map +1 -0
- package/dist/create-DjlRHPXy.mjs +23 -0
- package/dist/create-DjlRHPXy.mjs.map +1 -0
- package/dist/create-Dz6rJJSS.mjs +19 -0
- package/dist/create-Dz6rJJSS.mjs.map +1 -0
- package/dist/delete-B4dGrrus.mjs +22 -0
- package/dist/delete-B4dGrrus.mjs.map +1 -0
- package/dist/delete-BH6ZngTz.mjs +22 -0
- package/dist/delete-BH6ZngTz.mjs.map +1 -0
- package/dist/delete-BrY5zJ6S.mjs +22 -0
- package/dist/delete-BrY5zJ6S.mjs.map +1 -0
- package/dist/delete-CaG2d_xt.mjs +31 -0
- package/dist/delete-CaG2d_xt.mjs.map +1 -0
- package/dist/delete-YzRliRn8.mjs +31 -0
- package/dist/delete-YzRliRn8.mjs.map +1 -0
- package/dist/document-output-BVB8emLt.mjs +145 -0
- package/dist/document-output-BVB8emLt.mjs.map +1 -0
- package/dist/edit-Bb0Ze4w6.mjs +79 -0
- package/dist/edit-Bb0Ze4w6.mjs.map +1 -0
- package/dist/edit-BeFmC3XD.mjs +80 -0
- package/dist/edit-BeFmC3XD.mjs.map +1 -0
- package/dist/edit-DLDw_ZWX.mjs +86 -0
- package/dist/edit-DLDw_ZWX.mjs.map +1 -0
- package/dist/edit-z55ofr1S.mjs +40 -0
- package/dist/edit-z55ofr1S.mjs.map +1 -0
- package/dist/editor-xrF-j3UE.mjs +42 -0
- package/dist/editor-xrF-j3UE.mjs.map +1 -0
- package/dist/envelope-overlays-DPAlMNW1.mjs +26 -0
- package/dist/envelope-overlays-DPAlMNW1.mjs.map +1 -0
- package/dist/get-2qjq0wgG.mjs +28 -0
- package/dist/get-2qjq0wgG.mjs.map +1 -0
- package/dist/get-BGAk1Zmv.mjs +14 -0
- package/dist/get-BGAk1Zmv.mjs.map +1 -0
- package/dist/get-CRR13lgj.mjs +10 -0
- package/dist/get-CRR13lgj.mjs.map +1 -0
- package/dist/get-ClkpDBTL.mjs +13 -0
- package/dist/get-ClkpDBTL.mjs.map +1 -0
- package/dist/get-DJfUJ3sP.mjs +10 -0
- package/dist/get-DJfUJ3sP.mjs.map +1 -0
- package/dist/get-DiqaQvoS.mjs +14 -0
- package/dist/get-DiqaQvoS.mjs.map +1 -0
- package/dist/index.d.mts +338 -0
- package/dist/index.mjs +10 -0
- package/dist/list-BYzhflDg.mjs +14 -0
- package/dist/list-BYzhflDg.mjs.map +1 -0
- package/dist/list-DY-tPNG-.mjs +10 -0
- package/dist/list-DY-tPNG-.mjs.map +1 -0
- package/dist/list-DlQSkbqx.mjs +10 -0
- package/dist/list-DlQSkbqx.mjs.map +1 -0
- package/dist/list-DsrA5wIz.mjs +15 -0
- package/dist/list-DsrA5wIz.mjs.map +1 -0
- package/dist/list-Fp4nKfQb.mjs +10 -0
- package/dist/list-Fp4nKfQb.mjs.map +1 -0
- package/dist/list-s_tloiEX.mjs +10 -0
- package/dist/list-s_tloiEX.mjs.map +1 -0
- package/dist/litexml-codec-CDoRloiR.mjs +13451 -0
- package/dist/litexml-codec-CDoRloiR.mjs.map +1 -0
- package/dist/login-CLOJ-fTJ.mjs +579 -0
- package/dist/login-CLOJ-fTJ.mjs.map +1 -0
- package/dist/logout-v8ihxsim.mjs +11 -0
- package/dist/logout-v8ihxsim.mjs.map +1 -0
- package/dist/onboarding-640IldLz.mjs +888 -0
- package/dist/onboarding-640IldLz.mjs.map +1 -0
- package/dist/output-BM7aBFwt.mjs +120 -0
- package/dist/output-BM7aBFwt.mjs.map +1 -0
- package/dist/output-CTnO1lYt.d.mts +16 -0
- package/dist/payload-CN11pUto.mjs +547 -0
- package/dist/payload-CN11pUto.mjs.map +1 -0
- package/dist/publish-DkTYB-eK.mjs +23 -0
- package/dist/publish-DkTYB-eK.mjs.map +1 -0
- package/dist/publish-pRBfTJfe.mjs +23 -0
- package/dist/publish-pRBfTJfe.mjs.map +1 -0
- package/dist/resolve-BogJOR7R.mjs +119 -0
- package/dist/resolve-BogJOR7R.mjs.map +1 -0
- package/dist/resolve-Bp1AfBmM.mjs +26 -0
- package/dist/resolve-Bp1AfBmM.mjs.map +1 -0
- package/dist/resolve-Bw5Po9yK.mjs +16 -0
- package/dist/resolve-Bw5Po9yK.mjs.map +1 -0
- package/dist/resolve-CyjMlaoJ.mjs +23 -0
- package/dist/resolve-CyjMlaoJ.mjs.map +1 -0
- package/dist/resolve-helpers-CahPoJ0Y.mjs +30 -0
- package/dist/resolve-helpers-CahPoJ0Y.mjs.map +1 -0
- package/dist/set-BEGQz2si.mjs +43 -0
- package/dist/set-BEGQz2si.mjs.map +1 -0
- package/dist/shared-D0HVBDHI.mjs +39 -0
- package/dist/shared-D0HVBDHI.mjs.map +1 -0
- package/dist/status-LXrN9fI-.mjs +22 -0
- package/dist/status-LXrN9fI-.mjs.map +1 -0
- package/dist/update-B9ynFCdZ.mjs +30 -0
- package/dist/update-B9ynFCdZ.mjs.map +1 -0
- package/dist/update-BMZQPOvR.mjs +28 -0
- package/dist/update-BMZQPOvR.mjs.map +1 -0
- package/dist/update-BXz0l7Lz.mjs +36 -0
- package/dist/update-BXz0l7Lz.mjs.map +1 -0
- package/dist/update-Dx6AF8tf.mjs +30 -0
- package/dist/update-Dx6AF8tf.mjs.map +1 -0
- package/dist/update-q66TLS38.mjs +37 -0
- package/dist/update-q66TLS38.mjs.map +1 -0
- package/dist/whoami-CTkctOEd.mjs +22 -0
- package/dist/whoami-CTkctOEd.mjs.map +1 -0
- package/package.json +77 -0
|
@@ -0,0 +1,1084 @@
|
|
|
1
|
+
import { r as effect, s as namedSignals } from "./LexicalExtension.node-BM0gh8zu.mjs";
|
|
2
|
+
import { $n as safeCast, At as COMMAND_PRIORITY_LOW, D as $getChildCaret, Fn as getNearestEditorFromDOMNode, Hn as isDOMNode, Ht as ElementNode, Mn as defineExtension, N as $getNearestNodeFromDOMNode, On as createCommand, Ot as COMMAND_PRIORITY_EDITOR, Y as $isElementNode, _ as $createTextNode, _t as $setPointFromCaret, c as $copyNode, ct as $isTextNode, dt as $normalizeCaret, et as $isLineBreakNode, fn as PASTE_COMMAND, ft as $normalizeSelection__EXPERIMENTAL, ht as $rewindSiblingCaret, n as $applyNodeReplacement, ot as $isSiblingCaret, r as $caretFromPoint, rr as shallowMergeConfig, rt as $isRangeSelection, tt as $isNodeSelection, vt as $setSelection, wn as TextNode, z as $getSelection } from "./Lexical.node-gYNDt_p9.mjs";
|
|
3
|
+
import { d as addClassNamesToElement, g as objectKlassEquals, h as mergeRegister, l as $insertNodeToNearestRootAtCaret, p as isHTMLAnchorElement, r as $findMatchingParent } from "./LexicalUtils.node-CmK462d1.mjs";
|
|
4
|
+
//#region ../../node_modules/.pnpm/@lexical+link@0.44.0/node_modules/@lexical/link/LexicalLink.dev.mjs
|
|
5
|
+
/**
|
|
6
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
7
|
+
*
|
|
8
|
+
* This source code is licensed under the MIT license found in the
|
|
9
|
+
* LICENSE file in the root directory of this source tree.
|
|
10
|
+
*
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
14
|
+
*
|
|
15
|
+
* This source code is licensed under the MIT license found in the
|
|
16
|
+
* LICENSE file in the root directory of this source tree.
|
|
17
|
+
*
|
|
18
|
+
*/
|
|
19
|
+
function formatDevErrorMessage(message) {
|
|
20
|
+
throw new Error(message);
|
|
21
|
+
}
|
|
22
|
+
const SUPPORTED_URL_PROTOCOLS = new Set([
|
|
23
|
+
"http:",
|
|
24
|
+
"https:",
|
|
25
|
+
"mailto:",
|
|
26
|
+
"sms:",
|
|
27
|
+
"tel:"
|
|
28
|
+
]);
|
|
29
|
+
/** @noInheritDoc */
|
|
30
|
+
var LinkNode = class LinkNode extends ElementNode {
|
|
31
|
+
/** @internal */
|
|
32
|
+
__url;
|
|
33
|
+
/** @internal */
|
|
34
|
+
__target;
|
|
35
|
+
/** @internal */
|
|
36
|
+
__rel;
|
|
37
|
+
/** @internal */
|
|
38
|
+
__title;
|
|
39
|
+
static getType() {
|
|
40
|
+
return "link";
|
|
41
|
+
}
|
|
42
|
+
static clone(node) {
|
|
43
|
+
return new LinkNode(node.__url, {
|
|
44
|
+
rel: node.__rel,
|
|
45
|
+
target: node.__target,
|
|
46
|
+
title: node.__title
|
|
47
|
+
}, node.__key);
|
|
48
|
+
}
|
|
49
|
+
constructor(url = "", attributes = {}, key) {
|
|
50
|
+
super(key);
|
|
51
|
+
const { target = null, rel = null, title = null } = attributes;
|
|
52
|
+
this.__url = url;
|
|
53
|
+
this.__target = target;
|
|
54
|
+
this.__rel = rel;
|
|
55
|
+
this.__title = title;
|
|
56
|
+
}
|
|
57
|
+
afterCloneFrom(prevNode) {
|
|
58
|
+
super.afterCloneFrom(prevNode);
|
|
59
|
+
this.__url = prevNode.__url;
|
|
60
|
+
this.__rel = prevNode.__rel;
|
|
61
|
+
this.__target = prevNode.__target;
|
|
62
|
+
this.__title = prevNode.__title;
|
|
63
|
+
}
|
|
64
|
+
createDOM(config) {
|
|
65
|
+
const element = document.createElement("a");
|
|
66
|
+
this.updateLinkDOM(null, element, config);
|
|
67
|
+
addClassNamesToElement(element, config.theme.link);
|
|
68
|
+
return element;
|
|
69
|
+
}
|
|
70
|
+
updateLinkDOM(prevNode, anchor, config) {
|
|
71
|
+
if (isHTMLAnchorElement(anchor)) {
|
|
72
|
+
if (!prevNode || prevNode.__url !== this.__url) anchor.href = this.sanitizeUrl(this.__url);
|
|
73
|
+
for (const attr of [
|
|
74
|
+
"target",
|
|
75
|
+
"rel",
|
|
76
|
+
"title"
|
|
77
|
+
]) {
|
|
78
|
+
const key = `__${attr}`;
|
|
79
|
+
const value = this[key];
|
|
80
|
+
if (!prevNode || prevNode[key] !== value) if (value) anchor[attr] = value;
|
|
81
|
+
else anchor.removeAttribute(attr);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
updateDOM(prevNode, anchor, config) {
|
|
86
|
+
this.updateLinkDOM(prevNode, anchor, config);
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
static importDOM() {
|
|
90
|
+
return { a: (node) => ({
|
|
91
|
+
conversion: $convertAnchorElement,
|
|
92
|
+
priority: 1
|
|
93
|
+
}) };
|
|
94
|
+
}
|
|
95
|
+
static importJSON(serializedNode) {
|
|
96
|
+
return $createLinkNode().updateFromJSON(serializedNode);
|
|
97
|
+
}
|
|
98
|
+
updateFromJSON(serializedNode) {
|
|
99
|
+
return super.updateFromJSON(serializedNode).setURL(serializedNode.url).setRel(serializedNode.rel || null).setTarget(serializedNode.target || null).setTitle(serializedNode.title || null);
|
|
100
|
+
}
|
|
101
|
+
sanitizeUrl(url) {
|
|
102
|
+
url = formatUrl(url);
|
|
103
|
+
try {
|
|
104
|
+
const parsedUrl = new URL(formatUrl(url));
|
|
105
|
+
if (!SUPPORTED_URL_PROTOCOLS.has(parsedUrl.protocol)) return "about:blank";
|
|
106
|
+
} catch (_unused) {
|
|
107
|
+
return url;
|
|
108
|
+
}
|
|
109
|
+
return url;
|
|
110
|
+
}
|
|
111
|
+
exportJSON() {
|
|
112
|
+
return {
|
|
113
|
+
...super.exportJSON(),
|
|
114
|
+
rel: this.getRel(),
|
|
115
|
+
target: this.getTarget(),
|
|
116
|
+
title: this.getTitle(),
|
|
117
|
+
url: this.getURL()
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
getURL() {
|
|
121
|
+
return this.getLatest().__url;
|
|
122
|
+
}
|
|
123
|
+
setURL(url) {
|
|
124
|
+
const writable = this.getWritable();
|
|
125
|
+
writable.__url = url;
|
|
126
|
+
return writable;
|
|
127
|
+
}
|
|
128
|
+
getTarget() {
|
|
129
|
+
return this.getLatest().__target;
|
|
130
|
+
}
|
|
131
|
+
setTarget(target) {
|
|
132
|
+
const writable = this.getWritable();
|
|
133
|
+
writable.__target = target;
|
|
134
|
+
return writable;
|
|
135
|
+
}
|
|
136
|
+
getRel() {
|
|
137
|
+
return this.getLatest().__rel;
|
|
138
|
+
}
|
|
139
|
+
setRel(rel) {
|
|
140
|
+
const writable = this.getWritable();
|
|
141
|
+
writable.__rel = rel;
|
|
142
|
+
return writable;
|
|
143
|
+
}
|
|
144
|
+
getTitle() {
|
|
145
|
+
return this.getLatest().__title;
|
|
146
|
+
}
|
|
147
|
+
setTitle(title) {
|
|
148
|
+
const writable = this.getWritable();
|
|
149
|
+
writable.__title = title;
|
|
150
|
+
return writable;
|
|
151
|
+
}
|
|
152
|
+
insertNewAfter(_, restoreSelection = true) {
|
|
153
|
+
const linkNode = $copyNode(this);
|
|
154
|
+
this.insertAfter(linkNode, restoreSelection);
|
|
155
|
+
return linkNode;
|
|
156
|
+
}
|
|
157
|
+
canInsertTextBefore() {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
canInsertTextAfter() {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
canBeEmpty() {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
isInline() {
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
extractWithChild(child, selection, destination) {
|
|
170
|
+
if (!$isRangeSelection(selection)) return false;
|
|
171
|
+
const anchorNode = selection.anchor.getNode();
|
|
172
|
+
const focusNode = selection.focus.getNode();
|
|
173
|
+
return this.isParentOf(anchorNode) && this.isParentOf(focusNode) && selection.getTextContent().length > 0;
|
|
174
|
+
}
|
|
175
|
+
isEmailURI() {
|
|
176
|
+
return this.__url.startsWith("mailto:");
|
|
177
|
+
}
|
|
178
|
+
isWebSiteURI() {
|
|
179
|
+
return this.__url.startsWith("https://") || this.__url.startsWith("http://");
|
|
180
|
+
}
|
|
181
|
+
shouldMergeAdjacentLink(otherLink) {
|
|
182
|
+
return this.getType() === otherLink.getType() && this.__url === otherLink.__url && this.__target === otherLink.__target && this.__rel === otherLink.__rel && this.__title === otherLink.__title;
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
function $saveCaretPair(point) {
|
|
186
|
+
const next = $caretFromPoint(point, "next");
|
|
187
|
+
return [next, next.getFlipped()];
|
|
188
|
+
}
|
|
189
|
+
function $restoreCaretPair(point, pair) {
|
|
190
|
+
for (const caret of pair) if (caret.origin.isAttached()) {
|
|
191
|
+
$setPointFromCaret(point, $normalizeCaret(caret));
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Extracts block-level children from a LinkNode, splitting
|
|
197
|
+
* ancestor nodes as needed to maintain a valid document structure.
|
|
198
|
+
* @param link - The LinkNode to normalize
|
|
199
|
+
*/
|
|
200
|
+
function $linkNodeTransform(link) {
|
|
201
|
+
const selection = $getSelection();
|
|
202
|
+
let anchorPair = null;
|
|
203
|
+
let focusPair = null;
|
|
204
|
+
if ($isRangeSelection(selection)) {
|
|
205
|
+
anchorPair = $saveCaretPair(selection.anchor);
|
|
206
|
+
focusPair = $saveCaretPair(selection.focus);
|
|
207
|
+
}
|
|
208
|
+
function $restoreSelection() {
|
|
209
|
+
if ($isRangeSelection(selection)) {
|
|
210
|
+
$restoreCaretPair(selection.anchor, anchorPair);
|
|
211
|
+
$restoreCaretPair(selection.focus, focusPair);
|
|
212
|
+
$normalizeSelection__EXPERIMENTAL(selection);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
let transformed = false;
|
|
216
|
+
for (const caret of $getChildCaret(link, "next")) {
|
|
217
|
+
const node = caret.origin;
|
|
218
|
+
if ($isElementNode(node) && !node.isInline()) {
|
|
219
|
+
const blockChildren = node.getChildren();
|
|
220
|
+
if (blockChildren.length > 0) {
|
|
221
|
+
const innerLink = $copyNode(link);
|
|
222
|
+
innerLink.append(...blockChildren);
|
|
223
|
+
node.append(innerLink);
|
|
224
|
+
transformed = true;
|
|
225
|
+
}
|
|
226
|
+
$insertNodeToNearestRootAtCaret(node, $rewindSiblingCaret(caret), { $shouldSplit: () => false });
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
function $fixMergeBoundaryCaret(pair, absorbingLink, mergingLink) {
|
|
230
|
+
const [next, prev] = pair;
|
|
231
|
+
const $isAffected = (caret) => $isSiblingCaret(caret) && caret.origin.is(absorbingLink);
|
|
232
|
+
if (!$isAffected(next) && !$isAffected(prev)) return pair;
|
|
233
|
+
const fixed = $normalizeCaret($getChildCaret(mergingLink, "next"));
|
|
234
|
+
return [fixed, fixed.getFlipped()];
|
|
235
|
+
}
|
|
236
|
+
if (link.isAttached()) {
|
|
237
|
+
const prevSibling = link.getPreviousSibling();
|
|
238
|
+
if ($isLinkNode(prevSibling) && prevSibling.shouldMergeAdjacentLink(link)) {
|
|
239
|
+
if (anchorPair) anchorPair = $fixMergeBoundaryCaret(anchorPair, prevSibling, link);
|
|
240
|
+
if (focusPair) focusPair = $fixMergeBoundaryCaret(focusPair, prevSibling, link);
|
|
241
|
+
prevSibling.append(...link.getChildren());
|
|
242
|
+
link.remove();
|
|
243
|
+
$restoreSelection();
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
const nextSibling = link.getNextSibling();
|
|
247
|
+
if ($isLinkNode(nextSibling) && link.shouldMergeAdjacentLink(nextSibling)) {
|
|
248
|
+
if (anchorPair) anchorPair = $fixMergeBoundaryCaret(anchorPair, link, nextSibling);
|
|
249
|
+
if (focusPair) focusPair = $fixMergeBoundaryCaret(focusPair, link, nextSibling);
|
|
250
|
+
link.append(...nextSibling.getChildren());
|
|
251
|
+
nextSibling.remove();
|
|
252
|
+
transformed = true;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
if (!transformed) return;
|
|
256
|
+
if (!link.canBeEmpty() && link.isEmpty()) {
|
|
257
|
+
const parent = link.getParent();
|
|
258
|
+
link.remove();
|
|
259
|
+
if (parent && parent.isEmpty()) parent.remove();
|
|
260
|
+
}
|
|
261
|
+
$restoreSelection();
|
|
262
|
+
}
|
|
263
|
+
function $convertAnchorElement(domNode) {
|
|
264
|
+
let node = null;
|
|
265
|
+
if (isHTMLAnchorElement(domNode)) {
|
|
266
|
+
const content = domNode.textContent;
|
|
267
|
+
if (content !== null && content !== "" || domNode.children.length > 0) node = $createLinkNode(domNode.getAttribute("href") || "", {
|
|
268
|
+
rel: domNode.getAttribute("rel"),
|
|
269
|
+
target: domNode.getAttribute("target"),
|
|
270
|
+
title: domNode.getAttribute("title")
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
return { node };
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Takes a URL and creates a LinkNode.
|
|
277
|
+
* @param url - The URL the LinkNode should direct to.
|
|
278
|
+
* @param attributes - Optional HTML a tag attributes \\{ target, rel, title \\}
|
|
279
|
+
* @returns The LinkNode.
|
|
280
|
+
*/
|
|
281
|
+
function $createLinkNode(url = "", attributes) {
|
|
282
|
+
return $applyNodeReplacement(new LinkNode(url, attributes));
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Determines if node is a LinkNode.
|
|
286
|
+
* @param node - The node to be checked.
|
|
287
|
+
* @returns true if node is a LinkNode, false otherwise.
|
|
288
|
+
*/
|
|
289
|
+
function $isLinkNode(node) {
|
|
290
|
+
return node instanceof LinkNode;
|
|
291
|
+
}
|
|
292
|
+
var AutoLinkNode = class AutoLinkNode extends LinkNode {
|
|
293
|
+
/** @internal */
|
|
294
|
+
/** Indicates whether the autolink was ever unlinked. **/
|
|
295
|
+
__isUnlinked;
|
|
296
|
+
constructor(url = "", attributes = {}, key) {
|
|
297
|
+
super(url, attributes, key);
|
|
298
|
+
this.__isUnlinked = attributes.isUnlinked !== void 0 && attributes.isUnlinked !== null ? attributes.isUnlinked : false;
|
|
299
|
+
}
|
|
300
|
+
afterCloneFrom(prevNode) {
|
|
301
|
+
super.afterCloneFrom(prevNode);
|
|
302
|
+
this.__isUnlinked = prevNode.__isUnlinked;
|
|
303
|
+
}
|
|
304
|
+
static getType() {
|
|
305
|
+
return "autolink";
|
|
306
|
+
}
|
|
307
|
+
static clone(node) {
|
|
308
|
+
return new AutoLinkNode(node.__url, {
|
|
309
|
+
isUnlinked: node.__isUnlinked,
|
|
310
|
+
rel: node.__rel,
|
|
311
|
+
target: node.__target,
|
|
312
|
+
title: node.__title
|
|
313
|
+
}, node.__key);
|
|
314
|
+
}
|
|
315
|
+
shouldMergeAdjacentLink(_otherLink) {
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
getIsUnlinked() {
|
|
319
|
+
return this.__isUnlinked;
|
|
320
|
+
}
|
|
321
|
+
setIsUnlinked(value) {
|
|
322
|
+
const self = this.getWritable();
|
|
323
|
+
self.__isUnlinked = value;
|
|
324
|
+
return self;
|
|
325
|
+
}
|
|
326
|
+
createDOM(config) {
|
|
327
|
+
if (this.__isUnlinked) return document.createElement("span");
|
|
328
|
+
else return super.createDOM(config);
|
|
329
|
+
}
|
|
330
|
+
updateDOM(prevNode, anchor, config) {
|
|
331
|
+
return super.updateDOM(prevNode, anchor, config) || prevNode.__isUnlinked !== this.__isUnlinked;
|
|
332
|
+
}
|
|
333
|
+
static importJSON(serializedNode) {
|
|
334
|
+
return $createAutoLinkNode().updateFromJSON(serializedNode);
|
|
335
|
+
}
|
|
336
|
+
updateFromJSON(serializedNode) {
|
|
337
|
+
return super.updateFromJSON(serializedNode).setIsUnlinked(serializedNode.isUnlinked || false);
|
|
338
|
+
}
|
|
339
|
+
static importDOM() {
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
exportJSON() {
|
|
343
|
+
return {
|
|
344
|
+
...super.exportJSON(),
|
|
345
|
+
isUnlinked: this.__isUnlinked
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
insertNewAfter(_, restoreSelection = true) {
|
|
349
|
+
const linkNode = $createAutoLinkNode(this.__url, {
|
|
350
|
+
isUnlinked: this.__isUnlinked,
|
|
351
|
+
rel: this.__rel,
|
|
352
|
+
target: this.__target,
|
|
353
|
+
title: this.__title
|
|
354
|
+
});
|
|
355
|
+
this.insertAfter(linkNode, restoreSelection);
|
|
356
|
+
return linkNode;
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
/**
|
|
360
|
+
* Takes a URL and creates an AutoLinkNode. AutoLinkNodes are generally automatically generated
|
|
361
|
+
* during typing, which is especially useful when a button to generate a LinkNode is not practical.
|
|
362
|
+
* @param url - The URL the LinkNode should direct to.
|
|
363
|
+
* @param attributes - Optional HTML a tag attributes. \\{ target, rel, title \\}
|
|
364
|
+
* @returns The LinkNode.
|
|
365
|
+
*/
|
|
366
|
+
function $createAutoLinkNode(url = "", attributes) {
|
|
367
|
+
return $applyNodeReplacement(new AutoLinkNode(url, attributes));
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Determines if node is an AutoLinkNode.
|
|
371
|
+
* @param node - The node to be checked.
|
|
372
|
+
* @returns true if node is an AutoLinkNode, false otherwise.
|
|
373
|
+
*/
|
|
374
|
+
function $isAutoLinkNode(node) {
|
|
375
|
+
return node instanceof AutoLinkNode;
|
|
376
|
+
}
|
|
377
|
+
const TOGGLE_LINK_COMMAND = createCommand("TOGGLE_LINK_COMMAND");
|
|
378
|
+
function $getPointNode(point, offset) {
|
|
379
|
+
if (point.type === "element") {
|
|
380
|
+
const node = point.getNode();
|
|
381
|
+
if (!$isElementNode(node)) formatDevErrorMessage(`$getPointNode: element point is not an ElementNode`);
|
|
382
|
+
return node.getChildren()[point.offset + offset] || null;
|
|
383
|
+
}
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Preserve the logical start/end of a RangeSelection in situations where
|
|
388
|
+
* the point is an element that may be reparented in the callback.
|
|
389
|
+
*
|
|
390
|
+
* @param $fn The function to run
|
|
391
|
+
* @returns The result of the callback
|
|
392
|
+
*/
|
|
393
|
+
function $withSelectedNodes($fn) {
|
|
394
|
+
const initialSelection = $getSelection();
|
|
395
|
+
if (!$isRangeSelection(initialSelection)) return $fn();
|
|
396
|
+
const normalized = $normalizeSelection__EXPERIMENTAL(initialSelection);
|
|
397
|
+
const isBackwards = normalized.isBackward();
|
|
398
|
+
const anchorNode = $getPointNode(normalized.anchor, isBackwards ? -1 : 0);
|
|
399
|
+
const focusNode = $getPointNode(normalized.focus, isBackwards ? 0 : -1);
|
|
400
|
+
const rval = $fn();
|
|
401
|
+
if (anchorNode || focusNode) {
|
|
402
|
+
const updatedSelection = $getSelection();
|
|
403
|
+
if ($isRangeSelection(updatedSelection)) {
|
|
404
|
+
const finalSelection = updatedSelection.clone();
|
|
405
|
+
if (anchorNode) {
|
|
406
|
+
const anchorParent = anchorNode.getParent();
|
|
407
|
+
if (anchorParent) finalSelection.anchor.set(anchorParent.getKey(), anchorNode.getIndexWithinParent() + (isBackwards ? 1 : 0), "element");
|
|
408
|
+
}
|
|
409
|
+
if (focusNode) {
|
|
410
|
+
const focusParent = focusNode.getParent();
|
|
411
|
+
if (focusParent) finalSelection.focus.set(focusParent.getKey(), focusNode.getIndexWithinParent() + (isBackwards ? 0 : 1), "element");
|
|
412
|
+
}
|
|
413
|
+
$setSelection($normalizeSelection__EXPERIMENTAL(finalSelection));
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
return rval;
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Splits a LinkNode by removing selected children from it.
|
|
420
|
+
* Handles three cases: selection at start, end, or middle of the link.
|
|
421
|
+
* @param parentLink - The LinkNode to split
|
|
422
|
+
* @param extractedNodes - The nodes that were extracted from the selection
|
|
423
|
+
*/
|
|
424
|
+
function $splitLinkAtSelection(parentLink, extractedNodes) {
|
|
425
|
+
const extractedKeys = new Set(extractedNodes.filter((n) => parentLink.isParentOf(n)).map((n) => n.getKey()));
|
|
426
|
+
const allChildren = parentLink.getChildren();
|
|
427
|
+
const isExtractedChild = (child) => extractedKeys.has(child.getKey()) || $isElementNode(child) && extractedNodes.some((n) => parentLink.isParentOf(n) && child.isParentOf(n));
|
|
428
|
+
const extractedChildren = allChildren.filter(isExtractedChild);
|
|
429
|
+
if (extractedChildren.length === allChildren.length) {
|
|
430
|
+
allChildren.forEach((child) => parentLink.insertBefore(child));
|
|
431
|
+
parentLink.remove();
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
const firstExtractedIndex = allChildren.findIndex(isExtractedChild);
|
|
435
|
+
const lastExtractedIndex = allChildren.findLastIndex(isExtractedChild);
|
|
436
|
+
const isAtStart = firstExtractedIndex === 0;
|
|
437
|
+
const isAtEnd = lastExtractedIndex === allChildren.length - 1;
|
|
438
|
+
if (isAtStart) extractedChildren.forEach((child) => parentLink.insertBefore(child));
|
|
439
|
+
else if (isAtEnd) for (let i = extractedChildren.length - 1; i >= 0; i--) parentLink.insertAfter(extractedChildren[i]);
|
|
440
|
+
else {
|
|
441
|
+
for (let i = extractedChildren.length - 1; i >= 0; i--) parentLink.insertAfter(extractedChildren[i]);
|
|
442
|
+
const trailingChildren = allChildren.slice(lastExtractedIndex + 1);
|
|
443
|
+
if (trailingChildren.length > 0) {
|
|
444
|
+
const newLink = $copyNode(parentLink);
|
|
445
|
+
extractedChildren[extractedChildren.length - 1].insertAfter(newLink);
|
|
446
|
+
trailingChildren.forEach((child) => newLink.append(child));
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Generates or updates a LinkNode. It can also delete a LinkNode if the URL is null,
|
|
452
|
+
* but saves any children and brings them up to the parent node.
|
|
453
|
+
* @param urlOrAttributes - The URL the link directs to, or an attributes object with an url property
|
|
454
|
+
* @param attributes - Optional HTML a tag attributes. \\{ target, rel, title \\}
|
|
455
|
+
*/
|
|
456
|
+
function $toggleLink(urlOrAttributes, attributes = {}) {
|
|
457
|
+
let url;
|
|
458
|
+
if (urlOrAttributes && typeof urlOrAttributes === "object") {
|
|
459
|
+
const { url: urlProp, ...rest } = urlOrAttributes;
|
|
460
|
+
url = urlProp;
|
|
461
|
+
attributes = {
|
|
462
|
+
...rest,
|
|
463
|
+
...attributes
|
|
464
|
+
};
|
|
465
|
+
} else url = urlOrAttributes;
|
|
466
|
+
const { target, title } = attributes;
|
|
467
|
+
const rel = attributes.rel === void 0 ? "noreferrer" : attributes.rel;
|
|
468
|
+
const selection = $getSelection();
|
|
469
|
+
if (selection === null || !$isRangeSelection(selection) && !$isNodeSelection(selection)) return;
|
|
470
|
+
if ($isNodeSelection(selection)) {
|
|
471
|
+
const nodes = selection.getNodes();
|
|
472
|
+
if (nodes.length === 0) return;
|
|
473
|
+
nodes.forEach((node) => {
|
|
474
|
+
if (url === null) {
|
|
475
|
+
const linkParent = $findMatchingParent(node, (parent) => !$isAutoLinkNode(parent) && $isLinkNode(parent));
|
|
476
|
+
if (linkParent) {
|
|
477
|
+
linkParent.insertBefore(node);
|
|
478
|
+
if (linkParent.getChildren().length === 0) linkParent.remove();
|
|
479
|
+
}
|
|
480
|
+
} else {
|
|
481
|
+
const existingLink = $findMatchingParent(node, (parent) => !$isAutoLinkNode(parent) && $isLinkNode(parent));
|
|
482
|
+
if (existingLink) {
|
|
483
|
+
existingLink.setURL(url);
|
|
484
|
+
if (target !== void 0) existingLink.setTarget(target);
|
|
485
|
+
if (rel !== void 0) existingLink.setRel(rel);
|
|
486
|
+
} else {
|
|
487
|
+
const linkNode = $createLinkNode(url, {
|
|
488
|
+
rel,
|
|
489
|
+
target
|
|
490
|
+
});
|
|
491
|
+
node.insertBefore(linkNode);
|
|
492
|
+
linkNode.append(node);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
if (selection.isCollapsed() && url === null) for (const node of selection.getNodes()) {
|
|
499
|
+
const parentLink = $findMatchingParent(node, (parent) => !$isAutoLinkNode(parent) && $isLinkNode(parent));
|
|
500
|
+
if (parentLink !== null) {
|
|
501
|
+
parentLink.getChildren().forEach((child) => {
|
|
502
|
+
parentLink.insertBefore(child);
|
|
503
|
+
});
|
|
504
|
+
parentLink.remove();
|
|
505
|
+
}
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
const nodes = selection.extract();
|
|
509
|
+
if (url === null) {
|
|
510
|
+
const processedLinks = /* @__PURE__ */ new Set();
|
|
511
|
+
nodes.forEach((node) => {
|
|
512
|
+
const parentLink = $findMatchingParent(node, (parent) => !$isAutoLinkNode(parent) && $isLinkNode(parent));
|
|
513
|
+
if (parentLink !== null) {
|
|
514
|
+
const linkKey = parentLink.getKey();
|
|
515
|
+
if (processedLinks.has(linkKey)) return;
|
|
516
|
+
$splitLinkAtSelection(parentLink, nodes);
|
|
517
|
+
processedLinks.add(linkKey);
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
const updatedNodes = /* @__PURE__ */ new Set();
|
|
523
|
+
const updateLinkNode = (linkNode) => {
|
|
524
|
+
if (updatedNodes.has(linkNode.getKey())) return;
|
|
525
|
+
updatedNodes.add(linkNode.getKey());
|
|
526
|
+
linkNode.setURL(url);
|
|
527
|
+
if (target !== void 0) linkNode.setTarget(target);
|
|
528
|
+
if (rel !== void 0) linkNode.setRel(rel);
|
|
529
|
+
if (title !== void 0) linkNode.setTitle(title);
|
|
530
|
+
};
|
|
531
|
+
if (nodes.length === 1) {
|
|
532
|
+
const firstNode = nodes[0];
|
|
533
|
+
const linkNode = $findMatchingParent(firstNode, $isLinkNode);
|
|
534
|
+
if (linkNode !== null) return updateLinkNode(linkNode);
|
|
535
|
+
}
|
|
536
|
+
$withSelectedNodes(() => {
|
|
537
|
+
let linkNode = null;
|
|
538
|
+
for (const node of nodes) {
|
|
539
|
+
if (!node.isAttached()) continue;
|
|
540
|
+
const parentLinkNode = $findMatchingParent(node, $isLinkNode);
|
|
541
|
+
if (parentLinkNode) {
|
|
542
|
+
updateLinkNode(parentLinkNode);
|
|
543
|
+
continue;
|
|
544
|
+
}
|
|
545
|
+
if ($isElementNode(node)) {
|
|
546
|
+
if (!node.isInline()) continue;
|
|
547
|
+
if ($isLinkNode(node)) {
|
|
548
|
+
if (!$isAutoLinkNode(node) && (linkNode === null || !linkNode.getParentOrThrow().isParentOf(node))) {
|
|
549
|
+
updateLinkNode(node);
|
|
550
|
+
linkNode = node;
|
|
551
|
+
continue;
|
|
552
|
+
}
|
|
553
|
+
for (const child of node.getChildren()) node.insertBefore(child);
|
|
554
|
+
node.remove();
|
|
555
|
+
continue;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
const prevLinkNode = node.getPreviousSibling();
|
|
559
|
+
if ($isLinkNode(prevLinkNode) && prevLinkNode.is(linkNode)) {
|
|
560
|
+
prevLinkNode.append(node);
|
|
561
|
+
continue;
|
|
562
|
+
}
|
|
563
|
+
linkNode = $createLinkNode(url, {
|
|
564
|
+
rel,
|
|
565
|
+
target,
|
|
566
|
+
title
|
|
567
|
+
});
|
|
568
|
+
node.insertAfter(linkNode);
|
|
569
|
+
linkNode.append(node);
|
|
570
|
+
}
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
const PHONE_NUMBER_REGEX = /^\+?[0-9\s()-]{5,}$/;
|
|
574
|
+
/**
|
|
575
|
+
* Formats a URL string by adding appropriate protocol if missing
|
|
576
|
+
*
|
|
577
|
+
* @param url - URL to format
|
|
578
|
+
* @returns Formatted URL with appropriate protocol
|
|
579
|
+
*/
|
|
580
|
+
function formatUrl(url) {
|
|
581
|
+
if (url.match(/^[a-z][a-z0-9+.-]*:/i)) return url;
|
|
582
|
+
else if (url.match(/^[/#.]/)) return url;
|
|
583
|
+
else if (url.includes("@")) return `mailto:${url}`;
|
|
584
|
+
else if (PHONE_NUMBER_REGEX.test(url)) return `tel:${url}`;
|
|
585
|
+
return `https://${url}`;
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
589
|
+
*
|
|
590
|
+
* This source code is licensed under the MIT license found in the
|
|
591
|
+
* LICENSE file in the root directory of this source tree.
|
|
592
|
+
*
|
|
593
|
+
*/
|
|
594
|
+
const defaultProps = {
|
|
595
|
+
attributes: void 0,
|
|
596
|
+
validateUrl: void 0
|
|
597
|
+
};
|
|
598
|
+
/** @internal */
|
|
599
|
+
function registerLink(editor, stores) {
|
|
600
|
+
return mergeRegister(editor.registerNodeTransform(LinkNode, $linkNodeTransform), editor.registerCommand(TOGGLE_LINK_COMMAND, (payload) => {
|
|
601
|
+
const validateUrl = stores.validateUrl.peek();
|
|
602
|
+
const attributes = stores.attributes.peek();
|
|
603
|
+
if (payload === null) {
|
|
604
|
+
$toggleLink(null);
|
|
605
|
+
return true;
|
|
606
|
+
} else if (typeof payload === "string") {
|
|
607
|
+
if (validateUrl === void 0 || validateUrl(payload)) {
|
|
608
|
+
$toggleLink(payload, attributes);
|
|
609
|
+
return true;
|
|
610
|
+
}
|
|
611
|
+
return false;
|
|
612
|
+
} else {
|
|
613
|
+
const { url, target, rel, title } = payload;
|
|
614
|
+
$toggleLink(url, {
|
|
615
|
+
...attributes,
|
|
616
|
+
rel,
|
|
617
|
+
target,
|
|
618
|
+
title
|
|
619
|
+
});
|
|
620
|
+
return true;
|
|
621
|
+
}
|
|
622
|
+
}, COMMAND_PRIORITY_EDITOR), effect(() => {
|
|
623
|
+
const validateUrl = stores.validateUrl.value;
|
|
624
|
+
if (!validateUrl) return;
|
|
625
|
+
const attributes = stores.attributes.value;
|
|
626
|
+
return editor.registerCommand(PASTE_COMMAND, (event) => {
|
|
627
|
+
const selection = $getSelection();
|
|
628
|
+
if (!$isRangeSelection(selection) || selection.isCollapsed() || !objectKlassEquals(event, ClipboardEvent)) return false;
|
|
629
|
+
if (event.clipboardData === null) return false;
|
|
630
|
+
const clipboardText = event.clipboardData.getData("text");
|
|
631
|
+
if (!validateUrl(clipboardText)) return false;
|
|
632
|
+
if (!selection.getNodes().some((node) => $isElementNode(node))) {
|
|
633
|
+
editor.dispatchCommand(TOGGLE_LINK_COMMAND, {
|
|
634
|
+
...attributes,
|
|
635
|
+
url: clipboardText
|
|
636
|
+
});
|
|
637
|
+
event.preventDefault();
|
|
638
|
+
return true;
|
|
639
|
+
}
|
|
640
|
+
return false;
|
|
641
|
+
}, COMMAND_PRIORITY_LOW);
|
|
642
|
+
}));
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Provides {@link LinkNode}, an implementation of
|
|
646
|
+
* {@link TOGGLE_LINK_COMMAND}, and a {@link PASTE_COMMAND}
|
|
647
|
+
* listener to wrap selected nodes in a link when a
|
|
648
|
+
* URL is pasted and `validateUrl` is defined.
|
|
649
|
+
*/
|
|
650
|
+
const LinkExtension = defineExtension({
|
|
651
|
+
build(editor, config, state) {
|
|
652
|
+
return namedSignals(config);
|
|
653
|
+
},
|
|
654
|
+
config: defaultProps,
|
|
655
|
+
mergeConfig(config, overrides) {
|
|
656
|
+
const merged = shallowMergeConfig(config, overrides);
|
|
657
|
+
if (config.attributes) merged.attributes = shallowMergeConfig(config.attributes, merged.attributes);
|
|
658
|
+
return merged;
|
|
659
|
+
},
|
|
660
|
+
name: "@lexical/link/Link",
|
|
661
|
+
nodes: () => [LinkNode],
|
|
662
|
+
register(editor, config, state) {
|
|
663
|
+
return registerLink(editor, state.getOutput());
|
|
664
|
+
}
|
|
665
|
+
});
|
|
666
|
+
/**
|
|
667
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
668
|
+
*
|
|
669
|
+
* This source code is licensed under the MIT license found in the
|
|
670
|
+
* LICENSE file in the root directory of this source tree.
|
|
671
|
+
*
|
|
672
|
+
*/
|
|
673
|
+
function findMatchingDOM(startNode, predicate) {
|
|
674
|
+
let node = startNode;
|
|
675
|
+
while (node != null) {
|
|
676
|
+
if (predicate(node)) return node;
|
|
677
|
+
node = node.parentNode;
|
|
678
|
+
}
|
|
679
|
+
return null;
|
|
680
|
+
}
|
|
681
|
+
function registerClickableLink(editor, stores, eventOptions = {}) {
|
|
682
|
+
const onClick = (event) => {
|
|
683
|
+
const target = event.target;
|
|
684
|
+
if (!isDOMNode(target)) return;
|
|
685
|
+
const nearestEditor = getNearestEditorFromDOMNode(target);
|
|
686
|
+
if (nearestEditor === null) return;
|
|
687
|
+
let url = null;
|
|
688
|
+
let urlTarget = null;
|
|
689
|
+
nearestEditor.update(() => {
|
|
690
|
+
const clickedNode = $getNearestNodeFromDOMNode(target);
|
|
691
|
+
if (clickedNode !== null) {
|
|
692
|
+
const maybeLinkNode = $findMatchingParent(clickedNode, $isElementNode);
|
|
693
|
+
if (!stores.disabled.peek()) if ($isLinkNode(maybeLinkNode)) {
|
|
694
|
+
url = maybeLinkNode.sanitizeUrl(maybeLinkNode.getURL());
|
|
695
|
+
urlTarget = maybeLinkNode.getTarget();
|
|
696
|
+
} else {
|
|
697
|
+
const a = findMatchingDOM(target, isHTMLAnchorElement);
|
|
698
|
+
if (a !== null) {
|
|
699
|
+
url = a.href;
|
|
700
|
+
urlTarget = a.target;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
});
|
|
705
|
+
if (url === null || url === "") return;
|
|
706
|
+
const selection = editor.getEditorState().read($getSelection, { editor });
|
|
707
|
+
if ($isRangeSelection(selection) && !selection.isCollapsed()) {
|
|
708
|
+
event.preventDefault();
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
const isMiddle = event.type === "auxclick" && event.button === 1;
|
|
712
|
+
window.open(url, stores.newTab.peek() || isMiddle || event.metaKey || event.ctrlKey || urlTarget === "_blank" ? "_blank" : "_self");
|
|
713
|
+
event.preventDefault();
|
|
714
|
+
};
|
|
715
|
+
const onMouseUp = (event) => {
|
|
716
|
+
if (event.button === 1) onClick(event);
|
|
717
|
+
};
|
|
718
|
+
return editor.registerRootListener((rootElement) => {
|
|
719
|
+
if (rootElement) {
|
|
720
|
+
rootElement.addEventListener("click", onClick, eventOptions);
|
|
721
|
+
rootElement.addEventListener("mouseup", onMouseUp, eventOptions);
|
|
722
|
+
return () => {
|
|
723
|
+
rootElement.removeEventListener("click", onClick);
|
|
724
|
+
rootElement.removeEventListener("mouseup", onMouseUp);
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Normally in a Lexical editor the `CLICK_COMMAND` on a LinkNode will cause the
|
|
731
|
+
* selection to change instead of opening a link. This extension can be used to
|
|
732
|
+
* restore the default behavior, e.g. when the editor is not editable.
|
|
733
|
+
*/
|
|
734
|
+
const ClickableLinkExtension = defineExtension({
|
|
735
|
+
build(editor, config, state) {
|
|
736
|
+
return namedSignals(config);
|
|
737
|
+
},
|
|
738
|
+
config: safeCast({
|
|
739
|
+
disabled: false,
|
|
740
|
+
newTab: false
|
|
741
|
+
}),
|
|
742
|
+
dependencies: [LinkExtension],
|
|
743
|
+
name: "@lexical/link/ClickableLink",
|
|
744
|
+
register(editor, config, state) {
|
|
745
|
+
return registerClickableLink(editor, state.getOutput());
|
|
746
|
+
}
|
|
747
|
+
});
|
|
748
|
+
/**
|
|
749
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
750
|
+
*
|
|
751
|
+
* This source code is licensed under the MIT license found in the
|
|
752
|
+
* LICENSE file in the root directory of this source tree.
|
|
753
|
+
*
|
|
754
|
+
*/
|
|
755
|
+
function createLinkMatcherWithRegExp(regExp, urlTransformer = (text) => text) {
|
|
756
|
+
return (text) => {
|
|
757
|
+
const match = regExp.exec(text);
|
|
758
|
+
if (match === null) return null;
|
|
759
|
+
return {
|
|
760
|
+
index: match.index,
|
|
761
|
+
length: match[0].length,
|
|
762
|
+
text: match[0],
|
|
763
|
+
url: urlTransformer(match[0])
|
|
764
|
+
};
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
function findFirstMatch(text, matchers) {
|
|
768
|
+
for (let i = 0; i < matchers.length; i++) {
|
|
769
|
+
const match = matchers[i](text);
|
|
770
|
+
if (match) return match;
|
|
771
|
+
}
|
|
772
|
+
return null;
|
|
773
|
+
}
|
|
774
|
+
const PUNCTUATION_OR_SPACE = /[.,;\s]/;
|
|
775
|
+
function isSeparator(char, separatorRegex) {
|
|
776
|
+
return separatorRegex.test(char);
|
|
777
|
+
}
|
|
778
|
+
function endsWithSeparator(textContent, separatorRegex) {
|
|
779
|
+
return isSeparator(textContent[textContent.length - 1], separatorRegex);
|
|
780
|
+
}
|
|
781
|
+
function startsWithSeparator(textContent, separatorRegex) {
|
|
782
|
+
return isSeparator(textContent[0], separatorRegex);
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* Check if the text content starts with a fullstop followed by a top-level domain.
|
|
786
|
+
* Meaning if the text content can be a beginning of a top level domain.
|
|
787
|
+
* @param textContent
|
|
788
|
+
* @param isEmail
|
|
789
|
+
* @returns boolean
|
|
790
|
+
*/
|
|
791
|
+
function startsWithTLD(textContent, isEmail) {
|
|
792
|
+
if (isEmail) return /^\.[a-zA-Z]{2,}/.test(textContent);
|
|
793
|
+
else return /^\.[a-zA-Z0-9]{1,}/.test(textContent);
|
|
794
|
+
}
|
|
795
|
+
function isPreviousNodeValid(node, separatorRegex) {
|
|
796
|
+
let previousNode = node.getPreviousSibling();
|
|
797
|
+
if ($isElementNode(previousNode)) previousNode = previousNode.getLastDescendant();
|
|
798
|
+
return previousNode === null || $isLineBreakNode(previousNode) || $isTextNode(previousNode) && endsWithSeparator(previousNode.getTextContent(), separatorRegex);
|
|
799
|
+
}
|
|
800
|
+
function isNextNodeValid(node, separatorRegex) {
|
|
801
|
+
let nextNode = node.getNextSibling();
|
|
802
|
+
if ($isElementNode(nextNode)) nextNode = nextNode.getFirstDescendant();
|
|
803
|
+
return nextNode === null || $isLineBreakNode(nextNode) || $isTextNode(nextNode) && startsWithSeparator(nextNode.getTextContent(), separatorRegex);
|
|
804
|
+
}
|
|
805
|
+
function isContentAroundIsValid(matchStart, matchEnd, separatorRegex, text, nodes) {
|
|
806
|
+
if (!(matchStart > 0 ? isSeparator(text[matchStart - 1], separatorRegex) : isPreviousNodeValid(nodes[0], separatorRegex))) return false;
|
|
807
|
+
return matchEnd < text.length ? isSeparator(text[matchEnd], separatorRegex) : isNextNodeValid(nodes[nodes.length - 1], separatorRegex);
|
|
808
|
+
}
|
|
809
|
+
function extractMatchingNodes(nodes, startIndex, endIndex) {
|
|
810
|
+
const unmodifiedBeforeNodes = [];
|
|
811
|
+
const matchingNodes = [];
|
|
812
|
+
const unmodifiedAfterNodes = [];
|
|
813
|
+
let matchingOffset = 0;
|
|
814
|
+
let currentOffset = 0;
|
|
815
|
+
const currentNodes = [...nodes];
|
|
816
|
+
while (currentNodes.length > 0) {
|
|
817
|
+
const currentNode = currentNodes[0];
|
|
818
|
+
const currentNodeLength = currentNode.getTextContent().length;
|
|
819
|
+
const currentNodeStart = currentOffset;
|
|
820
|
+
if (currentOffset + currentNodeLength <= startIndex) {
|
|
821
|
+
unmodifiedBeforeNodes.push(currentNode);
|
|
822
|
+
matchingOffset += currentNodeLength;
|
|
823
|
+
} else if (currentNodeStart >= endIndex) unmodifiedAfterNodes.push(currentNode);
|
|
824
|
+
else matchingNodes.push(currentNode);
|
|
825
|
+
currentOffset += currentNodeLength;
|
|
826
|
+
currentNodes.shift();
|
|
827
|
+
}
|
|
828
|
+
return [
|
|
829
|
+
matchingOffset,
|
|
830
|
+
unmodifiedBeforeNodes,
|
|
831
|
+
matchingNodes,
|
|
832
|
+
unmodifiedAfterNodes
|
|
833
|
+
];
|
|
834
|
+
}
|
|
835
|
+
function $createAutoLinkNode_(nodes, startIndex, endIndex, match) {
|
|
836
|
+
const linkNode = $createAutoLinkNode(match.url, match.attributes);
|
|
837
|
+
if (nodes.length === 1) {
|
|
838
|
+
let remainingTextNode = nodes[0];
|
|
839
|
+
let linkTextNode;
|
|
840
|
+
if (startIndex === 0) [linkTextNode, remainingTextNode] = remainingTextNode.splitText(endIndex);
|
|
841
|
+
else [, linkTextNode, remainingTextNode] = remainingTextNode.splitText(startIndex, endIndex);
|
|
842
|
+
const textNode = $createTextNode(match.text);
|
|
843
|
+
textNode.setFormat(linkTextNode.getFormat());
|
|
844
|
+
textNode.setDetail(linkTextNode.getDetail());
|
|
845
|
+
textNode.setStyle(linkTextNode.getStyle());
|
|
846
|
+
linkNode.append(textNode);
|
|
847
|
+
linkTextNode.replace(linkNode);
|
|
848
|
+
return remainingTextNode;
|
|
849
|
+
} else if (nodes.length > 1) {
|
|
850
|
+
const firstTextNode = nodes[0];
|
|
851
|
+
let offset = firstTextNode.getTextContent().length;
|
|
852
|
+
let firstLinkTextNode;
|
|
853
|
+
if (startIndex === 0) firstLinkTextNode = firstTextNode;
|
|
854
|
+
else [, firstLinkTextNode] = firstTextNode.splitText(startIndex);
|
|
855
|
+
const linkNodes = [];
|
|
856
|
+
let remainingTextNode;
|
|
857
|
+
for (let i = 1; i < nodes.length; i++) {
|
|
858
|
+
const currentNode = nodes[i];
|
|
859
|
+
const currentNodeLength = currentNode.getTextContent().length;
|
|
860
|
+
const currentNodeStart = offset;
|
|
861
|
+
const currentNodeEnd = offset + currentNodeLength;
|
|
862
|
+
if (currentNodeStart < endIndex) if (currentNodeEnd <= endIndex) linkNodes.push(currentNode);
|
|
863
|
+
else {
|
|
864
|
+
const [linkTextNode, endNode] = currentNode.splitText(endIndex - currentNodeStart);
|
|
865
|
+
linkNodes.push(linkTextNode);
|
|
866
|
+
remainingTextNode = endNode;
|
|
867
|
+
}
|
|
868
|
+
offset += currentNodeLength;
|
|
869
|
+
}
|
|
870
|
+
const selection = $getSelection();
|
|
871
|
+
const selectedTextNode = selection ? selection.getNodes().find($isTextNode) : void 0;
|
|
872
|
+
const textNode = $createTextNode(firstLinkTextNode.getTextContent());
|
|
873
|
+
textNode.setFormat(firstLinkTextNode.getFormat());
|
|
874
|
+
textNode.setDetail(firstLinkTextNode.getDetail());
|
|
875
|
+
textNode.setStyle(firstLinkTextNode.getStyle());
|
|
876
|
+
linkNode.append(textNode, ...linkNodes);
|
|
877
|
+
if (selectedTextNode && selectedTextNode === firstLinkTextNode) {
|
|
878
|
+
if ($isRangeSelection(selection)) textNode.select(selection.anchor.offset, selection.focus.offset);
|
|
879
|
+
else if ($isNodeSelection(selection)) textNode.select(0, textNode.getTextContent().length);
|
|
880
|
+
}
|
|
881
|
+
firstLinkTextNode.replace(linkNode);
|
|
882
|
+
return remainingTextNode;
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
function $handleLinkCreation(nodes, matchers, onChange, separatorRegex) {
|
|
886
|
+
for (const node of nodes) {
|
|
887
|
+
const parent = node.getParent();
|
|
888
|
+
if ($isAutoLinkNode(parent) && !parent.getIsUnlinked()) return;
|
|
889
|
+
}
|
|
890
|
+
let currentNodes = [...nodes];
|
|
891
|
+
const initialText = currentNodes.map((node) => node.getTextContent()).join("");
|
|
892
|
+
let text = initialText;
|
|
893
|
+
let match;
|
|
894
|
+
let invalidMatchEnd = 0;
|
|
895
|
+
while ((match = findFirstMatch(text, matchers)) && match !== null) {
|
|
896
|
+
const matchStart = match.index;
|
|
897
|
+
const matchEnd = matchStart + match.length;
|
|
898
|
+
if (isContentAroundIsValid(invalidMatchEnd + matchStart, invalidMatchEnd + matchEnd, separatorRegex, initialText, currentNodes)) {
|
|
899
|
+
const [matchingOffset, , matchingNodes, unmodifiedAfterNodes] = extractMatchingNodes(currentNodes, invalidMatchEnd + matchStart, invalidMatchEnd + matchEnd);
|
|
900
|
+
let alreadyLinked = false;
|
|
901
|
+
for (const node of matchingNodes) {
|
|
902
|
+
const parent = node.getParent();
|
|
903
|
+
if ($isAutoLinkNode(parent) && !parent.getIsUnlinked()) {
|
|
904
|
+
alreadyLinked = true;
|
|
905
|
+
break;
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
if (alreadyLinked) {
|
|
909
|
+
invalidMatchEnd += matchEnd;
|
|
910
|
+
text = text.substring(matchEnd);
|
|
911
|
+
continue;
|
|
912
|
+
}
|
|
913
|
+
const remainingTextNode = $createAutoLinkNode_(matchingNodes, invalidMatchEnd + matchStart - matchingOffset, invalidMatchEnd + matchEnd - matchingOffset, match);
|
|
914
|
+
currentNodes = remainingTextNode ? [remainingTextNode, ...unmodifiedAfterNodes] : unmodifiedAfterNodes;
|
|
915
|
+
onChange(match.url, null);
|
|
916
|
+
invalidMatchEnd = 0;
|
|
917
|
+
} else invalidMatchEnd += matchEnd;
|
|
918
|
+
text = text.substring(matchEnd);
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
function handleLinkEdit(linkNode, matchers, onChange, separatorRegex) {
|
|
922
|
+
const children = linkNode.getChildren();
|
|
923
|
+
const childrenLength = children.length;
|
|
924
|
+
for (let i = 0; i < childrenLength; i++) {
|
|
925
|
+
const child = children[i];
|
|
926
|
+
if (!$isTextNode(child) || !child.isSimpleText()) {
|
|
927
|
+
replaceWithChildren(linkNode);
|
|
928
|
+
onChange(null, linkNode.getURL());
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
const text = linkNode.getTextContent();
|
|
933
|
+
const match = findFirstMatch(text, matchers);
|
|
934
|
+
if (match === null || match.text !== text) {
|
|
935
|
+
replaceWithChildren(linkNode);
|
|
936
|
+
onChange(null, linkNode.getURL());
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
939
|
+
if (!isPreviousNodeValid(linkNode, separatorRegex) || !isNextNodeValid(linkNode, separatorRegex)) {
|
|
940
|
+
replaceWithChildren(linkNode);
|
|
941
|
+
onChange(null, linkNode.getURL());
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
944
|
+
const url = linkNode.getURL();
|
|
945
|
+
if (url !== match.url) {
|
|
946
|
+
linkNode.setURL(match.url);
|
|
947
|
+
onChange(match.url, url);
|
|
948
|
+
}
|
|
949
|
+
if (match.attributes) {
|
|
950
|
+
const rel = linkNode.getRel();
|
|
951
|
+
if (rel !== match.attributes.rel) {
|
|
952
|
+
linkNode.setRel(match.attributes.rel || null);
|
|
953
|
+
onChange(match.attributes.rel || null, rel);
|
|
954
|
+
}
|
|
955
|
+
const target = linkNode.getTarget();
|
|
956
|
+
if (target !== match.attributes.target) {
|
|
957
|
+
linkNode.setTarget(match.attributes.target || null);
|
|
958
|
+
onChange(match.attributes.target || null, target);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
function handleBadNeighbors(textNode, matchers, onChange, separatorRegex) {
|
|
963
|
+
const parent = textNode.getParent();
|
|
964
|
+
const previousSibling = textNode.getPreviousSibling();
|
|
965
|
+
const nextSibling = textNode.getNextSibling();
|
|
966
|
+
const text = textNode.getTextContent();
|
|
967
|
+
if ($isAutoLinkNode(parent) && !parent.getIsUnlinked()) return;
|
|
968
|
+
if ($isAutoLinkNode(previousSibling) && !previousSibling.getIsUnlinked()) {
|
|
969
|
+
if (previousSibling.is(textNode.getPreviousSibling()) && textNode.getParent() === previousSibling.getParent()) {
|
|
970
|
+
if (!startsWithSeparator(text, separatorRegex)) {
|
|
971
|
+
replaceWithChildren(previousSibling);
|
|
972
|
+
onChange(null, previousSibling.getURL());
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
975
|
+
if (startsWithTLD(text, previousSibling.isEmailURI())) {
|
|
976
|
+
const combinedText = previousSibling.getTextContent() + text;
|
|
977
|
+
const match = findFirstMatch(combinedText, matchers);
|
|
978
|
+
if (match !== null && match.text === combinedText) {
|
|
979
|
+
previousSibling.append(textNode);
|
|
980
|
+
handleLinkEdit(previousSibling, matchers, onChange, separatorRegex);
|
|
981
|
+
onChange(null, previousSibling.getURL());
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
if ($isAutoLinkNode(nextSibling) && !nextSibling.getIsUnlinked() && !endsWithSeparator(text, separatorRegex)) {
|
|
987
|
+
if (nextSibling.is(textNode.getNextSibling()) && textNode.getParent() === nextSibling.getParent()) {
|
|
988
|
+
replaceWithChildren(nextSibling);
|
|
989
|
+
onChange(null, nextSibling.getURL());
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
function replaceWithChildren(node) {
|
|
994
|
+
const children = node.getChildren();
|
|
995
|
+
const childrenLength = children.length;
|
|
996
|
+
for (let j = childrenLength - 1; j >= 0; j--) node.insertAfter(children[j]);
|
|
997
|
+
node.remove();
|
|
998
|
+
return children.map((child) => child.getLatest());
|
|
999
|
+
}
|
|
1000
|
+
function getTextNodesToMatch(textNode) {
|
|
1001
|
+
const textNodesToMatch = [textNode];
|
|
1002
|
+
let nextSibling = textNode.getNextSibling();
|
|
1003
|
+
while (nextSibling !== null && $isTextNode(nextSibling) && nextSibling.isSimpleText()) {
|
|
1004
|
+
textNodesToMatch.push(nextSibling);
|
|
1005
|
+
if (/[\s]/.test(nextSibling.getTextContent())) break;
|
|
1006
|
+
nextSibling = nextSibling.getNextSibling();
|
|
1007
|
+
}
|
|
1008
|
+
return textNodesToMatch;
|
|
1009
|
+
}
|
|
1010
|
+
const defaultConfig = {
|
|
1011
|
+
changeHandlers: [],
|
|
1012
|
+
excludeParents: [],
|
|
1013
|
+
matchers: [],
|
|
1014
|
+
separatorRegex: PUNCTUATION_OR_SPACE
|
|
1015
|
+
};
|
|
1016
|
+
function registerAutoLink(editor, config = defaultConfig) {
|
|
1017
|
+
const { matchers, changeHandlers, excludeParents, separatorRegex = PUNCTUATION_OR_SPACE } = config;
|
|
1018
|
+
const onChange = (url, prevUrl) => {
|
|
1019
|
+
for (const handler of changeHandlers) handler(url, prevUrl);
|
|
1020
|
+
};
|
|
1021
|
+
return mergeRegister(editor.registerNodeTransform(TextNode, (textNode) => {
|
|
1022
|
+
const parent = textNode.getParentOrThrow();
|
|
1023
|
+
const previous = textNode.getPreviousSibling();
|
|
1024
|
+
if ($isAutoLinkNode(parent)) handleLinkEdit(parent, matchers, onChange, separatorRegex);
|
|
1025
|
+
else if (!$isLinkNode(parent) && !excludeParents.some((pred) => pred(parent))) {
|
|
1026
|
+
if (textNode.isSimpleText() && (startsWithSeparator(textNode.getTextContent(), separatorRegex) || !$isAutoLinkNode(previous))) $handleLinkCreation(getTextNodesToMatch(textNode), matchers, onChange, separatorRegex);
|
|
1027
|
+
handleBadNeighbors(textNode, matchers, onChange, separatorRegex);
|
|
1028
|
+
}
|
|
1029
|
+
}), editor.registerCommand(TOGGLE_LINK_COMMAND, (payload) => {
|
|
1030
|
+
const selection = $getSelection();
|
|
1031
|
+
if (payload !== null || !$isRangeSelection(selection)) return false;
|
|
1032
|
+
selection.extract().forEach((node) => {
|
|
1033
|
+
const parent = node.getParent();
|
|
1034
|
+
if ($isAutoLinkNode(parent)) {
|
|
1035
|
+
parent.setIsUnlinked(!parent.getIsUnlinked());
|
|
1036
|
+
parent.markDirty();
|
|
1037
|
+
}
|
|
1038
|
+
});
|
|
1039
|
+
return false;
|
|
1040
|
+
}, COMMAND_PRIORITY_LOW));
|
|
1041
|
+
}
|
|
1042
|
+
/**
|
|
1043
|
+
* An extension to automatically create AutoLinkNode from text
|
|
1044
|
+
* that matches the configured matchers. No default implementation
|
|
1045
|
+
* is provided for any matcher, see {@link createLinkMatcherWithRegExp}
|
|
1046
|
+
* for a helper function to create a matcher from a RegExp, and the
|
|
1047
|
+
* Playground's [AutoLinkPlugin](https://github.com/facebook/lexical/blob/main/packages/lexical-playground/src/plugins/AutoLinkPlugin/index.tsx)
|
|
1048
|
+
* for some example RegExps that could be used.
|
|
1049
|
+
*
|
|
1050
|
+
* The given `matchers` and `changeHandlers` will be merged by
|
|
1051
|
+
* concatenating the configured arrays.
|
|
1052
|
+
*/
|
|
1053
|
+
const AutoLinkExtension = defineExtension({
|
|
1054
|
+
config: defaultConfig,
|
|
1055
|
+
dependencies: [LinkExtension],
|
|
1056
|
+
mergeConfig(config, overrides) {
|
|
1057
|
+
const merged = shallowMergeConfig(config, overrides);
|
|
1058
|
+
for (const k of [
|
|
1059
|
+
"matchers",
|
|
1060
|
+
"changeHandlers",
|
|
1061
|
+
"excludeParents"
|
|
1062
|
+
]) {
|
|
1063
|
+
const v = overrides[k];
|
|
1064
|
+
if (Array.isArray(v)) merged[k] = [...config[k], ...v];
|
|
1065
|
+
}
|
|
1066
|
+
return merged;
|
|
1067
|
+
},
|
|
1068
|
+
name: "@lexical/link/AutoLink",
|
|
1069
|
+
nodes: [AutoLinkNode],
|
|
1070
|
+
register: registerAutoLink
|
|
1071
|
+
});
|
|
1072
|
+
/**
|
|
1073
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
1074
|
+
*
|
|
1075
|
+
* This source code is licensed under the MIT license found in the
|
|
1076
|
+
* LICENSE file in the root directory of this source tree.
|
|
1077
|
+
*
|
|
1078
|
+
*/
|
|
1079
|
+
/** @deprecated renamed to {@link $toggleLink} by @lexical/eslint-plugin rules-of-lexical */
|
|
1080
|
+
const toggleLink = $toggleLink;
|
|
1081
|
+
//#endregion
|
|
1082
|
+
export { $createAutoLinkNode, $createLinkNode, $isAutoLinkNode, $isLinkNode, $toggleLink, AutoLinkExtension, AutoLinkNode, ClickableLinkExtension, LinkExtension, LinkNode, TOGGLE_LINK_COMMAND, createLinkMatcherWithRegExp, formatUrl, registerAutoLink, registerClickableLink, registerLink, toggleLink };
|
|
1083
|
+
|
|
1084
|
+
//# sourceMappingURL=LexicalLink.dev-DK87Biuy.mjs.map
|