5htp-core 0.4.9-2 → 0.4.9-6

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 (30) hide show
  1. package/package.json +4 -1
  2. package/src/client/assets/css/components/lists.less +3 -3
  3. package/src/client/components/Form.ts +1 -1
  4. package/src/client/components/Select/ChoiceElement.tsx +20 -27
  5. package/src/client/components/Select/index.tsx +24 -42
  6. package/src/client/components/inputv3/Rte/Editor.tsx +16 -8
  7. package/src/client/components/inputv3/Rte/ToolbarPlugin/index.tsx +8 -12
  8. package/src/client/components/inputv3/Rte/nodes/HeadingNode.ts +55 -0
  9. package/src/client/components/inputv3/Rte/plugins/ComponentPickerPlugin/index.tsx +9 -6
  10. package/src/client/components/inputv3/Rte/plugins/DraggableBlockPlugin/index.css +23 -23
  11. package/src/client/components/inputv3/Rte/plugins/DraggableBlockPlugin/index.tsx +26 -24
  12. package/src/client/components/inputv3/Rte/plugins/FloatingLinkEditorPlugin/index.css +10 -38
  13. package/src/client/components/inputv3/Rte/plugins/FloatingLinkEditorPlugin/index.tsx +349 -356
  14. package/src/client/components/inputv3/Rte/plugins/FloatingTextFormatToolbarPlugin/index.css +80 -84
  15. package/src/client/components/inputv3/Rte/plugins/FloatingTextFormatToolbarPlugin/index.tsx +308 -347
  16. package/src/client/components/inputv3/Rte/style.less +0 -2
  17. package/src/client/components/inputv3/Rte/themes/PlaygroundEditorTheme.css +0 -11
  18. package/src/client/components/inputv3/Rte/themes/PlaygroundEditorTheme.ts +2 -2
  19. package/src/client/components/inputv3/base.less +0 -6
  20. package/src/client/components/inputv3/file/index.tsx +50 -48
  21. package/src/client/components/inputv3/index.tsx +1 -1
  22. package/src/client/services/router/request/api.ts +29 -13
  23. package/src/common/data/rte/nodes.ts +9 -1
  24. package/src/common/router/request/api.ts +6 -4
  25. package/src/server/services/disks/driver.ts +2 -0
  26. package/src/server/services/disks/drivers/s3/index.ts +30 -1
  27. package/src/server/utils/rte.ts +316 -46
  28. package/src/server/utils/slug.ts +67 -0
  29. package/src/client/components/inputv3/Rte/nodes/PlaygroundNodes.ts +0 -76
  30. package/src/server/services/schema/rte.ts +0 -110
@@ -8,386 +8,379 @@
8
8
  import './index.css';
9
9
 
10
10
  import {
11
- $createLinkNode,
12
- $isAutoLinkNode,
13
- $isLinkNode,
14
- TOGGLE_LINK_COMMAND,
11
+ $createLinkNode,
12
+ $isAutoLinkNode,
13
+ $isLinkNode,
14
+ TOGGLE_LINK_COMMAND,
15
15
  } from '@lexical/link';
16
- import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
17
- import {$findMatchingParent, mergeRegister} from '@lexical/utils';
16
+ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
17
+ import { $findMatchingParent, mergeRegister } from '@lexical/utils';
18
18
  import {
19
- $getSelection,
20
- $isLineBreakNode,
21
- $isRangeSelection,
22
- BaseSelection,
23
- CLICK_COMMAND,
24
- COMMAND_PRIORITY_CRITICAL,
25
- COMMAND_PRIORITY_HIGH,
26
- COMMAND_PRIORITY_LOW,
27
- KEY_ESCAPE_COMMAND,
28
- LexicalEditor,
29
- SELECTION_CHANGE_COMMAND,
19
+ $getSelection,
20
+ $isLineBreakNode,
21
+ $isRangeSelection,
22
+ BaseSelection,
23
+ CLICK_COMMAND,
24
+ COMMAND_PRIORITY_CRITICAL,
25
+ COMMAND_PRIORITY_HIGH,
26
+ COMMAND_PRIORITY_LOW,
27
+ KEY_ESCAPE_COMMAND,
28
+ LexicalEditor,
29
+ SELECTION_CHANGE_COMMAND,
30
30
  } from 'lexical';
