@collabchron/notiq 0.2.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.
Files changed (188) hide show
  1. package/README.md +71 -0
  2. package/components.json +21 -0
  3. package/eslint.config.mjs +16 -0
  4. package/next.config.ts +12 -0
  5. package/package.json +108 -0
  6. package/postcss.config.mjs +5 -0
  7. package/public/file.svg +1 -0
  8. package/public/globe.svg +1 -0
  9. package/public/images/icons/plus.svg +10 -0
  10. package/public/next.svg +1 -0
  11. package/public/vercel.svg +1 -0
  12. package/public/window.svg +1 -0
  13. package/src/app/actions.ts +2 -0
  14. package/src/app/api/ai/route.ts +175 -0
  15. package/src/app/api/edgestore/[...edgestore]/route.ts +28 -0
  16. package/src/app/favicon.ico +0 -0
  17. package/src/app/globals.css +205 -0
  18. package/src/app/layout.tsx +38 -0
  19. package/src/app/page.tsx +12 -0
  20. package/src/components/editor/Core.tsx +220 -0
  21. package/src/components/editor/hooks/instructions-messages.ts +300 -0
  22. package/src/components/editor/hooks/use-mobile.ts +19 -0
  23. package/src/components/editor/hooks/useReport.ts +67 -0
  24. package/src/components/editor/hooks/useResizeObservert.ts +22 -0
  25. package/src/components/editor/index.tsx +39 -0
  26. package/src/components/editor/lexical-on-change.tsx +28 -0
  27. package/src/components/editor/nodes/CollapsibleNode/CollapsibleContainerNode.ts +92 -0
  28. package/src/components/editor/nodes/CollapsibleNode/CollapsibleContentNode.ts +65 -0
  29. package/src/components/editor/nodes/CollapsibleNode/CollapsibleTitleNode.ts +105 -0
  30. package/src/components/editor/nodes/EquationNode/EquationComponent.tsx +143 -0
  31. package/src/components/editor/nodes/EquationNode/EquationNode.tsx +170 -0
  32. package/src/components/editor/nodes/ExcalidrawNode/ExcalidrawComponent.tsx +228 -0
  33. package/src/components/editor/nodes/ExcalidrawNode/ExcalidrawImage.tsx +137 -0
  34. package/src/components/editor/nodes/ExcalidrawNode/ImageResizer.tsx +317 -0
  35. package/src/components/editor/nodes/ExcalidrawNode/index.tsx +204 -0
  36. package/src/components/editor/nodes/FigmaNode/FigmaNode.tsx +134 -0
  37. package/src/components/editor/nodes/Hint/HintComponet.tsx +221 -0
  38. package/src/components/editor/nodes/Hint/index.tsx +190 -0
  39. package/src/components/editor/nodes/ImageNode/index.tsx +328 -0
  40. package/src/components/editor/nodes/InlineImageNode/InlineImageComponent.tsx +383 -0
  41. package/src/components/editor/nodes/InlineImageNode/InlineImageNode.css +94 -0
  42. package/src/components/editor/nodes/InlineImageNode/InlineImageNode.tsx +309 -0
  43. package/src/components/editor/nodes/LayoutNode/LayoutContainerNode.ts +146 -0
  44. package/src/components/editor/nodes/LayoutNode/LayoutItemNode.ts +79 -0
  45. package/src/components/editor/nodes/PollNode/index.tsx +204 -0
  46. package/src/components/editor/nodes/Stepper/index.tsx +260 -0
  47. package/src/components/editor/nodes/TweetNode/index.tsx +214 -0
  48. package/src/components/editor/nodes/index.ts +81 -0
  49. package/src/components/editor/plugins/AutoEmbedPlugin/index.tsx +350 -0
  50. package/src/components/editor/plugins/AutoLinkPlugin/index.tsx +56 -0
  51. package/src/components/editor/plugins/CodeActionMenuPlugin/components/CopyButton.tsx +70 -0
  52. package/src/components/editor/plugins/CodeActionMenuPlugin/components/PrettierButton.tsx +192 -0
  53. package/src/components/editor/plugins/CodeActionMenuPlugin/index.tsx +217 -0
  54. package/src/components/editor/plugins/CodeActionMenuPlugin/utils.ts +26 -0
  55. package/src/components/editor/plugins/CodeHighlightPlugin/index.ts +21 -0
  56. package/src/components/editor/plugins/CollapsiblePlugin/Collapsible.css +76 -0
  57. package/src/components/editor/plugins/CollapsiblePlugin/index.ts +228 -0
  58. package/src/components/editor/plugins/DragDropPastePlugin/index.tsx +44 -0
  59. package/src/components/editor/plugins/DraggableBlockPlugin/index.tsx +52 -0
  60. package/src/components/editor/plugins/EquationsPlugin/index.tsx +85 -0
  61. package/src/components/editor/plugins/ExcalidrawPlugin/index.tsx +98 -0
  62. package/src/components/editor/plugins/FigmaPlugin/index.tsx +42 -0
  63. package/src/components/editor/plugins/FloatingLinkEditorPlugin/index.tsx +445 -0
  64. package/src/components/editor/plugins/FloatingTextFormatToolbarPlugin/index.tsx +275 -0
  65. package/src/components/editor/plugins/ImagesPlugin/index.tsx +222 -0
  66. package/src/components/editor/plugins/InlineImagePlugin/index.tsx +351 -0
  67. package/src/components/editor/plugins/LayoutPlugin/index.tsx +238 -0
  68. package/src/components/editor/plugins/LinkPlugin/index.tsx +36 -0
  69. package/src/components/editor/plugins/LinkWithMetaData/index.tsx +271 -0
  70. package/src/components/editor/plugins/MarkdownShortcutPlugin/index.tsx +11 -0
  71. package/src/components/editor/plugins/MarkdownTransformers/index.tsx +304 -0
  72. package/src/components/editor/plugins/PollPlugin/index.tsx +49 -0
  73. package/src/components/editor/plugins/ShortcutsPlugin/index.tsx +180 -0
  74. package/src/components/editor/plugins/ShortcutsPlugin/shortcuts.ts +253 -0
  75. package/src/components/editor/plugins/SlashCommand/index.tsx +621 -0
  76. package/src/components/editor/plugins/SpeechToTextPlugin/index.ts +127 -0
  77. package/src/components/editor/plugins/TabFocusPlugin/index.ts +58 -0
  78. package/src/components/editor/plugins/TableCellActionMenuPlugin/index.tsx +759 -0
  79. package/src/components/editor/plugins/TableCellResizer/index.tsx +438 -0
  80. package/src/components/editor/plugins/TableHoverActionsPlugin/index.tsx +314 -0
  81. package/src/components/editor/plugins/TablePlugin/index.tsx +99 -0
  82. package/src/components/editor/plugins/ToolbarPlugin/index.tsx +522 -0
  83. package/src/components/editor/plugins/TwitterPlugin/index.ts +35 -0
  84. package/src/components/editor/plugins/YouTubeNode/index.tsx +179 -0
  85. package/src/components/editor/plugins/YouTubePlugin/index.ts +41 -0
  86. package/src/components/editor/themes/editor-theme.ts +113 -0
  87. package/src/components/editor/themes/theme.css +377 -0
  88. package/src/components/editor/utils/ai.ts +291 -0
  89. package/src/components/editor/utils/canUseDOM.ts +12 -0
  90. package/src/components/editor/utils/editorFormatting.ts +282 -0
  91. package/src/components/editor/utils/environment.ts +50 -0
  92. package/src/components/editor/utils/extract-data.ts +166 -0
  93. package/src/components/editor/utils/getAllLexicalChildren.ts +13 -0
  94. package/src/components/editor/utils/getDOMRangeRect.ts +27 -0
  95. package/src/components/editor/utils/getSelectedNode.ts +27 -0
  96. package/src/components/editor/utils/gif.ts +29 -0
  97. package/src/components/editor/utils/invariant.ts +15 -0
  98. package/src/components/editor/utils/setFloatingElemPosition.ts +51 -0
  99. package/src/components/editor/utils/setFloatingElemPositionForLinkEditor.ts +40 -0
  100. package/src/components/editor/utils/setNodePlaceholderFromSelection/getNodePlaceholder.ts +51 -0
  101. package/src/components/editor/utils/setNodePlaceholderFromSelection/setNodePlaceholderFromSelection.ts +15 -0
  102. package/src/components/editor/utils/setNodePlaceholderFromSelection/setPlaceholderOnSelection.ts +114 -0
  103. package/src/components/editor/utils/setNodePlaceholderFromSelection/styles.css +6 -0
  104. package/src/components/editor/utils/url.ts +109 -0
  105. package/src/components/editor/utils/useLayoutEffect.ts +13 -0
  106. package/src/components/providers/QueryProvider.tsx +15 -0
  107. package/src/components/providers/SharedHistoryContext.tsx +28 -0
  108. package/src/components/providers/ToolbarContext.tsx +123 -0
  109. package/src/components/providers/theme-provider.tsx +11 -0
  110. package/src/components/theme/ModeToggle.tsx +40 -0
  111. package/src/components/ui/FileInput.tsx +40 -0
  112. package/src/components/ui/Input.css +32 -0
  113. package/src/components/ui/Select.css +42 -0
  114. package/src/components/ui/Select.tsx +36 -0
  115. package/src/components/ui/TextInput.tsx +48 -0
  116. package/src/components/ui/ai/ai-button.tsx +574 -0
  117. package/src/components/ui/ai/border.tsx +99 -0
  118. package/src/components/ui/ai/placeholder-input-vanish.tsx +282 -0
  119. package/src/components/ui/button.tsx +89 -0
  120. package/src/components/ui/card.tsx +76 -0
  121. package/src/components/ui/checkbox.tsx +30 -0
  122. package/src/components/ui/command.tsx +153 -0
  123. package/src/components/ui/dialog/Dialog.css +25 -0
  124. package/src/components/ui/dialog/Dialog.tsx +34 -0
  125. package/src/components/ui/dialog.tsx +122 -0
  126. package/src/components/ui/drop-downs/background-color.tsx +183 -0
  127. package/src/components/ui/drop-downs/block-format.tsx +159 -0
  128. package/src/components/ui/drop-downs/code.tsx +42 -0
  129. package/src/components/ui/drop-downs/color.tsx +177 -0
  130. package/src/components/ui/drop-downs/font-size.tsx +138 -0
  131. package/src/components/ui/drop-downs/font.tsx +155 -0
  132. package/src/components/ui/drop-downs/index.tsx +122 -0
  133. package/src/components/ui/drop-downs/insert-node.tsx +213 -0
  134. package/src/components/ui/drop-downs/text-align.tsx +123 -0
  135. package/src/components/ui/drop-downs/text-format.tsx +104 -0
  136. package/src/components/ui/dropdown-menu.tsx +201 -0
  137. package/src/components/ui/equation/EquationEditor.css +38 -0
  138. package/src/components/ui/equation/EquationEditor.tsx +56 -0
  139. package/src/components/ui/equation/KatexEquationAlterer.css +41 -0
  140. package/src/components/ui/equation/KatexEquationAlterer.tsx +83 -0
  141. package/src/components/ui/equation/KatexRenderer.tsx +66 -0
  142. package/src/components/ui/excalidraw/ExcalidrawModal.css +64 -0
  143. package/src/components/ui/excalidraw/ExcalidrawModal.tsx +234 -0
  144. package/src/components/ui/excalidraw/Modal.css +62 -0
  145. package/src/components/ui/excalidraw/Modal.tsx +110 -0
  146. package/src/components/ui/hover-card.tsx +29 -0
  147. package/src/components/ui/image/error-image.tsx +17 -0
  148. package/src/components/ui/image/file-upload.tsx +240 -0
  149. package/src/components/ui/image/image-resizer.tsx +297 -0
  150. package/src/components/ui/image/image-toolbar.tsx +264 -0
  151. package/src/components/ui/image/index.tsx +408 -0
  152. package/src/components/ui/image/lazy-image.tsx +68 -0
  153. package/src/components/ui/image/lazy-video.tsx +71 -0
  154. package/src/components/ui/input.tsx +22 -0
  155. package/src/components/ui/models/custom-dialog.tsx +320 -0
  156. package/src/components/ui/models/insert-gif.tsx +90 -0
  157. package/src/components/ui/models/insert-image.tsx +52 -0
  158. package/src/components/ui/models/insert-poll.tsx +29 -0
  159. package/src/components/ui/models/insert-table.tsx +62 -0
  160. package/src/components/ui/models/use-model.tsx +91 -0
  161. package/src/components/ui/poll/poll-component.tsx +304 -0
  162. package/src/components/ui/popover.tsx +33 -0
  163. package/src/components/ui/progress.tsx +28 -0
  164. package/src/components/ui/scroll-area.tsx +48 -0
  165. package/src/components/ui/separator.tsx +31 -0
  166. package/src/components/ui/skeleton.tsx +15 -0
  167. package/src/components/ui/sonner.tsx +31 -0
  168. package/src/components/ui/stepper/step.tsx +179 -0
  169. package/src/components/ui/stepper/stepper.tsx +89 -0
  170. package/src/components/ui/textarea.tsx +22 -0
  171. package/src/components/ui/toggle.tsx +71 -0
  172. package/src/components/ui/tooltip.tsx +32 -0
  173. package/src/components/ui/write/text-format-floting-toolbar.tsx +346 -0
  174. package/src/lib/edgestore.ts +9 -0
  175. package/src/lib/pinecone-client.ts +0 -0
  176. package/src/lib/utils.ts +6 -0
  177. package/src/utils/docSerialization.ts +77 -0
  178. package/src/utils/emoji-list.ts +16615 -0
  179. package/src/utils/getDOMRangeRect.ts +27 -0
  180. package/src/utils/getSelectedNode.ts +27 -0
  181. package/src/utils/getThemeSelector.ts +25 -0
  182. package/src/utils/isMobileWidth.ts +7 -0
  183. package/src/utils/joinClasses.ts +13 -0
  184. package/src/utils/setFloatingElemPosition.ts +74 -0
  185. package/src/utils/setFloatingElemPositionForLinkEditor.ts +46 -0
  186. package/src/utils/swipe.ts +127 -0
  187. package/src/utils/url.ts +38 -0
  188. package/tsconfig.json +27 -0
