@lexical/link 0.44.1-nightly.20260519.0 → 0.45.1-dev.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/{LexicalLink.dev.js → dist/LexicalLink.dev.js} +60 -2
- package/{LexicalLink.dev.mjs → dist/LexicalLink.dev.mjs} +60 -4
- package/{LexicalLink.mjs → dist/LexicalLink.mjs} +2 -0
- package/{LexicalLink.node.mjs → dist/LexicalLink.node.mjs} +2 -0
- package/dist/LexicalLink.prod.js +9 -0
- package/dist/LexicalLink.prod.mjs +9 -0
- package/dist/LinkImportExtension.d.ts +23 -0
- package/{index.d.ts → dist/index.d.ts} +1 -0
- package/package.json +33 -17
- package/src/ClickableLinkExtension.ts +142 -0
- package/src/LexicalAutoLinkExtension.ts +639 -0
- package/src/LexicalLinkExtension.ts +164 -0
- package/src/LexicalLinkNode.ts +964 -0
- package/src/LinkImportExtension.ts +67 -0
- package/src/index.ts +43 -0
- package/LexicalLink.prod.js +0 -9
- package/LexicalLink.prod.mjs +0 -9
- /package/{ClickableLinkExtension.d.ts → dist/ClickableLinkExtension.d.ts} +0 -0
- /package/{LexicalAutoLinkExtension.d.ts → dist/LexicalAutoLinkExtension.d.ts} +0 -0
- /package/{LexicalLink.js → dist/LexicalLink.js} +0 -0
- /package/{LexicalLink.js.flow → dist/LexicalLink.js.flow} +0 -0
- /package/{LexicalLinkExtension.d.ts → dist/LexicalLinkExtension.d.ts} +0 -0
- /package/{LexicalLinkNode.d.ts → dist/LexicalLinkNode.d.ts} +0 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {effect, namedSignals, NamedSignalsOutput} from '@lexical/extension';
|
|
10
|
+
import {mergeRegister, objectKlassEquals} from '@lexical/utils';
|
|
11
|
+
import {
|
|
12
|
+
$getSelection,
|
|
13
|
+
$isElementNode,
|
|
14
|
+
$isRangeSelection,
|
|
15
|
+
$isTextNode,
|
|
16
|
+
COMMAND_PRIORITY_EDITOR,
|
|
17
|
+
COMMAND_PRIORITY_LOW,
|
|
18
|
+
defineExtension,
|
|
19
|
+
LexicalEditor,
|
|
20
|
+
PASTE_COMMAND,
|
|
21
|
+
shallowMergeConfig,
|
|
22
|
+
} from 'lexical';
|
|
23
|
+
|
|
24
|
+
import {
|
|
25
|
+
$linkNodeTransform,
|
|
26
|
+
$toggleLink,
|
|
27
|
+
LinkAttributes,
|
|
28
|
+
LinkNode,
|
|
29
|
+
TOGGLE_LINK_COMMAND,
|
|
30
|
+
} from './LexicalLinkNode';
|
|
31
|
+
|
|
32
|
+
export interface LinkConfig {
|
|
33
|
+
/**
|
|
34
|
+
* If this function is specified a {@link PASTE_COMMAND}
|
|
35
|
+
* listener will be registered to wrap selected nodes
|
|
36
|
+
* when a URL is pasted and `validateUrl(url)` returns true.
|
|
37
|
+
* The default of `undefined` will not register this listener.
|
|
38
|
+
*
|
|
39
|
+
* In the implementation of {@link TOGGLE_LINK_COMMAND}
|
|
40
|
+
* it will reject URLs that return false when specified.
|
|
41
|
+
* The default of `undefined` will always accept URLs.
|
|
42
|
+
*/
|
|
43
|
+
validateUrl: undefined | ((url: string) => boolean);
|
|
44
|
+
/**
|
|
45
|
+
* The default anchor tag attributes to use for
|
|
46
|
+
* {@link TOGGLE_LINK_COMMAND}
|
|
47
|
+
*/
|
|
48
|
+
attributes: undefined | LinkAttributes;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const defaultProps: LinkConfig = {
|
|
52
|
+
attributes: undefined,
|
|
53
|
+
validateUrl: undefined,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/** @internal */
|
|
57
|
+
export function registerLink(
|
|
58
|
+
editor: LexicalEditor,
|
|
59
|
+
stores: NamedSignalsOutput<LinkConfig>,
|
|
60
|
+
) {
|
|
61
|
+
return mergeRegister(
|
|
62
|
+
editor.registerNodeTransform(LinkNode, $linkNodeTransform),
|
|
63
|
+
editor.registerCommand(
|
|
64
|
+
TOGGLE_LINK_COMMAND,
|
|
65
|
+
payload => {
|
|
66
|
+
const validateUrl = stores.validateUrl.peek();
|
|
67
|
+
const attributes = stores.attributes.peek();
|
|
68
|
+
if (payload === null) {
|
|
69
|
+
$toggleLink(null);
|
|
70
|
+
return true;
|
|
71
|
+
} else if (typeof payload === 'string') {
|
|
72
|
+
if (validateUrl === undefined || validateUrl(payload)) {
|
|
73
|
+
$toggleLink(payload, attributes);
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
return false;
|
|
77
|
+
} else {
|
|
78
|
+
const {url, target, rel, title} = payload;
|
|
79
|
+
$toggleLink(url, {
|
|
80
|
+
...attributes,
|
|
81
|
+
rel,
|
|
82
|
+
target,
|
|
83
|
+
title,
|
|
84
|
+
});
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
COMMAND_PRIORITY_EDITOR,
|
|
89
|
+
),
|
|
90
|
+
effect(() => {
|
|
91
|
+
const validateUrl = stores.validateUrl.value;
|
|
92
|
+
if (!validateUrl) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const attributes = stores.attributes.value;
|
|
96
|
+
return editor.registerCommand(
|
|
97
|
+
PASTE_COMMAND,
|
|
98
|
+
event => {
|
|
99
|
+
const selection = $getSelection();
|
|
100
|
+
if (
|
|
101
|
+
!$isRangeSelection(selection) ||
|
|
102
|
+
selection.isCollapsed() ||
|
|
103
|
+
!objectKlassEquals(event, ClipboardEvent)
|
|
104
|
+
) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
if (event.clipboardData === null) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
const clipboardText = event.clipboardData.getData('text');
|
|
111
|
+
if (!validateUrl(clipboardText)) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
// Skip link wrapping for non-simple text nodes (e.g. code blocks).
|
|
115
|
+
const nodes = selection.getNodes();
|
|
116
|
+
if (
|
|
117
|
+
!nodes.some(
|
|
118
|
+
node =>
|
|
119
|
+
$isElementNode(node) ||
|
|
120
|
+
($isTextNode(node) && !node.isSimpleText()),
|
|
121
|
+
)
|
|
122
|
+
) {
|
|
123
|
+
editor.dispatchCommand(TOGGLE_LINK_COMMAND, {
|
|
124
|
+
...attributes,
|
|
125
|
+
url: clipboardText,
|
|
126
|
+
});
|
|
127
|
+
event.preventDefault();
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
return false;
|
|
131
|
+
},
|
|
132
|
+
COMMAND_PRIORITY_LOW,
|
|
133
|
+
);
|
|
134
|
+
}),
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Provides {@link LinkNode}, an implementation of
|
|
140
|
+
* {@link TOGGLE_LINK_COMMAND}, and a {@link PASTE_COMMAND}
|
|
141
|
+
* listener to wrap selected nodes in a link when a
|
|
142
|
+
* URL is pasted and `validateUrl` is defined.
|
|
143
|
+
*/
|
|
144
|
+
export const LinkExtension = defineExtension({
|
|
145
|
+
build(editor, config, state) {
|
|
146
|
+
return namedSignals(config);
|
|
147
|
+
},
|
|
148
|
+
config: defaultProps,
|
|
149
|
+
mergeConfig(config, overrides) {
|
|
150
|
+
const merged = shallowMergeConfig(config, overrides);
|
|
151
|
+
if (config.attributes) {
|
|
152
|
+
merged.attributes = shallowMergeConfig(
|
|
153
|
+
config.attributes,
|
|
154
|
+
merged.attributes,
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
return merged;
|
|
158
|
+
},
|
|
159
|
+
name: '@lexical/link/Link',
|
|
160
|
+
nodes: () => [LinkNode],
|
|
161
|
+
register(editor, config, state) {
|
|
162
|
+
return registerLink(editor, state.getOutput());
|
|
163
|
+
},
|
|
164
|
+
});
|