@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,240 @@
1
+ "use client";
2
+
3
+ import type React from "react";
4
+ import { useState, useRef } from "react";
5
+ import {
6
+ Upload,
7
+ Loader2,
8
+ X,
9
+ Image,
10
+ File,
11
+ UploadCloudIcon,
12
+ Video,
13
+ } from "lucide-react";
14
+ import { motion, AnimatePresence } from "framer-motion";
15
+ import { Card, CardContent } from "@/components/ui/card";
16
+ import { Progress } from "@/components/ui/progress";
17
+ import { Button } from "@/components/ui/button";
18
+ import { useEdgeStore } from "@/lib/edgestore";
19
+
20
+ const FileUploadZone = ({
21
+ InsertMedia,
22
+ }: {
23
+ InsertMedia: (files: { url: string; alt: string }[]) => void;
24
+ }) => {
25
+ const [draggedZone, setDraggedZone] = useState<number | null>(null);
26
+ const [files, setFiles] = useState<{ url: string; alt: string }[]>([]);
27
+ const [uploading, setUploading] = useState(false);
28
+ const [progress, setProgress] = useState(0);
29
+ const fileInputRef = useRef<HTMLInputElement>(null);
30
+ const { edgestore } = useEdgeStore();
31
+
32
+ const handleDragEnter = (index: number) => (e: React.DragEvent) => {
33
+ e.preventDefault();
34
+ setDraggedZone(index);
35
+ };
36
+
37
+ const handleDragLeave = (e: React.DragEvent) => {
38
+ e.preventDefault();
39
+ setDraggedZone(null);
40
+ };
41
+
42
+ // loading
43
+ const upload = async (newFiles: File[]) => {
44
+ setUploading(true);
45
+ setProgress(0);
46
+
47
+ // Store uploaded files
48
+ let uploadedFiles: { url: string; alt: string }[] = [];
49
+
50
+ for (let i = 0; i < newFiles.length; i++) {
51
+ const file = newFiles[i];
52
+
53
+ try {
54
+ const response = await edgestore.publicFiles.upload({
55
+ file,
56
+ onProgressChange: (progress) => {
57
+ setProgress(
58
+ Math.round((progress / 100) * ((i + 1) / newFiles.length) * 100)
59
+ );
60
+ },
61
+ });
62
+
63
+ uploadedFiles.push({ url: response.url, alt: file.name });
64
+ } catch (error) {
65
+ console.error(`Error uploading ${file.name}:`, error);
66
+ }
67
+ }
68
+
69
+ setFiles((prevFiles) => [...prevFiles, ...uploadedFiles]);
70
+ setUploading(false);
71
+ setProgress(100);
72
+ };
73
+
74
+ const handleDrop = () => async (e: React.DragEvent) => {
75
+ e.preventDefault();
76
+ setDraggedZone(null);
77
+ const droppedFiles = Array.from(e.dataTransfer.files);
78
+ if (droppedFiles.length > 0) {
79
+ await upload(droppedFiles);
80
+ }
81
+ };
82
+
83
+ const handleFileSelect = async (e: React.ChangeEvent<HTMLInputElement>) => {
84
+ const selectedFiles = Array.from(e.target.files || []);
85
+ if (selectedFiles.length > 0) {
86
+ await upload(selectedFiles);
87
+ }
88
+ };
89
+
90
+ const removeFile = (index: number) => {
91
+ setFiles((prevFiles) => prevFiles.filter((_, i) => i !== index));
92
+ };
93
+
94
+ const zones = [
95
+ {
96
+ title: "Upload Images",
97
+ subtitle: "Drop images here",
98
+ icon: Image,
99
+ gradient: "from-purple-400 via-pink-500 to-red-500",
100
+ rotate: "-rotate-2",
101
+ },
102
+ {
103
+ title: "Upload Videos",
104
+ subtitle: "Drop videos here",
105
+ icon: Video,
106
+ gradient: "from-blue-400 via-teal-500 to-green-500",
107
+ rotate: "",
108
+ },
109
+ {
110
+ title: "Upload Files",
111
+ subtitle: "Drop files here",
112
+ icon: UploadCloudIcon,
113
+ gradient: "from-yellow-400 via-orange-500 to-red-500",
114
+ rotate: "rotate-3",
115
+ },
116
+ ];
117
+
118
+ return (
119
+ <Card className="mx-auto w-full bg-transparent border-none max-w-[300px] lg:max-w-[500px] overflow-hidden rounded-[1rem]">
120
+ <CardContent className="p-6 py-7 cursor:pointer">
121
+ <div className="grid grid-cols-1 sm:grid-cols-3 gap-4 mb-6">
122
+ {zones.map((zone, index) => (
123
+ <div key={index} className={`relative ${zone.rotate}`}>
124
+ <motion.div
125
+ onDragEnter={handleDragEnter(index)}
126
+ onDragOver={(e: { preventDefault: () => any }) =>
127
+ e.preventDefault()
128
+ }
129
+ onDragLeave={handleDragLeave}
130
+ onDrop={handleDrop}
131
+ whileHover={{ y: -4, scale: 1.02 }}
132
+ whileTap={{ scale: 0.98 }}
133
+ className="group relative h-full"
134
+ >
135
+ <div
136
+ className={`
137
+ absolute inset-0 -z-10 rounded-xl bg-gradient-to-br ${
138
+ zone.gradient
139
+ }
140
+ opacity-0 blur-md transition-opacity duration-300
141
+ ${
142
+ draggedZone === index
143
+ ? "opacity-70"
144
+ : "group-hover:opacity-70"
145
+ }
146
+ `}
147
+ />
148
+ <Card className="relative h-full rounded-[1rem] overflow-hidden border-2 border-dashed border-gray-200 dark:border-gray-800 transition-colors duration-300 group-hover:border-transparent">
149
+ <CardContent className="flex h-full flex-col items-center justify-center p-6 text-center">
150
+ <motion.div
151
+ whileHover={{ scale: 1.1, rotate: 10 }}
152
+ className="rounded-full bg-gray-100 dark:bg-gray-800 p-3 mb-4"
153
+ >
154
+ <zone.icon className="h-8 w-8 text-gray-500" />
155
+ </motion.div>
156
+ <h3 className="mb-1 text-sm font-medium">{zone.title}</h3>
157
+ <p className="text-xs text-gray-500">{zone.subtitle}</p>
158
+ </CardContent>
159
+ </Card>
160
+ </motion.div>
161
+ </div>
162
+ ))}
163
+ </div>
164
+
165
+ {(uploading || files.length > 0) && (
166
+ <div className="mb-6">
167
+ <Progress value={uploading ? progress : 100} className="h-2 mb-2" />
168
+ <p className="text-sm text-gray-500 mb-2">
169
+ {uploading
170
+ ? `Uploading... ${progress}%`
171
+ : `${files.length} file(s) uploaded`}
172
+ </p>
173
+ <AnimatePresence>
174
+ {files.map((file, index) => (
175
+ <motion.div
176
+ key={`${file.alt}-${index}`}
177
+ initial={{ opacity: 0, y: -10 }}
178
+ animate={{ opacity: 1, y: 0 }}
179
+ exit={{ opacity: 0, y: -10 }}
180
+ className="flex items-center justify-between bg-gray-100 dark:bg-gray-800 rounded-[1rem] p-2 mb-2"
181
+ >
182
+ <span className="truncate max-w-[80%] text-sm text-gray-700 dark:text-gray-300 ml-2">
183
+ {file.alt}
184
+ </span>
185
+ <Button
186
+ variant="ghost"
187
+ size="icon"
188
+ onClick={() => removeFile(index)}
189
+ className="h-6 w-6 p-0 hover:bg-gray-200 dark:hover:bg-gray-700"
190
+ >
191
+ <X className="h-4 w-4" />
192
+ </Button>
193
+ </motion.div>
194
+ ))}
195
+ </AnimatePresence>
196
+ </div>
197
+ )}
198
+
199
+ <div className="text-center flex flex-col">
200
+ <input
201
+ type="file"
202
+ ref={fileInputRef}
203
+ onChange={handleFileSelect}
204
+ className="hidden"
205
+ multiple
206
+ />
207
+ <Button
208
+ onClick={() => fileInputRef.current?.click()}
209
+ disabled={uploading}
210
+ className="rounded-[1rem] mt-5"
211
+ >
212
+ {uploading ? (
213
+ <>
214
+ <Loader2 className="mr-2 h-4 w-4 animate-spin" />
215
+ Uploading...
216
+ </>
217
+ ) : (
218
+ <>
219
+ <Upload className="mr-2 h-4 w-4" />
220
+ Choose Files
221
+ </>
222
+ )}
223
+ </Button>
224
+
225
+ {files.length > 0 && (
226
+ <Button
227
+ onClick={() => InsertMedia(files)}
228
+ disabled={uploading}
229
+ className="rounded-[1rem] mt-2"
230
+ >
231
+ Insert the {files.length == 1 ? "image" : "images"}
232
+ </Button>
233
+ )}
234
+ </div>
235
+ </CardContent>
236
+ </Card>
237
+ );
238
+ };
239
+
240
+ export default FileUploadZone;
@@ -0,0 +1,297 @@
1
+ import * as React from "react";
2
+ import { useRef } from "react";
3
+ import { calculateZoomLevel } from "@lexical/utils";
4
+ import { LexicalEditor } from "lexical";
5
+
6
+ function clamp(value: number, min: number, max: number) {
7
+ return Math.min(Math.max(value, min), max);
8
+ }
9
+
10
+ const Direction = {
11
+ east: 1 << 0,
12
+ north: 1 << 3,
13
+ south: 1 << 1,
14
+ west: 1 << 2,
15
+ };
16
+
17
+ export default function ImageResizer({
18
+ onResizeStart,
19
+ onResizeEnd,
20
+ MediaRef,
21
+ editor,
22
+ }: {
23
+ editor: LexicalEditor;
24
+ MediaRef: { current: null | HTMLElement };
25
+ onResizeEnd: (width: "inherit" | number, height: "inherit" | number) => void;
26
+ onResizeStart: () => void;
27
+ }): React.JSX.Element {
28
+ const controlWrapperRef = useRef<HTMLDivElement>(null);
29
+ const userSelect = useRef({
30
+ priority: "",
31
+ value: "default",
32
+ });
33
+ const positioningRef = useRef<{
34
+ currentHeight: "inherit" | number;
35
+ currentWidth: "inherit" | number;
36
+ direction: number;
37
+ isResizing: boolean;
38
+ ratio: number;
39
+ startHeight: number;
40
+ startWidth: number;
41
+ startX: number;
42
+ startY: number;
43
+ }>({
44
+ currentHeight: 0,
45
+ currentWidth: 0,
46
+ direction: 0,
47
+ isResizing: false,
48
+ ratio: 0,
49
+ startHeight: 0,
50
+ startWidth: 0,
51
+ startX: 0,
52
+ startY: 0,
53
+ });
54
+
55
+ const editorRootElement = editor.getRootElement();
56
+ // Find max width, accounting for editor padding.
57
+ // document.getElementById("Editor")?.scrollHeight
58
+
59
+ const maxWidthContainer = 950
60
+ const maxHeightContainer =
61
+ editorRootElement !== null
62
+ ? editorRootElement.getBoundingClientRect().height - 20
63
+ : 100;
64
+
65
+ const minWidth = 100;
66
+ const minHeight = 100;
67
+
68
+ const setStartCursor = (direction: number) => {
69
+ const ew = direction === Direction.east || direction === Direction.west;
70
+ const ns = direction === Direction.north || direction === Direction.south;
71
+ const nwse =
72
+ (direction & Direction.north && direction & Direction.west) ||
73
+ (direction & Direction.south && direction & Direction.east);
74
+
75
+ const cursorDir = ew ? "ew" : ns ? "ns" : nwse ? "nwse" : "nesw";
76
+
77
+ if (editorRootElement !== null) {
78
+ editorRootElement.style.setProperty(
79
+ "cursor",
80
+ `${cursorDir}-resize`,
81
+ "important"
82
+ );
83
+ }
84
+ if (document.body !== null) {
85
+ document.body.style.setProperty(
86
+ "cursor",
87
+ `${cursorDir}-resize`,
88
+ "important"
89
+ );
90
+ userSelect.current.value = document.body.style.getPropertyValue(
91
+ "-webkit-user-select"
92
+ );
93
+ userSelect.current.priority = document.body.style.getPropertyPriority(
94
+ "-webkit-user-select"
95
+ );
96
+ document.body.style.setProperty(
97
+ "-webkit-user-select",
98
+ `none`,
99
+ "important"
100
+ );
101
+ }
102
+ };
103
+
104
+ const setEndCursor = () => {
105
+ if (editorRootElement !== null) {
106
+ editorRootElement.style.setProperty("cursor", "text");
107
+ }
108
+ if (document.body !== null) {
109
+ document.body.style.setProperty("cursor", "default");
110
+ document.body.style.setProperty(
111
+ "-webkit-user-select",
112
+ userSelect.current.value,
113
+ userSelect.current.priority
114
+ );
115
+ }
116
+ };
117
+
118
+ const handlePointerDown = (
119
+ event: React.PointerEvent<HTMLDivElement>,
120
+ direction: number
121
+ ) => {
122
+ if (!editor.isEditable()) {
123
+ return;
124
+ }
125
+
126
+ const image = MediaRef.current;
127
+ const controlWrapper = controlWrapperRef.current;
128
+
129
+ if (image !== null && controlWrapper !== null) {
130
+ event.preventDefault();
131
+ const { width, height } = image.getBoundingClientRect();
132
+ const zoom = calculateZoomLevel(image);
133
+ const positioning = positioningRef.current;
134
+ positioning.startWidth = width;
135
+ positioning.startHeight = height;
136
+ positioning.ratio = width / height;
137
+ positioning.currentWidth = width;
138
+ positioning.currentHeight = height;
139
+ positioning.startX = event.clientX / zoom;
140
+ positioning.startY = event.clientY / zoom;
141
+ positioning.isResizing = true;
142
+ positioning.direction = direction;
143
+
144
+ setStartCursor(direction);
145
+ onResizeStart();
146
+
147
+ controlWrapper.classList.add("resizing");
148
+ image.classList.add("resizing");
149
+
150
+ document.addEventListener("pointermove", handlePointerMove);
151
+ document.addEventListener("pointerup", handlePointerUp);
152
+ }
153
+ };
154
+
155
+ const handlePointerMove = (event: PointerEvent) => {
156
+ const image = MediaRef.current;
157
+ const positioning = positioningRef.current;
158
+
159
+ const isHorizontal =
160
+ positioning.direction & (Direction.east | Direction.west);
161
+ const isVertical =
162
+ positioning.direction & (Direction.south | Direction.north);
163
+
164
+ if (image !== null && positioning.isResizing) {
165
+ const zoom = calculateZoomLevel(image);
166
+ // Corner cursor
167
+ if (isHorizontal && isVertical) {
168
+ let diff = Math.floor(positioning.startX - event.clientX / zoom);
169
+ diff = positioning.direction & Direction.east ? -diff : diff;
170
+
171
+ const width = clamp(
172
+ positioning.startWidth + diff,
173
+ minWidth,
174
+ maxWidthContainer
175
+ );
176
+
177
+ const height = width / positioning.ratio;
178
+ image.style.width = `${width}px`;
179
+ image.style.height = `${height}px`;
180
+ positioning.currentHeight = height;
181
+ positioning.currentWidth = width;
182
+ } else if (isVertical) {
183
+ let diff = Math.floor(positioning.startY - event.clientY / zoom);
184
+ diff = positioning.direction & Direction.south ? -diff : diff;
185
+
186
+ const height = clamp(
187
+ positioning.startHeight + diff,
188
+ minHeight,
189
+ maxHeightContainer
190
+ );
191
+
192
+ image.style.height = `${height}px`;
193
+ positioning.currentHeight = height;
194
+ } else {
195
+ let diff = Math.floor(positioning.startX - event.clientX / zoom);
196
+ diff = positioning.direction & Direction.east ? -diff : diff;
197
+
198
+ const width = clamp(
199
+ positioning.startWidth + diff,
200
+ minWidth,
201
+ maxWidthContainer
202
+ );
203
+
204
+ image.style.width = `${width}px`;
205
+ positioning.currentWidth = width;
206
+ }
207
+ }
208
+ };
209
+
210
+ const handlePointerUp = () => {
211
+ const image = MediaRef.current;
212
+ const positioning = positioningRef.current;
213
+ const controlWrapper = controlWrapperRef.current;
214
+ if (image !== null && controlWrapper !== null && positioning.isResizing) {
215
+ const width = positioning.currentWidth;
216
+ const height = positioning.currentHeight;
217
+ positioning.startWidth = 0;
218
+ positioning.startHeight = 0;
219
+ positioning.ratio = 0;
220
+ positioning.startX = 0;
221
+ positioning.startY = 0;
222
+ positioning.currentWidth = 0;
223
+ positioning.currentHeight = 0;
224
+ positioning.isResizing = false;
225
+
226
+ controlWrapper.classList.remove("resizing");
227
+ image.classList.remove("resizing");
228
+
229
+ setEndCursor();
230
+ onResizeEnd(width, height);
231
+
232
+ document.removeEventListener("pointermove", handlePointerMove);
233
+ document.removeEventListener("pointerup", handlePointerUp);
234
+ }
235
+ };
236
+
237
+ return (
238
+ <div
239
+ ref={controlWrapperRef}
240
+ className="absolute inset-0 z-10"
241
+ style={{
242
+ width: MediaRef.current?.style.width,
243
+ height: MediaRef.current?.style.height,
244
+ }}
245
+ >
246
+ {/* Resizer Handles */}
247
+ <div
248
+ className="absolute top-0 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-2 h-2 bg-blue-500 rounded-full cursor-n-resize"
249
+ onPointerDown={(event) => {
250
+ handlePointerDown(event, Direction.north);
251
+ }}
252
+ />
253
+ <div
254
+ className="absolute top-0 right-0 transform -translate-y-1/2 translate-x-1/2 w-2 h-2 bg-blue-500 rounded-full cursor-ne-resize"
255
+ onPointerDown={(event) => {
256
+ handlePointerDown(event, Direction.north | Direction.east);
257
+ }}
258
+ />
259
+ <div
260
+ className="absolute top-1/2 right-0 transform translate-x-1/2 -translate-y-1/2 w-2 h-2 bg-blue-500 rounded-full cursor-e-resize"
261
+ onPointerDown={(event) => {
262
+ handlePointerDown(event, Direction.east);
263
+ }}
264
+ />
265
+ <div
266
+ className="absolute bottom-0 right-0 transform translate-x-1/2 translate-y-1/2 w-2 h-2 bg-blue-500 rounded-full cursor-se-resize"
267
+ onPointerDown={(event) => {
268
+ handlePointerDown(event, Direction.south | Direction.east);
269
+ }}
270
+ />
271
+ <div
272
+ className="absolute bottom-0 left-1/2 transform -translate-x-1/2 translate-y-1/2 w-2 h-2 bg-blue-500 rounded-full cursor-s-resize"
273
+ onPointerDown={(event) => {
274
+ handlePointerDown(event, Direction.south);
275
+ }}
276
+ />
277
+ <div
278
+ className="absolute bottom-0 left-0 transform -translate-x-1/2 translate-y-1/2 w-2 h-2 bg-blue-500 rounded-full cursor-sw-resize"
279
+ onPointerDown={(event) => {
280
+ handlePointerDown(event, Direction.south | Direction.west);
281
+ }}
282
+ />
283
+ <div
284
+ className="absolute top-1/2 left-0 transform -translate-x-1/2 -translate-y-1/2 w-2 h-2 bg-blue-500 rounded-full cursor-w-resize"
285
+ onPointerDown={(event) => {
286
+ handlePointerDown(event, Direction.west);
287
+ }}
288
+ />
289
+ <div
290
+ className="absolute top-0 left-0 transform -translate-x-1/2 -translate-y-1/2 w-2 h-2 bg-blue-500 rounded-full cursor-nw-resize"
291
+ onPointerDown={(event) => {
292
+ handlePointerDown(event, Direction.north | Direction.west);
293
+ }}
294
+ />
295
+ </div>
296
+ );
297
+ }