@@ -0,0 +1,445 @@
1
+ import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
2
+ import { Dispatch, JSX, useCallback, useEffect, useRef, useState } from "react";
3
+ import { createPortal } from "react-dom";
4
+ import { getSelectedNode } from "../../utils/getSelectedNode";
5
+ import {
6
+ $createLinkNode,
7
+ $isAutoLinkNode,
8
+ $isLinkNode,
9
+ TOGGLE_LINK_COMMAND,
10
+ } from "@lexical/link";
11
+ import { $findMatchingParent, mergeRegister } from "@lexical/utils";
12
+ import {
13
+ $getSelection,
14
+ $isLineBreakNode,
15
+ $isRangeSelection,
16
+ BaseSelection,
17
+ CLICK_COMMAND,
18
+ COMMAND_PRIORITY_CRITICAL,
19
+ COMMAND_PRIORITY_HIGH,
20
+ COMMAND_PRIORITY_LOW,
21
+ getDOMSelection,
22
+ KEY_ESCAPE_COMMAND,
23
+ LexicalEditor,
24
+ SELECTION_CHANGE_COMMAND,
25
+ } from "lexical";
26
+ import { sanitizeUrl } from "../../utils/url";
27
+ import { setFloatingElemPositionForLinkEditor } from "../../utils/setFloatingElemPositionForLinkEditor";
28
+ import { Input } from "@/components/ui/input";
29
+ import { Check, Edit2Icon, Link, X } from "lucide-react";
30
+ import { cn } from "@/lib/utils";
31
+ import { Button } from "@/components/ui/button";
32
+
33
+ function preventDefault(
34
+ event: React.KeyboardEvent<HTMLInputElement> | React.MouseEvent<HTMLElement>
35
+ ): void {
36
+ event.preventDefault();
37
+ }
38
+
39
+ function FloatingLinkEditor({
40
+ editor,
41
+ isLink,
42
+ setIsLink,
43
+ anchorElem,
44
+ isLinkEditMode,
45
+ setIsLinkEditMode,
46
+ }: {
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 [urlLogo, setUrlLogo] = useState<string | null>(null);
63
+
64
+ const $updateLinkEditor = useCallback(() => {
65
+ const selection = $getSelection();
66
+ if ($isRangeSelection(selection)) {
67
+ const node = getSelectedNode(selection);
68
+ const linkParent = $findMatchingParent(node, $isLinkNode);
69
+
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 = getDOMSelection(editor._window);
83
+ const activeElement = document.activeElement;
84
+
85
+ if (editorElem === null) {
86
+ return;
87
+ }
88
+
89
+ const rootElement = editor.getRootElement();
90
+
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);
108
+ }
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();
174
+ });
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
+ handleLinkSubmission(event);
188
+ } else if (event.key === "Escape") {
189
+ event.preventDefault();
190
+ setIsLinkEditMode(false);
191
+ }
192
+ };
193
+
194
+ const handleLinkSubmission = (
195
+ event: React.KeyboardEvent<HTMLInputElement> | React.MouseEvent<HTMLElement>
196
+ ) => {
197
+ event.preventDefault();
198
+
199
+ if (lastSelection !== null) {
200
+ if (linkUrl !== "") {
201
+ editor.update( () => {
202
+ editor.dispatchCommand(
203
+ TOGGLE_LINK_COMMAND,
204
+ sanitizeUrl(editedLinkUrl)
205
+ );
206
+
207
+ const selection = $getSelection();
208
+ if ($isRangeSelection(selection)) {
209
+ const parent = getSelectedNode(selection).getParent();
210
+ if ($isAutoLinkNode(parent)) {
211
+ const linkNode = $createLinkNode(parent.getURL(), {
212
+ rel: parent.__rel,
213
+ target: parent.__target,
214
+ title: parent.__title,
215
+ });
216
+ parent.replace(linkNode, true);
217
+ }
218
+ }
219
+ });
220
+ }
221
+ // Fetch metadata asynchronously AFTER editor update
222
+ setEditedLinkUrl("https://");
223
+
224
+ setIsLinkEditMode(false);
225
+ }
226
+ };
227
+
228
+ return (
229
+ <div
230
+ ref={editorRef}
231
+ className=" absolute max-w-96 bg-background max-sm:mx-1 w-full border-0 top-0 left-0"
232
+ >
233
+ {isLinkEditMode ? (
234
+ <div className=" relative w-full">
235
+ <Link
236
+ className={cn(
237
+ "size-[14px] text-muted-foreground absolute transition-colors duration-500 transform -translate-x-1/2 -translate-y-1/2 left-4 top-1/2",
238
+ !!editedLinkUrl && "text-black dark:text-white"
239
+ )}
240
+ />
241
+ <Input
242
+ placeholder="https://"
243
+ value={editedLinkUrl}
244
+ onChange={(event) => {
245
+ setEditedLinkUrl(event.target.value);
246
+ }}
247
+ onKeyDown={(event) => {
248
+ monitorInputInteraction(event);
249
+ }}
250
+ ref={inputRef}
251
+ className="text-gray-900 pl-7 dark:text-gray-100 rounded-xl placeholder-gray-400 dark:placeholder-gray-500"
252
+ />
253
+ <div className=" absolute bg-background rounded-xl flex flex-row gap-x-1 items-center transition-colors duration-500 transform -translate-y-1/2 right-2 top-1/2">
254
+ <Button
255
+ className=" rounded-xl h-6 w-6 flex items-center"
256
+ role="button"
257
+ variant={"destructive"}
258
+ size={"Toolbar"}
259
+ tip="delete link"
260
+ tabIndex={0}
261
+ onMouseDown={preventDefault}
262
+ onClick={() => {
263
+ setIsLinkEditMode(false);
264
+ }}
265
+ >
266
+ <X />
267
+ </Button>
268
+ <Button
269
+ className=" rounded-xl h-6 w-6 flex items-center"
270
+ role="button"
271
+ tabIndex={0}
272
+ tip="confirm link"
273
+ onMouseDown={preventDefault}
274
+ onClick={handleLinkSubmission}
275
+ size={"Toolbar"}
276
+ >
277
+ <Check />
278
+ </Button>
279
+ </div>
280
+ </div>
281
+ ) : (
282
+ <div className="w-full border-input border rounded-xl h-9 flex items-center justify-between px-3 py-1 text-base shadow-sm ">
283
+ <Link
284
+ className={cn(
285
+ "size-[14px] text-muted-foreground absolute transition-colors duration-500 transform -translate-x-1/2 -translate-y-1/2 left-4 top-1/2",
286
+ !!editedLinkUrl && "text-black dark:text-white"
287
+ )}
288
+ />
289
+ <a
290
+ href={sanitizeUrl(linkUrl)}
291
+ target="_blank"
292
+ rel="noopener noreferrer"
293
+ className="pl-4 mb-[1px]"
294
+ >
295
+ {linkUrl}
296
+ </a>
297
+ <div className=" absolute bg-background rounded-xl flex flex-row gap-x-1 items-center transition-colors duration-500 transform -translate-y-1/2 right-2 top-1/2">
298
+ <Button
299
+ className=" rounded-xl z-50 h-6 w-6 flex items-center"
300
+ role="button"
301
+ size={"Toolbar"}
302
+ tip="edit link"
303
+ tabIndex={0}
304
+ onMouseDown={preventDefault}
305
+ onClick={(event) => {
306
+ event.preventDefault();
307
+ setEditedLinkUrl(linkUrl);
308
+ setIsLinkEditMode(true);
309
+ }}
310
+ >
311
+ <Edit2Icon className="size-4" />
312
+ </Button>
313
+ <Button
314
+ className=" rounded-xl h-6 w-6 flex items-center"
315
+ role="button"
316
+ tabIndex={0}
317
+ variant={"secondary"}
318
+ tip="delet link"
319
+ size={"Toolbar"}
320
+ onMouseDown={preventDefault}
321
+ onClick={() => {
322
+ editor.dispatchCommand(TOGGLE_LINK_COMMAND, null);
323
+ }}
324
+ >
325
+ <X />
326
+ </Button>
327
+ </div>
328
+ </div>
329
+ )}
330
+ </div>
331
+ );
332
+ }
333
+ function useFloatingLinkEditorToolbar(
334
+ editor: LexicalEditor,
335
+ anchorElem: HTMLElement,
336
+ isLinkEditMode: boolean,
337
+ setIsLinkEditMode: Dispatch<boolean>
338
+ ): JSX.Element | null {
339
+ const [activeEditor, setActiveEditor] = useState(editor);
340
+ const [isLink, setIsLink] = useState(false);
341
+
342
+ useEffect(() => {
343
+ function $updateToolbar() {
344
+ const selection = $getSelection();
345
+ if ($isRangeSelection(selection)) {
346
+ const focusNode = getSelectedNode(selection);
347
+ const focusLinkNode = $findMatchingParent(focusNode, $isLinkNode);
348
+ const focusAutoLinkNode = $findMatchingParent(
349
+ focusNode,
350
+ $isAutoLinkNode
351
+ );
352
+ if (!(focusLinkNode || focusAutoLinkNode)) {
353
+ setIsLink(false);
354
+ return;
355
+ }
356
+ const badNode = selection
357
+ .getNodes()
358
+ .filter((node) => !$isLineBreakNode(node))
359
+ .find((node) => {
360
+ const linkNode = $findMatchingParent(node, $isLinkNode);
361
+ const autoLinkNode = $findMatchingParent(node, $isAutoLinkNode);
362
+ return (
363
+ (focusLinkNode && !focusLinkNode.is(linkNode)) ||
364
+ (linkNode && !linkNode.is(focusLinkNode)) ||
365
+ (focusAutoLinkNode && !focusAutoLinkNode.is(autoLinkNode)) ||
366
+ (autoLinkNode &&
367
+ (!autoLinkNode.is(focusAutoLinkNode) ||
368
+ autoLinkNode.getIsUnlinked()))
369
+ );
370
+ });
371
+ if (!badNode) {
372
+ setIsLink(true);
373
+ } else {
374
+ setIsLink(false);
375
+ }
376
+ }
377
+ }
378
+ return mergeRegister(
379
+ editor.registerUpdateListener(({ editorState }) => {
380
+ editorState.read(() => {
381
+ $updateToolbar();
382
+ });
383
+ }),
384
+ editor.registerCommand(
385
+ SELECTION_CHANGE_COMMAND,
386
+ (_payload, newEditor) => {
387
+ $updateToolbar();
388
+ setActiveEditor(newEditor);
389
+ return false;
390
+ },
391
+ COMMAND_PRIORITY_CRITICAL
392
+ ),
393
+ editor.registerCommand(
394
+ CLICK_COMMAND,
395
+ (payload) => {
396
+ const selection = $getSelection();
397
+ if ($isRangeSelection(selection)) {
398
+ const node = getSelectedNode(selection);
399
+ const linkNode = $findMatchingParent(node, $isLinkNode);
400
+ if ($isLinkNode(linkNode) && (payload.metaKey || payload.ctrlKey)) {
401
+ window.open(linkNode.getURL(), "_blank");
402
+ return true;
403
+ }
404
+ }
405
+ return false;
406
+ },
407
+ COMMAND_PRIORITY_LOW
408
+ )
409
+ );
410
+ }, [editor]);
411
+
412
+ if (!isLink) return null;
413
+
414
+ return createPortal(
415
+ <FloatingLinkEditor
416
+ editor={activeEditor}
417
+ isLink={isLink}
418
+ anchorElem={anchorElem}
419
+ setIsLink={setIsLink}
420
+ isLinkEditMode={isLinkEditMode}
421
+ setIsLinkEditMode={setIsLinkEditMode}
422
+ />,
423
+ anchorElem
424
+ );
425
+ }
426
+
427
+ export default function FloatingLinkEditorPlugin({
428
+ anchorElem = document.body,
429
+ isLinkEditMode,
430
+ setIsLinkEditMode,
431
+ }: {
432
+ anchorElem?: HTMLElement;
433
+ isLinkEditMode: boolean;
434
+ setIsLinkEditMode: Dispatch<boolean>;
435
+ }): JSX.Element | null {
436
+ const [editor] = useLexicalComposerContext();
437
+
438
+
439
+ return useFloatingLinkEditorToolbar(
440
+ editor,
441
+ anchorElem,
442
+ isLinkEditMode,
443
+ setIsLinkEditMode
444
+ );
445
+ }