@open-cloud-initiative/editor-x 0.0.1

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 (190) hide show
  1. package/.devcontainer/Dockerfile +13 -0
  2. package/.devcontainer/devcontainer.json +52 -0
  3. package/.github/dependabot.yml +10 -0
  4. package/.github/workflows/pages.yml +42 -0
  5. package/.github/workflows/publish.yml +41 -0
  6. package/.vscode/settings.json +7 -0
  7. package/LICENSE +9 -0
  8. package/README.md +9 -0
  9. package/app/_component/editor.tsx +383 -0
  10. package/app/layout.tsx +46 -0
  11. package/app/page.tsx +11 -0
  12. package/app/r/registry.json/route.ts +22 -0
  13. package/components/editorx/editor.tsx +1794 -0
  14. package/components/editorx/extensions/floating-menu.tsx +376 -0
  15. package/components/editorx/extensions/floating-toolbar.tsx +97 -0
  16. package/components/editorx/extensions/image-placeholder.tsx +316 -0
  17. package/components/editorx/extensions/image.tsx +462 -0
  18. package/components/editorx/extensions/search-and-replace.tsx +438 -0
  19. package/components/editorx/rich-text-editor.tsx +383 -0
  20. package/components/editorx/tiptap.css +421 -0
  21. package/components/editorx/toolbars/alignment.tsx +126 -0
  22. package/components/editorx/toolbars/blockquote.tsx +47 -0
  23. package/components/editorx/toolbars/bold.tsx +48 -0
  24. package/components/editorx/toolbars/bullet-list.tsx +48 -0
  25. package/components/editorx/toolbars/code-block.tsx +47 -0
  26. package/components/editorx/toolbars/code.tsx +43 -0
  27. package/components/editorx/toolbars/color-and-highlight.tsx +215 -0
  28. package/components/editorx/toolbars/editor-toolbar.tsx +77 -0
  29. package/components/editorx/toolbars/hard-break.tsx +46 -0
  30. package/components/editorx/toolbars/headings.tsx +97 -0
  31. package/components/editorx/toolbars/horizontal-rule.tsx +42 -0
  32. package/components/editorx/toolbars/image-placeholder-toolbar.tsx +47 -0
  33. package/components/editorx/toolbars/italic.tsx +48 -0
  34. package/components/editorx/toolbars/link.tsx +130 -0
  35. package/components/editorx/toolbars/mobile-toolbar-group.tsx +76 -0
  36. package/components/editorx/toolbars/ordered-list.tsx +47 -0
  37. package/components/editorx/toolbars/redo.tsx +44 -0
  38. package/components/editorx/toolbars/strikethrough.tsx +48 -0
  39. package/components/editorx/toolbars/toolbar-provider.tsx +29 -0
  40. package/components/editorx/toolbars/underline.tsx +48 -0
  41. package/components/editorx/toolbars/undo.tsx +43 -0
  42. package/components/layout/theme-switcher.tsx +26 -0
  43. package/components/main-nav.tsx +24 -0
  44. package/components/mobile-nav.tsx +46 -0
  45. package/components/open-in-v0-button.tsx +38 -0
  46. package/components/page-header.tsx +30 -0
  47. package/components/site-footer.tsx +41 -0
  48. package/components/site-header.tsx +32 -0
  49. package/components/theme-provider.tsx +8 -0
  50. package/components/ui/button.tsx +57 -0
  51. package/components/ui/checkbox.tsx +30 -0
  52. package/components/ui/collapsible.tsx +11 -0
  53. package/components/ui/command.tsx +148 -0
  54. package/components/ui/dialog.tsx +122 -0
  55. package/components/ui/drawer.tsx +118 -0
  56. package/components/ui/dropdown-menu.tsx +201 -0
  57. package/components/ui/input.tsx +22 -0
  58. package/components/ui/label.tsx +26 -0
  59. package/components/ui/popover.tsx +33 -0
  60. package/components/ui/resizable.tsx +40 -0
  61. package/components/ui/scroll-area.tsx +42 -0
  62. package/components/ui/separator.tsx +31 -0
  63. package/components/ui/sheet.tsx +140 -0
  64. package/components/ui/sidebar.tsx +763 -0
  65. package/components/ui/skeleton.tsx +15 -0
  66. package/components/ui/spinner.tsx +29 -0
  67. package/components/ui/tabs.tsx +55 -0
  68. package/components/ui/toggle-group.tsx +61 -0
  69. package/components/ui/toggle.tsx +45 -0
  70. package/components/ui/tooltip.tsx +32 -0
  71. package/components.json +21 -0
  72. package/config/site.ts +15 -0
  73. package/eslint.config.mjs +20 -0
  74. package/hooks/use-character-limit.ts +28 -0
  75. package/hooks/use-copy-to-clipboard.ts +16 -0
  76. package/hooks/use-debounce.ts +17 -0
  77. package/hooks/use-image-upload.ts +97 -0
  78. package/hooks/use-media-querry.ts +18 -0
  79. package/hooks/use-mobile.tsx +19 -0
  80. package/images/editor.png +0 -0
  81. package/lib/content.ts +39 -0
  82. package/lib/cookie-client.ts +19 -0
  83. package/lib/localstorage-client.ts +19 -0
  84. package/lib/package.ts +144 -0
  85. package/lib/preferences-config.ts +72 -0
  86. package/lib/preferences-storage.ts +20 -0
  87. package/lib/theme-utils.ts +12 -0
  88. package/lib/theme.ts +50 -0
  89. package/lib/tiptap-utils.ts +45 -0
  90. package/lib/utils.ts +11 -0
  91. package/next-env.d.ts +6 -0
  92. package/next.config.mjs +11 -0
  93. package/package.json +92 -0
  94. package/postcss.config.mjs +8 -0
  95. package/prettier.config.mjs +15 -0
  96. package/public/android-chrome-192x192.png +0 -0
  97. package/public/android-chrome-512x512.png +0 -0
  98. package/public/apple-touch-icon.png +0 -0
  99. package/public/favicon-16x16.png +0 -0
  100. package/public/favicon-32x32.png +0 -0
  101. package/public/favicon.ico +0 -0
  102. package/public/file.svg +1 -0
  103. package/public/globe.svg +1 -0
  104. package/public/next.svg +1 -0
  105. package/public/og.webp +0 -0
  106. package/public/r/editor-x.json +85 -0
  107. package/public/r/registry.json +93 -0
  108. package/public/site.webmanifest +19 -0
  109. package/public/vercel.svg +1 -0
  110. package/public/window.svg +1 -0
  111. package/registry/editor/components/editor.tsx +1794 -0
  112. package/registry/editor/components/extensions/floating-menu.tsx +376 -0
  113. package/registry/editor/components/extensions/floating-toolbar.tsx +97 -0
  114. package/registry/editor/components/extensions/image-placeholder.tsx +316 -0
  115. package/registry/editor/components/extensions/image.tsx +462 -0
  116. package/registry/editor/components/extensions/search-and-replace.tsx +438 -0
  117. package/registry/editor/components/rich-text-editor.tsx +383 -0
  118. package/registry/editor/components/tiptap.css +421 -0
  119. package/registry/editor/components/toolbars/alignment.tsx +126 -0
  120. package/registry/editor/components/toolbars/blockquote.tsx +47 -0
  121. package/registry/editor/components/toolbars/bold.tsx +48 -0
  122. package/registry/editor/components/toolbars/bullet-list.tsx +48 -0
  123. package/registry/editor/components/toolbars/code-block.tsx +47 -0
  124. package/registry/editor/components/toolbars/code.tsx +43 -0
  125. package/registry/editor/components/toolbars/color-and-highlight.tsx +215 -0
  126. package/registry/editor/components/toolbars/editor-toolbar.tsx +77 -0
  127. package/registry/editor/components/toolbars/hard-break.tsx +46 -0
  128. package/registry/editor/components/toolbars/headings.tsx +97 -0
  129. package/registry/editor/components/toolbars/horizontal-rule.tsx +42 -0
  130. package/registry/editor/components/toolbars/image-placeholder-toolbar.tsx +47 -0
  131. package/registry/editor/components/toolbars/italic.tsx +48 -0
  132. package/registry/editor/components/toolbars/link.tsx +130 -0
  133. package/registry/editor/components/toolbars/mobile-toolbar-group.tsx +76 -0
  134. package/registry/editor/components/toolbars/ordered-list.tsx +47 -0
  135. package/registry/editor/components/toolbars/redo.tsx +44 -0
  136. package/registry/editor/components/toolbars/strikethrough.tsx +48 -0
  137. package/registry/editor/components/toolbars/toolbar-provider.tsx +29 -0
  138. package/registry/editor/components/toolbars/underline.tsx +48 -0
  139. package/registry/editor/components/toolbars/undo.tsx +43 -0
  140. package/registry/editor/components/ui/button.tsx +57 -0
  141. package/registry/editor/components/ui/checkbox.tsx +30 -0
  142. package/registry/editor/components/ui/collapsible.tsx +11 -0
  143. package/registry/editor/components/ui/command.tsx +148 -0
  144. package/registry/editor/components/ui/dialog.tsx +122 -0
  145. package/registry/editor/components/ui/drawer.tsx +118 -0
  146. package/registry/editor/components/ui/dropdown-menu.tsx +201 -0
  147. package/registry/editor/components/ui/input.tsx +22 -0
  148. package/registry/editor/components/ui/label.tsx +26 -0
  149. package/registry/editor/components/ui/popover.tsx +33 -0
  150. package/registry/editor/components/ui/resizable.tsx +40 -0
  151. package/registry/editor/components/ui/scroll-area.tsx +42 -0
  152. package/registry/editor/components/ui/separator.tsx +31 -0
  153. package/registry/editor/components/ui/sheet.tsx +140 -0
  154. package/registry/editor/components/ui/sidebar.tsx +763 -0
  155. package/registry/editor/components/ui/skeleton.tsx +15 -0
  156. package/registry/editor/components/ui/spinner.tsx +29 -0
  157. package/registry/editor/components/ui/tabs.tsx +55 -0
  158. package/registry/editor/components/ui/toggle-group.tsx +61 -0
  159. package/registry/editor/components/ui/toggle.tsx +45 -0
  160. package/registry/editor/components/ui/tooltip.tsx +32 -0
  161. package/registry/editor/hooks/use-character-limit.ts +28 -0
  162. package/registry/editor/hooks/use-copy-to-clipboard.ts +16 -0
  163. package/registry/editor/hooks/use-debounce.ts +17 -0
  164. package/registry/editor/hooks/use-image-upload.ts +97 -0
  165. package/registry/editor/hooks/use-media-querry.ts +18 -0
  166. package/registry/editor/hooks/use-mobile.tsx +19 -0
  167. package/registry/editor/lib/content.ts +39 -0
  168. package/registry/editor/lib/cookie-client.ts +19 -0
  169. package/registry/editor/lib/localstorage-client.ts +19 -0
  170. package/registry/editor/lib/package.ts +144 -0
  171. package/registry/editor/lib/preferences-config.ts +72 -0
  172. package/registry/editor/lib/preferences-storage.ts +20 -0
  173. package/registry/editor/lib/theme-utils.ts +12 -0
  174. package/registry/editor/lib/theme.ts +50 -0
  175. package/registry/editor/lib/tiptap-utils.ts +45 -0
  176. package/registry/editor/lib/utils.ts +11 -0
  177. package/registry/editor/page.tsx +9 -0
  178. package/registry.json +93 -0
  179. package/reset.d.ts +1 -0
  180. package/scripts/generate-theme-presets.ts +128 -0
  181. package/scripts/postCreateCommand.sh +0 -0
  182. package/scripts/theme-boot.tsx +105 -0
  183. package/server/server-actions.ts +27 -0
  184. package/stores/preferences/preferences-provider.tsx +55 -0
  185. package/stores/preferences/preferences-store.ts +23 -0
  186. package/styles/globals.css +288 -0
  187. package/styles/presets/brutalist.css +89 -0
  188. package/styles/presets/soft-pop.css +89 -0
  189. package/styles/presets/tangerine.css +89 -0
  190. package/tsconfig.json +50 -0
