@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,81 @@
1
+
2
+ import type {Klass, LexicalNode} from 'lexical';
3
+
4
+ import {CodeHighlightNode, CodeNode} from '@lexical/code';
5
+ import {HashtagNode} from '@lexical/hashtag';
6
+ import {AutoLinkNode, LinkNode} from '@lexical/link';
7
+ import {ListItemNode, ListNode} from '@lexical/list';
8
+ import {MarkNode} from '@lexical/mark';
9
+ import {OverflowNode} from '@lexical/overflow';
10
+ import {HorizontalRuleNode} from '@lexical/react/LexicalHorizontalRuleNode';
11
+ import {HeadingNode, QuoteNode} from '@lexical/rich-text';
12
+ import {TableCellNode, TableNode, TableRowNode} from '@lexical/table';
13
+ import { ImageNode } from './ImageNode';
14
+ import { PollNode } from './PollNode';
15
+ import { LayoutItemNode } from './LayoutNode/LayoutItemNode';
16
+ import { LayoutContainerNode } from './LayoutNode/LayoutContainerNode';
17
+ import { CollapsibleContainerNode } from './CollapsibleNode/CollapsibleContainerNode';
18
+ import { CollapsibleContentNode } from './CollapsibleNode/CollapsibleContentNode';
19
+ import { CollapsibleTitleNode } from './CollapsibleNode/CollapsibleTitleNode';
20
+ import { LinkWithMetaDataNode } from '../plugins/LinkWithMetaData';
21
+ import { TweetNode } from './TweetNode';
22
+ import { YouTubeNode } from '../plugins/YouTubeNode';
23
+ import { Hint } from './Hint';
24
+ import { StepperNode } from './Stepper';
25
+ import { InlineImageNode } from './InlineImageNode/InlineImageNode';
26
+ import { ExcalidrawNode } from './ExcalidrawNode';
27
+ import { FigmaNode } from './FigmaNode/FigmaNode';
28
+ import { EquationNode } from './EquationNode/EquationNode';
29
+
30
+
31
+
32
+ const nodes: Array<Klass<LexicalNode>> = [
33
+ HeadingNode,
34
+ ListNode,
35
+ ListItemNode,
36
+ QuoteNode,
37
+ CodeNode,
38
+ TableNode,
39
+ TableCellNode,
40
+ TableRowNode,
41
+ CodeHighlightNode,
42
+ StepperNode,
43
+ AutoLinkNode,
44
+ YouTubeNode,
45
+ Hint,
46
+ TweetNode,
47
+ LinkNode,
48
+ OverflowNode,
49
+ HorizontalRuleNode,
50
+ MarkNode,
51
+ ImageNode,
52
+ PollNode,
53
+ LayoutItemNode,
54
+ LayoutContainerNode,
55
+ CollapsibleContainerNode,
56
+ CollapsibleContentNode,
57
+ CollapsibleTitleNode,
58
+ LinkWithMetaDataNode,
59
+ InlineImageNode,
60
+ ExcalidrawNode,
61
+ FigmaNode,
62
+ EquationNode,
63
+ ];
64
+
65
+ export const nestedNodes: Array<Klass<LexicalNode>>=[
66
+ HeadingNode,
67
+ ListNode,
68
+ ListItemNode,
69
+ LinkNode,
70
+ CodeHighlightNode,
71
+ QuoteNode,
72
+ CodeNode,
73
+ StepperNode,
74
+ LinkWithMetaDataNode,
75
+ ImageNode,
76
+ InlineImageNode,
77
+ ExcalidrawNode,
78
+ FigmaNode,
79
+ EquationNode,
80
+ ]
81
+ export default nodes
@@ -0,0 +1,350 @@
1
+
2
+
3
+ import type {LexicalEditor} from 'lexical';
4
+
5
+ import {
6
+ AutoEmbedOption,
7
+ EmbedConfig,
8
+ EmbedMatchResult,
9
+ LexicalAutoEmbedPlugin,
10
+ URL_MATCHER,
11
+ } from '@lexical/react/LexicalAutoEmbedPlugin';
12
+ import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
13
+ import {useMemo, useState} from 'react';
14
+ import * as React from 'react';
15
+ import * as ReactDOM from 'react-dom';
16
+ import {INSERT_FIGMA_COMMAND} from '../FigmaPlugin';
17
+ import { INSERT_YOUTUBE_COMMAND } from '../YouTubePlugin';
18
+ import { INSERT_TWEET_COMMAND } from '../TwitterPlugin';
19
+ import { Button } from '@/components/ui/button';
20
+ import { Input } from '@/components/ui/input';
21
+ import { Twitter, Youtube, Figma } from 'lucide-react';
22
+ import useModal from '@/components/ui/models/use-model';
23
+
24
+ interface PlaygroundEmbedConfig extends EmbedConfig {
25
+ // Human readable name of the embeded content e.g. Tweet or Google Map.
26
+ contentName: string;
27
+
28
+ // Icon for display.
29
+ icon?: React.JSX.Element;
30
+
31
+ // An example of a matching url https://twitter.com/jack/status/20
32
+ exampleUrl: string;
33
+
34
+ // For extra searching.
35
+ keywords: Array<string>;
36
+
37
+ // Embed a Figma Project.
38
+ description?: string;
39
+ }
40
+
41
+ export const YoutubeEmbedConfig: PlaygroundEmbedConfig = {
42
+ contentName: 'Youtube Video',
43
+
44
+ exampleUrl: 'https://www.youtube.com/watch?v=jNQXAC9IVRw',
45
+
46
+ // Icon for display.
47
+ icon: <Youtube className="h-4 w-4" />,
48
+
49
+ insertNode: (editor: LexicalEditor, result: EmbedMatchResult) => {
50
+ editor.dispatchCommand(INSERT_YOUTUBE_COMMAND, result.id);
51
+ },
52
+
53
+ keywords: ['youtube', 'video'],
54
+
55
+ // Determine if a given URL is a match and return url data.
56
+ parseUrl: async (url: string) => {
57
+ const match =
58
+ /^.*(youtu\.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/.exec(url);
59
+
60
+ const id = match ? (match?.[2].length === 11 ? match[2] : null) : null;
61
+
62
+ if (id != null) {
63
+ return {
64
+ id,
65
+ url,
66
+ };
67
+ }
68
+
69
+ return null;
70
+ },
71
+
72
+ type: 'youtube-video',
73
+ };
74
+
75
+ export const TwitterEmbedConfig: PlaygroundEmbedConfig = {
76
+ // e.g. Tweet or Google Map.
77
+ contentName: 'Tweet',
78
+
79
+ exampleUrl: 'https://twitter.com/jack/status/20',
80
+
81
+ // Icon for display.
82
+ icon: <Twitter className="h-4 w-4" />,
83
+
84
+ // Create the Lexical embed node from the url data.
85
+ insertNode: (editor: LexicalEditor, result: EmbedMatchResult) => {
86
+ editor.dispatchCommand(INSERT_TWEET_COMMAND, result.id);
87
+ },
88
+
89
+ // For extra searching.
90
+ keywords: ['tweet', 'twitter'],
91
+
92
+ // Determine if a given URL is a match and return url data.
93
+ parseUrl: (text: string) => {
94
+ const match =
95
+ /^https:\/\/(twitter|x)\.com\/(#!\/)?(\w+)\/status(es)*\/(\d+)/.exec(
96
+ text,
97
+ );
98
+
99
+ if (match != null) {
100
+ return {
101
+ id: match[5],
102
+ url: match[1],
103
+ };
104
+ }
105
+
106
+ return null;
107
+ },
108
+
109
+ type: 'tweet',
110
+ };
111
+
112
+ export const FigmaEmbedConfig: PlaygroundEmbedConfig = {
113
+ contentName: 'Figma Document',
114
+
115
+ exampleUrl: 'https://www.figma.com/file/LKQ4FJ4bTnCSjedbRpk931/Sample-File',
116
+
117
+ icon: <Figma className="h-4 w-4" />,
118
+
119
+ insertNode: (editor: LexicalEditor, result: EmbedMatchResult) => {
120
+ editor.dispatchCommand(INSERT_FIGMA_COMMAND, result.id);
121
+ },
122
+
123
+ keywords: ['figma', 'figma.com', 'mock-up'],
124
+
125
+ // Determine if a given URL is a match and return url data.
126
+ parseUrl: (text: string) => {
127
+ const match =
128
+ /https:\/\/([\w.-]+\.)?figma.com\/(file|proto)\/([0-9a-zA-Z]{22,128})(?:\/.*)?$/.exec(
129
+ text,
130
+ );
131
+
132
+ if (match != null) {
133
+ return {
134
+ id: match[3],
135
+ url: match[0],
136
+ };
137
+ }
138
+
139
+ return null;
140
+ },
141
+
142
+ type: 'figma',
143
+ };
144
+
145
+ export const EmbedConfigs = [
146
+ TwitterEmbedConfig,
147
+ YoutubeEmbedConfig,
148
+ FigmaEmbedConfig,
149
+ ];
150
+
151
+ function AutoEmbedMenuItem({
152
+ index,
153
+ isSelected,
154
+ onClick,
155
+ onMouseEnter,
156
+ option,
157
+ }: {
158
+ index: number;
159
+ isSelected: boolean;
160
+ onClick: () => void;
161
+ onMouseEnter: () => void;
162
+ option: AutoEmbedOption;
163
+ }) {
164
+ let className = 'item';
165
+ if (isSelected) {
166
+ className += ' selected';
167
+ }
168
+ return (
169
+ <li
170
+ key={option.key}
171
+ tabIndex={-1}
172
+ className={className}
173
+ ref={option.setRefElement}
174
+ role="option"
175
+ aria-selected={isSelected}
176
+ id={'typeahead-item-' + index}
177
+ onMouseEnter={onMouseEnter}
178
+ onClick={onClick}>
179
+ <span className="text">{option.title}</span>
180
+ </li>
181
+ );
182
+ }
183
+
184
+ function AutoEmbedMenu({
185
+ options,
186
+ selectedItemIndex,
187
+ onOptionClick,
188
+ onOptionMouseEnter,
189
+ }: {
190
+ selectedItemIndex: number | null;
191
+ onOptionClick: (option: AutoEmbedOption, index: number) => void;
192
+ onOptionMouseEnter: (index: number) => void;
193
+ options: Array<AutoEmbedOption>;
194
+ }) {
195
+ return (
196
+ <div className="typeahead-popover">
197
+ <ul>
198
+ {options.map((option: AutoEmbedOption, i: number) => (
199
+ <AutoEmbedMenuItem
200
+ index={i}
201
+ isSelected={selectedItemIndex === i}
202
+ onClick={() => onOptionClick(option, i)}
203
+ onMouseEnter={() => onOptionMouseEnter(i)}
204
+ key={option.key}
205
+ option={option}
206
+ />
207
+ ))}
208
+ </ul>
209
+ </div>
210
+ );
211
+ }
212
+
213
+ const debounce = (callback: (text: string) => void, delay: number) => {
214
+ let timeoutId: number;
215
+ return (text: string) => {
216
+ window.clearTimeout(timeoutId);
217
+ timeoutId = window.setTimeout(() => {
218
+ callback(text);
219
+ }, delay);
220
+ };
221
+ };
222
+
223
+ export function AutoEmbedDialog({
224
+ embedConfig,
225
+ onClose,
226
+ }: {
227
+ embedConfig: PlaygroundEmbedConfig;
228
+ onClose: () => void;
229
+ }): React.JSX.Element {
230
+ const [text, setText] = useState('');
231
+ const [editor] = useLexicalComposerContext();
232
+ const [embedResult, setEmbedResult] = useState<EmbedMatchResult | null>(null);
233
+
234
+ const validateText = useMemo(
235
+ () =>
236
+ debounce((inputText: string) => {
237
+ const urlMatch = URL_MATCHER.exec(inputText);
238
+ if (embedConfig != null && inputText != null && urlMatch != null) {
239
+ Promise.resolve(embedConfig.parseUrl(inputText)).then(
240
+ (parseResult) => {
241
+ setEmbedResult(parseResult);
242
+ },
243
+ );
244
+ } else if (embedResult != null) {
245
+ setEmbedResult(null);
246
+ }
247
+ }, 200),
248
+ [embedConfig, embedResult],
249
+ );
250
+
251
+ const onClick = () => {
252
+ if (embedResult != null) {
253
+ embedConfig.insertNode(editor, embedResult);
254
+ onClose();
255
+ }
256
+ };
257
+
258
+ return (
259
+ <div className='flex items-center justify-center flex-col space-y-3'>
260
+ <div >
261
+ <Input
262
+ type="text"
263
+ className="Input__input"
264
+ placeholder={embedConfig.exampleUrl}
265
+ value={text}
266
+ data-test-id={`${embedConfig.type}-embed-modal-url`}
267
+ onChange={(e) => {
268
+ const {value} = e.target;
269
+ setText(value);
270
+ validateText(value);
271
+ }}
272
+ />
273
+ </div>
274
+ <Button
275
+ disabled={!embedResult}
276
+ onClick={onClick}
277
+ data-test-id={`${embedConfig.type}-embed-modal-submit-btn`}>
278
+ Embed
279
+ </Button>
280
+ </div>
281
+ );
282
+ }
283
+
284
+ export default function AutoEmbedPlugin(): React.JSX.Element {
285
+ const [modal, showModal] = useModal();
286
+
287
+ const openEmbedModal = (embedConfig: PlaygroundEmbedConfig) => {
288
+
289
+ showModal(`Embed ${embedConfig.contentName}`,"type the url", (onClose) => (
290
+ <AutoEmbedDialog embedConfig={embedConfig} onClose={onClose} />
291
+ ));
292
+ };
293
+
294
+ const getMenuOptions = (
295
+ activeEmbedConfig: PlaygroundEmbedConfig,
296
+ embedFn: () => void,
297
+ dismissFn: () => void,
298
+ ) => {
299
+ return [
300
+ new AutoEmbedOption('Dismiss', {
301
+ onSelect: dismissFn,
302
+ }),
303
+ new AutoEmbedOption(`Embed ${activeEmbedConfig.contentName}`, {
304
+ onSelect: embedFn,
305
+ }),
306
+ ];
307
+ };
308
+
309
+ return (
310
+ <>
311
+ {modal}
312
+ <LexicalAutoEmbedPlugin<PlaygroundEmbedConfig>
313
+ embedConfigs={EmbedConfigs}
314
+ onOpenEmbedModalForConfig={openEmbedModal}
315
+ getMenuOptions={getMenuOptions}
316
+ menuRenderFn={(
317
+ anchorElementRef,
318
+ {selectedIndex, options, selectOptionAndCleanUp, setHighlightedIndex},
319
+ ) =>
320
+ anchorElementRef.current
321
+ ? ReactDOM.createPortal(
322
+ <div
323
+ className="typeahead-popover flex items-center auto-embed-menu"
324
+ style={{
325
+ marginLeft: `${Math.max(
326
+ parseFloat(anchorElementRef.current.style.width) - 200,
327
+ 0,
328
+ )}px`,
329
+ width: 200,
330
+ }}>
331
+ <AutoEmbedMenu
332
+ options={options}
333
+ selectedItemIndex={selectedIndex}
334
+ onOptionClick={(option: AutoEmbedOption, index: number) => {
335
+ setHighlightedIndex(index);
336
+ selectOptionAndCleanUp(option);
337
+ }}
338
+ onOptionMouseEnter={(index: number) => {
339
+ setHighlightedIndex(index);
340
+ }}
341
+ />
342
+ </div>,
343
+ anchorElementRef.current,
344
+ )
345
+ : null
346
+ }
347
+ />
348
+ </>
349
+ );
350
+ }
@@ -0,0 +1,56 @@
1
+ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
2
+ import { useEffect } from 'react';
3
+ import { TOGGLE_LINK_COMMAND_LinkWithMetaDataNode } from '../LinkWithMetaData';
4
+ import { $getSelection, $isRangeSelection, } from 'lexical';
5
+ import { $isCodeHighlightNode } from "@lexical/code";
6
+
7
+ const URL_REGEX =
8
+ /((https?:\/\/(www\.)?)|(www\.))[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)(?<![-.+():%])/;
9
+
10
+ export default function LexicalAutoLinkPlugin(): null {
11
+ const [editor] = useLexicalComposerContext();
12
+ useEffect(() => {
13
+ const handlePaste = (event: ClipboardEvent) => {
14
+ const pastedText = event.clipboardData?.getData('text/plain');
15
+ if (pastedText && URL_REGEX.test(pastedText)) {
16
+
17
+
18
+ event.preventDefault();
19
+ pastedText.split(" ").map((TEXT)=>{
20
+ if(URL_REGEX.test(TEXT)){
21
+ editor.update(() => {
22
+ const selection = $getSelection();
23
+ if ($isRangeSelection(selection)) {
24
+ const node = selection.anchor.getNode();
25
+
26
+
27
+ if(!$isCodeHighlightNode(node)){
28
+ if (node && node.getTextContent() === TEXT) {
29
+ node.remove();
30
+ }
31
+
32
+ editor.dispatchCommand(TOGGLE_LINK_COMMAND_LinkWithMetaDataNode, TEXT);
33
+ }
34
+
35
+ }
36
+ });
37
+ }
38
+ })
39
+
40
+ }
41
+ };
42
+
43
+ const rootElement = editor.getRootElement();
44
+ if (rootElement) {
45
+ rootElement.addEventListener('paste', handlePaste);
46
+ }
47
+
48
+ return () => {
49
+ if (rootElement) {
50
+ rootElement.removeEventListener('paste', handlePaste);
51
+ }
52
+ };
53
+ }, [editor]);
54
+
55
+ return null;
56
+ }
@@ -0,0 +1,70 @@
1
+ import { $isCodeNode } from "@lexical/code";
2
+ import {
3
+ $getNearestNodeFromDOMNode,
4
+ $getSelection,
5
+ $setSelection,
6
+ LexicalEditor,
7
+ } from "lexical";
8
+ import * as React from "react";
9
+ import { useState } from "react";
10
+ import { Copy, Check } from "lucide-react";
11
+ import { Button } from "@/components/ui/button";
12
+
13
+ interface Props {
14
+ editor: LexicalEditor;
15
+ getCodeDOMNode: () => HTMLElement | null;
16
+ }
17
+
18
+ export function CopyButton({ editor, getCodeDOMNode }: Props) {
19
+ const [isCopyCompleted, setCopyCompleted] = useState<boolean>(false);
20
+
21
+ const copyToClipboard = async () => {
22
+ const codeDOMNode = getCodeDOMNode();
23
+
24
+ if (!codeDOMNode) {
25
+ return;
26
+ }
27
+
28
+ let content = "";
29
+
30
+ editor.update(() => {
31
+ const codeNode = $getNearestNodeFromDOMNode(codeDOMNode);
32
+
33
+ if ($isCodeNode(codeNode)) {
34
+ content = codeNode.getTextContent();
35
+ }
36
+
37
+ const selection = $getSelection();
38
+ $setSelection(selection);
39
+ });
40
+
41
+ try {
42
+ await navigator.clipboard.writeText(content);
43
+ setCopyCompleted(true);
44
+ setTimeout(() => setCopyCompleted(false), 2000);
45
+ } catch (err) {
46
+ console.error("Failed to copy: ", err);
47
+ }
48
+ };
49
+ return (
50
+ <Button
51
+ variant="outline"
52
+ size={"Toolbar"}
53
+ className="relative rounded-md"
54
+ onClick={copyToClipboard}
55
+ aria-label={isCopyCompleted ? "Copied" : "Copy to clipboard"}
56
+ >
57
+ <span className="sr-only">{isCopyCompleted ? "Copied" : "Copy"}</span>
58
+ <Copy
59
+ className={`h-4 w-4 transition-all duration-300 ${
60
+ isCopyCompleted ? "scale-0" : "scale-100"
61
+ }`}
62
+ />
63
+ <Check
64
+ className={`absolute inset-0 m-auto h-4 w-4 transition-all duration-300 ${
65
+ isCopyCompleted ? "scale-100" : "scale-0"
66
+ }`}
67
+ />
68
+ </Button>
69
+ );
70
+ }