31
- import {Dispatch, useCallback, useEffect, useRef, useState} from 'react';
31
+ import { Dispatch, useCallback, useEffect, useRef, useState } from 'react';
32
32
  import * as React from 'react';
33
- import {createPortal} from 'react-dom';
33
+ import { createPortal } from 'react-dom';
34
34
 
35
- import {getSelectedNode} from '../../utils/getSelectedNode';
36
- import {setFloatingElemPositionForLinkEditor} from '../../utils/setFloatingElemPositionForLinkEditor';
37
- import {sanitizeUrl} from '../../utils/url';
35
+ import Button from '@client/components/button';
36
+
37
+ import { getSelectedNode } from '../../utils/getSelectedNode';
38
+ import { setFloatingElemPositionForLinkEditor } from '../../utils/setFloatingElemPositionForLinkEditor';
39
+ import { sanitizeUrl } from '../../utils/url';
38
40
 
39
41
  function FloatingLinkEditor({
40
- editor,
41
- isLink,
42
- setIsLink,
43
- anchorElem,
44
- isLinkEditMode,
45
- setIsLinkEditMode,
42
+ editor,
43
+ isLink,
44
+ setIsLink,
45
+ anchorElem,
46
+ isLinkEditMode,
47
+ setIsLinkEditMode,
46
48
  }: {
47
- editor: LexicalEditor;
48
- isLink: boolean;
49
- setIsLink: Dispatch<boolean>;
50
- anchorElem: HTMLElement;
51
- isLinkEditMode: boolean;
52
- setIsLinkEditMode: Dispatch<boolean>;
53
- }): JSX.Element {
54
- const editorRef = useRef<HTMLDivElement | null>(null);
55
- const inputRef = useRef<HTMLInputElement>(null);
56
- const [linkUrl, setLinkUrl] = useState('');
57
- const [editedLinkUrl, setEditedLinkUrl] = useState('https://');
58
- const [lastSelection, setLastSelection] = useState<BaseSelection | null>(
59
- null,
60
- );
61
-
62
- const $updateLinkEditor = useCallback(() => {
63
- const selection = $getSelection();
64
- if ($isRangeSelection(selection)) {
65
- const node = getSelectedNode(selection);
66
- const linkParent = $findMatchingParent(node, $isLinkNode);
67
-
68
- if (linkParent) {
69
- setLinkUrl(linkParent.getURL());
70
- } else if ($isLinkNode(node)) {
71
- setLinkUrl(node.getURL());
72
- } else {
73
- setLinkUrl('');
74
- }
75
- if (isLinkEditMode) {
76
- setEditedLinkUrl(linkUrl);
77
- }
78
- }
79
- const editorElem = editorRef.current;
80
- const nativeSelection = window.getSelection();
81
- const activeElement = document.activeElement;
82
-
83
- if (editorElem === null) {
84
- return;
85
- }
86
-
87
- const rootElement = editor.getRootElement();
88
-
89
- if (
90
- selection !== null &&
91
- nativeSelection !== null &&
92
- rootElement !== null &&
93
- rootElement.contains(nativeSelection.anchorNode) &&
94
- editor.isEditable()
95
- ) {
96
- const domRect: DOMRect | undefined =
97
- nativeSelection.focusNode?.parentElement?.getBoundingClientRect();
98
- if (domRect) {
99
- domRect.y += 40;
100
- setFloatingElemPositionForLinkEditor(domRect, editorElem, anchorElem);
101
- }
102
- setLastSelection(selection);
103
- } else if (!activeElement || activeElement.className !== 'link-input') {
104
- if (rootElement !== null) {
105
- setFloatingElemPositionForLinkEditor(null, editorElem, anchorElem);
106
- }
107
- setLastSelection(null);
108
- setIsLinkEditMode(false);
109
- setLinkUrl('');
110
- }
111
-
112
- return true;
113
- }, [anchorElem, editor, setIsLinkEditMode, isLinkEditMode, linkUrl]);
114
-
115
- useEffect(() => {
116
- const scrollerElem = anchorElem.parentElement;
117
-
118
- const update = () => {
119
- editor.getEditorState().read(() => {
120
- $updateLinkEditor();
121
- });
122
- };
49
+ editor: LexicalEditor;
50
+ isLink: boolean;
51
+ setIsLink: Dispatch<boolean>;
52
+ anchorElem: HTMLElement;
53
+ isLinkEditMode: boolean;
54
+ setIsLinkEditMode: Dispatch<boolean>;
55
+ }): React.JSX.Element {
56
+ const editorRef = useRef<HTMLDivElement | null>(null);
57
+ const inputRef = useRef<HTMLInputElement>(null);
58
+ const [linkUrl, setLinkUrl] = useState('');
59
+ const [editedLinkUrl, setEditedLinkUrl] = useState('https://');
60
+ const [lastSelection, setLastSelection] = useState<BaseSelection | null>(
61
+ null,
62
+ );
123
63
 
124
- window.addEventListener('resize', update);
64
+ const $updateLinkEditor = useCallback(() => {
65
+ const selection = $getSelection();
66
+ if ($isRangeSelection(selection)) {
67
+ const node = getSelectedNode(selection);
68
+ const linkParent = $findMatchingParent(node, $isLinkNode);
125
69
 
126
- if (scrollerElem) {
127
- scrollerElem.addEventListener('scroll', update);
128
- }
70
+ if (linkParent) {
71
+ setLinkUrl(linkParent.getURL());
72
+ } else if ($isLinkNode(node)) {
73
+ setLinkUrl(node.getURL());
74
+ } else {
75
+ setLinkUrl('');
76
+ }
77
+ if (isLinkEditMode) {
78
+ setEditedLinkUrl(linkUrl);
79
+ }
80
+ }
81
+ const editorElem = editorRef.current;
82
+ const nativeSelection = window.getSelection();
83
+ const activeElement = document.activeElement;
129
84
 
130
- return () => {
131
- window.removeEventListener('resize', update);
85
+ if (editorElem === null) {
86
+ return;
87
+ }
132
88
 
133
- if (scrollerElem) {
134
- scrollerElem.removeEventListener('scroll', update);
135
- }
136
- };
137
- }, [anchorElem.parentElement, editor, $updateLinkEditor]);
89
+ const rootElement = editor.getRootElement();
138
90
 
139
- useEffect(() => {
140
- return mergeRegister(
141
- editor.registerUpdateListener(({editorState}) => {
142
- editorState.read(() => {
143
- $updateLinkEditor();
144
- });
145
- }),
146
-
147
- editor.registerCommand(
148
- SELECTION_CHANGE_COMMAND,
149
- () => {
150
- $updateLinkEditor();
151
- return true;
152
- },
153
- COMMAND_PRIORITY_LOW,
154
- ),
155
- editor.registerCommand(
156
- KEY_ESCAPE_COMMAND,
157
- () => {
158
- if (isLink) {
159
- setIsLink(false);
160
- return true;
161
- }
162
- return false;
163
- },
164
- COMMAND_PRIORITY_HIGH,
165
- ),
166
- );
167
- }, [editor, $updateLinkEditor, setIsLink, isLink]);
168
-
169
- useEffect(() => {
170
- editor.getEditorState().read(() => {
171
- $updateLinkEditor();
172
- });
173
- }, [editor, $updateLinkEditor]);
174
-
175
- useEffect(() => {
176
- if (isLinkEditMode && inputRef.current) {
177
- inputRef.current.focus();
178
- }
179
- }, [isLinkEditMode, isLink]);
180
-
181
- const monitorInputInteraction = (
182
- event: React.KeyboardEvent<HTMLInputElement>,
183
- ) => {
184
- if (event.key === 'Enter') {
185
- event.preventDefault();
186
- handleLinkSubmission();
187
- } else if (event.key === 'Escape') {
188
- event.preventDefault();
189
- setIsLinkEditMode(false);
190
- }
191
- };
192
-
193
- const handleLinkSubmission = () => {
194
- if (lastSelection !== null) {
195
- if (linkUrl !== '') {
196
- editor.dispatchCommand(TOGGLE_LINK_COMMAND, sanitizeUrl(editedLinkUrl));
197
- editor.update(() => {
198
- const selection = $getSelection();
199
- if ($isRangeSelection(selection)) {
200
- const parent = getSelectedNode(selection).getParent();
201
- if ($isAutoLinkNode(parent)) {
202
- const linkNode = $createLinkNode(parent.getURL(), {
203
- rel: parent.__rel,
204
- target: parent.__target,
205
- title: parent.__title,
206
- });
207
- parent.replace(linkNode, true);
91
+ if (
92
+ selection !== null &&
93
+ nativeSelection !== null &&
94
+ rootElement !== null &&
95
+ rootElement.contains(nativeSelection.anchorNode) &&
96
+ editor.isEditable()
97
+ ) {
98
+ const domRect: DOMRect | undefined =
99
+ nativeSelection.focusNode?.parentElement?.getBoundingClientRect();
100
+ if (domRect) {
101
+ domRect.y += 40;
102
+ setFloatingElemPositionForLinkEditor(domRect, editorElem, anchorElem);
103
+ }
104
+ setLastSelection(selection);
105
+ } else if (!activeElement || activeElement.className !== 'link-input') {
106
+ if (rootElement !== null) {
107
+ setFloatingElemPositionForLinkEditor(null, editorElem, anchorElem);
208
108
  }
209
- }
109
+ setLastSelection(null);
110
+ setIsLinkEditMode(false);
111
+ setLinkUrl('');
112
+ }
113
+
114
+ return true;
115
+ }, [anchorElem, editor, setIsLinkEditMode, isLinkEditMode, linkUrl]);
116
+
117
+ useEffect(() => {
118
+ const scrollerElem = anchorElem.parentElement;
119
+
120
+ const update = () => {
121
+ editor.getEditorState().read(() => {
122
+ $updateLinkEditor();
123
+ });
124
+ };
125
+
126
+ window.addEventListener('resize', update);
127
+
128
+ if (scrollerElem) {
129
+ scrollerElem.addEventListener('scroll', update);
130
+ }
131
+
132
+ return () => {
133
+ window.removeEventListener('resize', update);
134
+
135
+ if (scrollerElem) {
136
+ scrollerElem.removeEventListener('scroll', update);
137
+ }
138
+ };
139
+ }, [anchorElem.parentElement, editor, $updateLinkEditor]);
140
+
141
+ useEffect(() => {
142
+ return mergeRegister(
143
+ editor.registerUpdateListener(({ editorState }) => {
144
+ editorState.read(() => {
145
+ $updateLinkEditor();
146
+ });
147
+ }),
148
+
149
+ editor.registerCommand(
150
+ SELECTION_CHANGE_COMMAND,
151
+ () => {
152
+ $updateLinkEditor();
153
+ return true;
154
+ },
155
+ COMMAND_PRIORITY_LOW,
156
+ ),
157
+ editor.registerCommand(
158
+ KEY_ESCAPE_COMMAND,
159
+ () => {
160
+ if (isLink) {
161
+ setIsLink(false);
162
+ return true;
163
+ }
164
+ return false;
165
+ },
166
+ COMMAND_PRIORITY_HIGH,
167
+ ),
168
+ );
169
+ }, [editor, $updateLinkEditor, setIsLink, isLink]);
170
+
171
+ useEffect(() => {
172
+ editor.getEditorState().read(() => {
173
+ $updateLinkEditor();
210
174
  });
211
- }
212
- setEditedLinkUrl('https://');
213
- setIsLinkEditMode(false);
214
- }
215
- };
216
-
217
- return (
218
- <div ref={editorRef} className="link-editor">
219
- {!isLink ? null : isLinkEditMode ? (
220
- <>
221
- <input
222
- ref={inputRef}
223
- className="link-input"
224
- value={editedLinkUrl}
225
- onChange={(event) => {
226
- setEditedLinkUrl(event.target.value);
227
- }}
228
- onKeyDown={(event) => {
229
- monitorInputInteraction(event);
230
- }}
231
- />
232
- <div>
233
- <div
234
- className="link-cancel"
235
- role="button"
236
- tabIndex={0}
237
- onMouseDown={(event) => event.preventDefault()}
238
- onClick={() => {
239
- setIsLinkEditMode(false);
240
- }}
241
- />
242
-
243
- <div
244
- className="link-confirm"
245
- role="button"
246
- tabIndex={0}
247
- onMouseDown={(event) => event.preventDefault()}
248
- onClick={handleLinkSubmission}
249
- />
250
- </div>
251
- </>
252
- ) : (
253
- <div className="link-view">
254
- <a
255
- href={sanitizeUrl(linkUrl)}
256
- target="_blank"
257
- rel="noopener noreferrer">
258
- {linkUrl}
259
- </a>
260
- <div
261
- className="link-edit"
262
- role="button"
263
- tabIndex={0}
264
- onMouseDown={(event) => event.preventDefault()}
265
- onClick={() => {
266
- setEditedLinkUrl(linkUrl);
267
- setIsLinkEditMode(true);
268
- }}
269
- />
270
- <div
271
- className="link-trash"
272
- role="button"
273
- tabIndex={0}
274
- onMouseDown={(event) => event.preventDefault()}
275
- onClick={() => {
276
- editor.dispatchCommand(TOGGLE_LINK_COMMAND, null);
277
- }}
278
- />
175
+ }, [editor, $updateLinkEditor]);
176
+
177
+ useEffect(() => {
178
+ if (isLinkEditMode && inputRef.current) {
179
+ inputRef.current.focus();
180
+ }
181
+ }, [isLinkEditMode, isLink]);
182
+
183
+ const monitorInputInteraction = (
184
+ event: React.KeyboardEvent<HTMLInputElement>,
185
+ ) => {
186
+ if (event.key === 'Enter') {
187
+ event.preventDefault();
188
+ handleLinkSubmission();
189
+ } else if (event.key === 'Escape') {
190
+ event.preventDefault();
191
+ setIsLinkEditMode(false);
192
+ }
193
+ };
194
+
195
+ const handleLinkSubmission = () => {
196
+ if (lastSelection !== null) {
197
+ if (linkUrl !== '') {
198
+ editor.dispatchCommand(TOGGLE_LINK_COMMAND, sanitizeUrl(editedLinkUrl));
199
+ editor.update(() => {
200
+ const selection = $getSelection();
201
+ if ($isRangeSelection(selection)) {
202
+ const parent = getSelectedNode(selection).getParent();
203
+ if ($isAutoLinkNode(parent)) {
204
+ const linkNode = $createLinkNode(parent.getURL(), {
205
+ rel: parent.__rel,
206
+ target: parent.__target,
207
+ title: parent.__title,
208
+ });
209
+ parent.replace(linkNode, true);
210
+ }
211
+ }
212
+ });
213
+ }
214
+ setEditedLinkUrl('https://');
215
+ setIsLinkEditMode(false);
216
+ }
217
+ };
218
+
219
+ if (!isLink) return null;
220
+
221
+ return (
222
+ <div ref={editorRef} className="link-editor card row menu pd-05">
223
+ {isLinkEditMode ? (
224
+ <>
225
+ <input
226
+ ref={inputRef}
227
+ className="link-input pdh-1"
228
+ value={editedLinkUrl}
229
+ onChange={(event) => {
230
+ setEditedLinkUrl(event.target.value);
231
+ }}
232
+ onKeyDown={(event) => {
233
+ monitorInputInteraction(event);
234
+ }}
235
+ />
236
+ <Button icon="unlink" size="s"
237
+ onMouseDown={(event) => event.preventDefault()}
238
+ onClick={() => {
239
+ setIsLinkEditMode(false);
240
+ }}
241
+ />
242
+
243
+ <Button icon="check" size="s"
244
+ onMouseDown={(event) => event.preventDefault()}
245
+ onClick={handleLinkSubmission}
246
+ />
247
+ </>
248
+ ) : <>
249
+
250
+ <a
251
+ href={sanitizeUrl(linkUrl)}
252
+ target="_blank"
253
+ rel="noopener noreferrer"
254
+ className="col-1 pdh-1"
255
+ >
256
+ {linkUrl}
257
+ </a>
258
+
259
+ <Button icon="pen" size="s"
260
+ onMouseDown={(event) => event.preventDefault()}
261
+ onClick={() => {
262
+ setEditedLinkUrl(linkUrl);
263
+ setIsLinkEditMode(true);
264
+ }}
265
+ />
266
+
267
+ <Button icon="unlink" size="s"
268
+ onMouseDown={(event) => event.preventDefault()}
269
+ onClick={() => {
270
+ editor.dispatchCommand(TOGGLE_LINK_COMMAND, null);
271
+ }}
272
+ />
273
+ </>}
279
274
  </div>
280
- )}
281
- </div>
282
- );
275
+ );
283
276
  }
284
277
 
285
278
  function useFloatingLinkEditorToolbar(
286
- editor: LexicalEditor,
287
- anchorElem: HTMLElement,
288
- isLinkEditMode: boolean,
289
- setIsLinkEditMode: Dispatch<boolean>,
290
- ): JSX.Element | null {
291
- const [activeEditor, setActiveEditor] = useState(editor);
292
- const [isLink, setIsLink] = useState(false);
293
-
294
- useEffect(() => {
295
- function $updateToolbar() {
296
- const selection = $getSelection();
297
- if ($isRangeSelection(selection)) {
298
- const focusNode = getSelectedNode(selection);
299
- const focusLinkNode = $findMatchingParent(focusNode, $isLinkNode);
300
- const focusAutoLinkNode = $findMatchingParent(
301
- focusNode,
302
- $isAutoLinkNode,
303
- );
304
- if (!(focusLinkNode || focusAutoLinkNode)) {
305
- setIsLink(false);
306
- return;
307
- }
308
- const badNode = selection
309
- .getNodes()
310
- .filter((node) => !$isLineBreakNode(node))
311
- .find((node) => {
312
- const linkNode = $findMatchingParent(node, $isLinkNode);
313
- const autoLinkNode = $findMatchingParent(node, $isAutoLinkNode);
314
- return (
315
- (focusLinkNode && !focusLinkNode.is(linkNode)) ||
316
- (linkNode && !linkNode.is(focusLinkNode)) ||
317
- (focusAutoLinkNode && !focusAutoLinkNode.is(autoLinkNode)) ||
318
- (autoLinkNode &&
319
- (!autoLinkNode.is(focusAutoLinkNode) ||
320
- autoLinkNode.getIsUnlinked()))
321
- );
322
- });
323
- if (!badNode) {
324
- setIsLink(true);
325
- } else {
326
- setIsLink(false);
327
- }
328
- }
329
- }
330
- return mergeRegister(
331
- editor.registerUpdateListener(({editorState}) => {
332
- editorState.read(() => {
333
- $updateToolbar();
334
- });
335
- }),
336
- editor.registerCommand(
337
- SELECTION_CHANGE_COMMAND,
338
- (_payload, newEditor) => {
339
- $updateToolbar();
340
- setActiveEditor(newEditor);
341
- return false;
342
- },
343
- COMMAND_PRIORITY_CRITICAL,
344
- ),
345
- editor.registerCommand(
346
- CLICK_COMMAND,
347
- (payload) => {
348
- const selection = $getSelection();
349
- if ($isRangeSelection(selection)) {
350
- const node = getSelectedNode(selection);
351
- const linkNode = $findMatchingParent(node, $isLinkNode);
352
- if ($isLinkNode(linkNode) && (payload.metaKey || payload.ctrlKey)) {
353
- window.open(linkNode.getURL(), '_blank');
354
- return true;
279
+ editor: LexicalEditor,
280
+ anchorElem: HTMLElement,
281
+ isLinkEditMode: boolean,
282
+ setIsLinkEditMode: Dispatch<boolean>,
283
+ ): React.JSX.Element | null {
284
+ const [activeEditor, setActiveEditor] = useState(editor);
285
+ const [isLink, setIsLink] = useState(false);
286
+
287
+ useEffect(() => {
288
+ function $updateToolbar() {
289
+ const selection = $getSelection();
290
+ if ($isRangeSelection(selection)) {
291
+ const focusNode = getSelectedNode(selection);
292
+ const focusLinkNode = $findMatchingParent(focusNode, $isLinkNode);
293
+ const focusAutoLinkNode = $findMatchingParent(
294
+ focusNode,
295
+ $isAutoLinkNode,
296
+ );
297
+ if (!(focusLinkNode || focusAutoLinkNode)) {
298
+ setIsLink(false);
299
+ return;
300
+ }
301
+ const badNode = selection
302
+ .getNodes()
303
+ .filter((node) => !$isLineBreakNode(node))
304
+ .find((node) => {
305
+ const linkNode = $findMatchingParent(node, $isLinkNode);
306
+ const autoLinkNode = $findMatchingParent(node, $isAutoLinkNode);
307
+ return (
308
+ (focusLinkNode && !focusLinkNode.is(linkNode)) ||
309
+ (linkNode && !linkNode.is(focusLinkNode)) ||
310
+ (focusAutoLinkNode && !focusAutoLinkNode.is(autoLinkNode)) ||
311
+ (autoLinkNode &&
312
+ (!autoLinkNode.is(focusAutoLinkNode) ||
313
+ autoLinkNode.getIsUnlinked()))
314
+ );
315
+ });
316
+ if (!badNode) {
317
+ setIsLink(true);
318
+ } else {
319
+ setIsLink(false);
320
+ }
355
321
  }
356
- }
357
- return false;
358
- },
359
- COMMAND_PRIORITY_LOW,
360
- ),
322
+ }
323
+ return mergeRegister(
324
+ editor.registerUpdateListener(({ editorState }) => {
325
+ editorState.read(() => {
326
+ $updateToolbar();
327
+ });
328
+ }),
329
+ editor.registerCommand(
330
+ SELECTION_CHANGE_COMMAND,
331
+ (_payload, newEditor) => {
332
+ $updateToolbar();
333
+ setActiveEditor(newEditor);
334
+ return false;
335
+ },
336
+ COMMAND_PRIORITY_CRITICAL,
337
+ ),
338
+ editor.registerCommand(
339
+ CLICK_COMMAND,
340
+ (payload) => {
341
+ const selection = $getSelection();
342
+ if ($isRangeSelection(selection)) {
343
+ const node = getSelectedNode(selection);
344
+ const linkNode = $findMatchingParent(node, $isLinkNode);
345
+ if ($isLinkNode(linkNode) && (payload.metaKey || payload.ctrlKey)) {
346
+ window.open(linkNode.getURL(), '_blank');
347
+ return true;
348
+ }
349
+ }
350
+ return false;
351
+ },
352
+ COMMAND_PRIORITY_LOW,
353
+ ),
354
+ );
355
+ }, [editor]);
356
+
357
+ return createPortal(
358
+ <FloatingLinkEditor
359
+ editor={activeEditor}
360
+ isLink={isLink}
361
+ anchorElem={anchorElem}
362
+ setIsLink={setIsLink}
363
+ isLinkEditMode={isLinkEditMode}
364
+ setIsLinkEditMode={setIsLinkEditMode}
365
+ />,
366
+ anchorElem,
361
367
  );
362
- }, [editor]);
363
-
364
- return createPortal(
365
- <FloatingLinkEditor
366
- editor={activeEditor}
367
- isLink={isLink}
368
- anchorElem={anchorElem}
369
- setIsLink={setIsLink}
370
- isLinkEditMode={isLinkEditMode}
371
- setIsLinkEditMode={setIsLinkEditMode}
372
- />,
373
- anchorElem,
374
- );
375
368
  }
376
369
 
377
370
  export default function FloatingLinkEditorPlugin({
378
- anchorElem = document.body,
379
- isLinkEditMode,
380
- setIsLinkEditMode,
381
- }: {
382
- anchorElem?: HTMLElement;
383
- isLinkEditMode: boolean;
384
- setIsLinkEditMode: Dispatch<boolean>;
385
- }): JSX.Element | null {
386
- const [editor] = useLexicalComposerContext();
387
- return useFloatingLinkEditorToolbar(
388
- editor,
389
- anchorElem,
371
+ anchorElem = document.body,
390
372
  isLinkEditMode,
391
373
  setIsLinkEditMode,
392
- );
374
+ }: {
375
+ anchorElem?: HTMLElement;
376
+ isLinkEditMode: boolean;
377
+ setIsLinkEditMode: Dispatch<boolean>;
378
+ }): React.JSX.Element | null {
379
+ const [editor] = useLexicalComposerContext();
380
+ return useFloatingLinkEditorToolbar(
381
+ editor,
382
+ anchorElem,
383
+ isLinkEditMode,
384
+ setIsLinkEditMode,
385
+ );
393
386
  }