@@ -0,0 +1,316 @@
1
+ 'use client'
2
+ /* eslint-disable */
3
+ // @ts-nocheck
4
+ import { Button } from "@/components/ui/button";
5
+ import { Input } from "@/components/ui/input";
6
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
7
+ import { useImageUpload } from "@/hooks/use-image-upload";
8
+ import {
9
+ NODE_HANDLES_SELECTED_STYLE_CLASSNAME,
10
+ isValidUrl,
11
+ } from "@/lib/tiptap-utils";
12
+ import { cn } from "@/lib/utils";
13
+ import {
14
+ type CommandProps,
15
+ Node,
16
+ type NodeViewProps,
17
+ NodeViewWrapper,
18
+ ReactNodeViewRenderer,
19
+ mergeAttributes,
20
+ } from "@tiptap/react";
21
+ import { Image, Link, Loader2, Upload, X } from "lucide-react";
22
+ import { type FormEvent, useState } from "react";
23
+
24
+ export interface ImagePlaceholderOptions {
25
+ HTMLAttributes: Record<string, any>;
26
+ onUpload?: (url: string) => void;
27
+ onError?: (error: string) => void;
28
+ }
29
+
30
+ declare module "@tiptap/core" {
31
+ interface Commands<ReturnType> {
32
+ imagePlaceholder: {
33
+ /**
34
+ * Inserts an image placeholder
35
+ */
36
+ insertImagePlaceholder: () => ReturnType;
37
+ };
38
+ }
39
+ }
40
+
41
+ export const ImagePlaceholder = Node.create<ImagePlaceholderOptions>({
42
+ name: "image-placeholder",
43
+
44
+ addOptions() {
45
+ return {
46
+ HTMLAttributes: {},
47
+ onUpload: () => {},
48
+ onError: () => {},
49
+ };
50
+ },
51
+
52
+ group: "block",
53
+
54
+ parseHTML() {
55
+ return [{ tag: `div[data-type="${this.name}"]` }];
56
+ },
57
+
58
+ renderHTML({ HTMLAttributes }) {
59
+ return ["div", mergeAttributes(HTMLAttributes)];
60
+ },
61
+
62
+ addNodeView() {
63
+ return ReactNodeViewRenderer(ImagePlaceholderComponent, {
64
+ className: NODE_HANDLES_SELECTED_STYLE_CLASSNAME,
65
+ });
66
+ },
67
+
68
+ addCommands() {
69
+ return {
70
+ insertImagePlaceholder: () => (props: CommandProps) => {
71
+ return props.commands.insertContent({
72
+ type: "image-placeholder",
73
+ });
74
+ },
75
+ };
76
+ },
77
+ });
78
+
79
+ function ImagePlaceholderComponent(props: NodeViewProps) {
80
+ const { editor, extension, selected } = props;
81
+ const [isExpanded, setIsExpanded] = useState(false);
82
+ const [activeTab, setActiveTab] = useState<'upload' | 'url'>('upload');
83
+ const [url, setUrl] = useState("");
84
+ const [altText, setAltText] = useState("");
85
+ const [urlError, setUrlError] = useState(false);
86
+ const [isDragActive, setIsDragActive] = useState(false);
87
+
88
+ const {
89
+ previewUrl,
90
+ fileInputRef,
91
+ handleFileChange,
92
+ handleRemove,
93
+ uploading,
94
+ error,
95
+ } = useImageUpload({
96
+ onUpload: (imageUrl) => {
97
+ editor.chain().focus().setImage({
98
+ src: imageUrl,
99
+ alt: altText || fileInputRef.current?.files?.[0]?.name
100
+ }).run();
101
+ handleRemove();
102
+ setIsExpanded(false);
103
+ },
104
+ });
105
+
106
+ const handleDragEnter = (e: React.DragEvent<HTMLDivElement>) => {
107
+ e.preventDefault();
108
+ e.stopPropagation();
109
+ setIsDragActive(true);
110
+ };
111
+
112
+ const handleDragLeave = (e: React.DragEvent<HTMLDivElement>) => {
113
+ e.preventDefault();
114
+ e.stopPropagation();
115
+ setIsDragActive(false);
116
+ };
117
+
118
+ const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {
119
+ e.preventDefault();
120
+ e.stopPropagation();
121
+ };
122
+
123
+ const handleDrop = (e: React.DragEvent<HTMLDivElement>) => {
124
+ e.preventDefault();
125
+ e.stopPropagation();
126
+ setIsDragActive(false);
127
+
128
+ const file = e.dataTransfer.files[0];
129
+ if (file) {
130
+ const input = fileInputRef.current;
131
+ if (input) {
132
+ const dataTransfer = new DataTransfer();
133
+ dataTransfer.items.add(file);
134
+ input.files = dataTransfer.files;
135
+ handleFileChange({ target: input } as any);
136
+ }
137
+ }
138
+ };
139
+
140
+ const handleInsertEmbed = (e: FormEvent) => {
141
+ e.preventDefault();
142
+ const valid = isValidUrl(url);
143
+ if (!valid) {
144
+ setUrlError(true);
145
+ return;
146
+ }
147
+ if (url) {
148
+ editor.chain().focus().setImage({ src: url, alt: altText }).run();
149
+ setIsExpanded(false);
150
+ setUrl("");
151
+ setAltText("");
152
+ }
153
+ };
154
+
155
+ return (
156
+ <NodeViewWrapper className="w-full">
157
+ <div className="relative">
158
+ {!isExpanded ? (
159
+ <div
160
+ onClick={() => setIsExpanded(true)}
161
+ className={cn(
162
+ "group relative flex cursor-pointer flex-col items-center gap-4 rounded-lg border-2 border-dashed p-8 transition-all hover:bg-accent",
163
+ selected && "border-primary bg-primary/5",
164
+ isDragActive && "border-primary bg-primary/5",
165
+ error && "border-destructive bg-destructive/5"
166
+ )}
167
+ >
168
+ <div className="rounded-full bg-background p-4 shadow-sm transition-colors group-hover:bg-accent">
169
+ <Image className="h-6 w-6" />
170
+ </div>
171
+ <div className="text-center">
172
+ <p className="text-sm font-medium">Click to upload or drag and drop</p>
173
+ <p className="text-xs text-muted-foreground">
174
+ SVG, PNG, JPG or GIF
175
+ </p>
176
+ </div>
177
+ </div>
178
+ ) : (
179
+ <div className="rounded-lg border bg-card p-4 shadow-sm">
180
+ <div className="mb-4 flex items-center justify-between">
181
+ <h3 className="text-lg font-semibold">Add Image</h3>
182
+ <Button
183
+ variant="ghost"
184
+ size="icon"
185
+ onClick={() => setIsExpanded(false)}
186
+ >
187
+ <X className="h-4 w-4" />
188
+ </Button>
189
+ </div>
190
+
191
+ <Tabs value={activeTab} onValueChange={(v: any) => setActiveTab(v)} className="w-full">
192
+ <TabsList className="grid w-full grid-cols-2">
193
+ <TabsTrigger value="upload">
194
+ <Upload className="mr-2 h-4 w-4" />
195
+ Upload
196
+ </TabsTrigger>
197
+ <TabsTrigger value="url">
198
+ <Link className="mr-2 h-4 w-4" />
199
+ URL
200
+ </TabsTrigger>
201
+ </TabsList>
202
+
203
+ <TabsContent value="upload">
204
+ <div
205
+ onDragEnter={handleDragEnter}
206
+ onDragLeave={handleDragLeave}
207
+ onDragOver={handleDragOver}
208
+ onDrop={handleDrop}
209
+ className={cn(
210
+ "my-4 rounded-lg border-2 border-dashed p-8 text-center transition-colors",
211
+ isDragActive && "border-primary bg-primary/10",
212
+ error && "border-destructive bg-destructive/10"
213
+ )}
214
+ >
215
+ {previewUrl ? (
216
+ <div className="space-y-4">
217
+ <img
218
+ src={previewUrl}
219
+ alt="Preview"
220
+ className="mx-auto max-h-[200px] rounded-lg object-cover"
221
+ />
222
+ <div className="space-y-2">
223
+ <Input
224
+ value={altText}
225
+ onChange={(e) => setAltText(e.target.value)}
226
+ placeholder="Alt text (optional)"
227
+ />
228
+ <div className="flex justify-end gap-2">
229
+ <Button
230
+ variant="outline"
231
+ onClick={handleRemove}
232
+ disabled={uploading}
233
+ >
234
+ Remove
235
+ </Button>
236
+ <Button disabled={uploading}>
237
+ {uploading && (
238
+ <Loader2 className="mr-2 h-4 w-4 animate-spin" />
239
+ )}
240
+ Upload
241
+ </Button>
242
+ </div>
243
+ </div>
244
+ </div>
245
+ ) : (
246
+ <>
247
+ <input
248
+ ref={fileInputRef}
249
+ type="file"
250
+ accept="image/*"
251
+ onChange={handleFileChange}
252
+ className="hidden"
253
+ id="image-upload"
254
+ />
255
+ <label
256
+ htmlFor="image-upload"
257
+ className="flex cursor-pointer flex-col items-center gap-4"
258
+ >
259
+ <Upload className="h-8 w-8 text-muted-foreground" />
260
+ <div>
261
+ <p className="text-sm font-medium">
262
+ Click to upload or drag and drop
263
+ </p>
264
+ <p className="text-xs text-muted-foreground">
265
+ SVG, PNG, JPG or GIF
266
+ </p>
267
+ </div>
268
+ </label>
269
+ </>
270
+ )}
271
+ {error && (
272
+ <p className="mt-2 text-sm text-destructive">{error}</p>
273
+ )}
274
+ </div>
275
+ </TabsContent>
276
+
277
+ <TabsContent value="url">
278
+ <div className="space-y-4 py-4">
279
+ <div className="space-y-2">
280
+ <Input
281
+ value={url}
282
+ onChange={(e) => {
283
+ setUrl(e.target.value);
284
+ if (urlError) setUrlError(false);
285
+ }}
286
+ placeholder="Enter image URL..."
287
+ />
288
+ {urlError && (
289
+ <p className="text-xs text-destructive">
290
+ Please enter a valid URL
291
+ </p>
292
+ )}
293
+ </div>
294
+ <div className="space-y-2">
295
+ <Input
296
+ value={altText}
297
+ onChange={(e) => setAltText(e.target.value)}
298
+ placeholder="Alt text (optional)"
299
+ />
300
+ </div>
301
+ <Button
302
+ onClick={handleInsertEmbed}
303
+ className="w-full"
304
+ disabled={!url}
305
+ >
306
+ Add Image
307
+ </Button>
308
+ </div>
309
+ </TabsContent>
310
+ </Tabs>
311
+ </div>
312
+ )}
313
+ </div>
314
+ </NodeViewWrapper>
315
+ );
316
+ }