@shadospace/editor 1.0.3 → 1.0.5
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.
- package/app/api/uploadthing/core.ts +72 -0
- package/app/api/uploadthing/route.ts +11 -0
- package/components/editor/index.tsx +2 -0
- package/components/editor/menu-bar.tsx +170 -37
- package/components/editor/menubar-state.tsx +9 -4
- package/lib/uploadthing.ts +11 -0
- package/package.json +24 -2
- package/scripts/init.js +15 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { createUploadthing, type FileRouter } from "uploadthing/next"
|
|
2
|
+
import { UploadThingError } from "uploadthing/server"
|
|
3
|
+
|
|
4
|
+
const f = createUploadthing()
|
|
5
|
+
|
|
6
|
+
const auth = (req: Request) => ({ id: "fakeId" }) // Fake auth function
|
|
7
|
+
|
|
8
|
+
// FileRouter for your app, can contain multiple FileRoutes
|
|
9
|
+
export const ourFileRouter = {
|
|
10
|
+
// Define as many FileRoutes as you like, each with a unique routeSlug
|
|
11
|
+
imageUploader: f({
|
|
12
|
+
image: {
|
|
13
|
+
/**
|
|
14
|
+
* For full list of options and defaults, see the File Route API reference
|
|
15
|
+
* @see https://docs.uploadthing.com/file-routes#route-config
|
|
16
|
+
*/
|
|
17
|
+
maxFileSize: "4MB",
|
|
18
|
+
maxFileCount: 1,
|
|
19
|
+
},
|
|
20
|
+
})
|
|
21
|
+
.middleware(async ({ req }) => {
|
|
22
|
+
// This code runs on your server before upload
|
|
23
|
+
const user = await auth(req)
|
|
24
|
+
|
|
25
|
+
// If you throw, the user will not be able to upload
|
|
26
|
+
if (!user) throw new UploadThingError("Unauthorized")
|
|
27
|
+
|
|
28
|
+
// Whatever is returned here is accessible in onUploadComplete as `metadata`
|
|
29
|
+
return { userId: user.id }
|
|
30
|
+
})
|
|
31
|
+
.onUploadComplete(async ({ metadata, file }) => {
|
|
32
|
+
// This code RUNS ON YOUR SERVER after upload
|
|
33
|
+
console.log("Upload complete for userId:", metadata.userId)
|
|
34
|
+
|
|
35
|
+
console.log("file url", file.ufsUrl)
|
|
36
|
+
|
|
37
|
+
// !!! Whatever is returned here is sent to the clientside `onClientUploadComplete` callback
|
|
38
|
+
return { uploadedBy: metadata.userId }
|
|
39
|
+
}),
|
|
40
|
+
coverImageUploader: f({
|
|
41
|
+
image: {
|
|
42
|
+
/**
|
|
43
|
+
* For full list of options and defaults, see the File Route API reference
|
|
44
|
+
* @see https://docs.uploadthing.com/file-routes#route-config
|
|
45
|
+
*/
|
|
46
|
+
maxFileSize: "4MB",
|
|
47
|
+
maxFileCount: 1,
|
|
48
|
+
},
|
|
49
|
+
})
|
|
50
|
+
// Set permissions and file types for this FileRoute
|
|
51
|
+
.middleware(async ({ req }) => {
|
|
52
|
+
// This code runs on your server before upload
|
|
53
|
+
const user = await auth(req)
|
|
54
|
+
|
|
55
|
+
// If you throw, the user will not be able to upload
|
|
56
|
+
if (!user) throw new UploadThingError("Unauthorized")
|
|
57
|
+
|
|
58
|
+
// Whatever is returned here is accessible in onUploadComplete as `metadata`
|
|
59
|
+
return { userId: user.id }
|
|
60
|
+
})
|
|
61
|
+
.onUploadComplete(async ({ metadata, file }) => {
|
|
62
|
+
// This code RUNS ON YOUR SERVER after upload
|
|
63
|
+
console.log("Upload complete for userId:", metadata.userId)
|
|
64
|
+
|
|
65
|
+
console.log("file url", file.ufsUrl)
|
|
66
|
+
|
|
67
|
+
// !!! Whatever is returned here is sent to the clientside `onClientUploadComplete` callback
|
|
68
|
+
return { uploadedBy: metadata.userId }
|
|
69
|
+
}),
|
|
70
|
+
} satisfies FileRouter
|
|
71
|
+
|
|
72
|
+
export type OurFileRouter = typeof ourFileRouter
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { createRouteHandler } from "uploadthing/next"
|
|
2
|
+
|
|
3
|
+
import { ourFileRouter } from "./core"
|
|
4
|
+
|
|
5
|
+
// Export routes for Next App Router
|
|
6
|
+
export const { GET, POST } = createRouteHandler({
|
|
7
|
+
router: ourFileRouter,
|
|
8
|
+
|
|
9
|
+
// Apply an (optional) custom config:
|
|
10
|
+
// config: { ... },
|
|
11
|
+
})
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { useEditor, EditorContent } from "@tiptap/react"
|
|
3
3
|
import StarterKit from "@tiptap/starter-kit"
|
|
4
4
|
import Image from "@tiptap/extension-image"
|
|
5
|
+
import Link from "@tiptap/extension-link"
|
|
5
6
|
import { TableKit } from "@tiptap/extension-table"
|
|
6
7
|
import { TextStyleKit } from "@tiptap/extension-text-style"
|
|
7
8
|
import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight"
|
|
@@ -35,6 +36,7 @@ export default function Editor({
|
|
|
35
36
|
TextStyleKit,
|
|
36
37
|
Image,
|
|
37
38
|
TableKit,
|
|
39
|
+
Link,
|
|
38
40
|
CodeBlockLowlight.configure({ lowlight }),
|
|
39
41
|
],
|
|
40
42
|
editorProps: {
|
|
@@ -4,11 +4,15 @@ import { useEditorState } from "@tiptap/react"
|
|
|
4
4
|
import { menuBarStateSelector } from "./menubar-state"
|
|
5
5
|
import {
|
|
6
6
|
Bold,
|
|
7
|
-
|
|
7
|
+
ChevronDown,
|
|
8
|
+
CodeXml,
|
|
9
|
+
Heading,
|
|
8
10
|
Heading1,
|
|
9
11
|
Heading2,
|
|
10
12
|
Heading3,
|
|
13
|
+
ImageIcon,
|
|
11
14
|
Italic,
|
|
15
|
+
Link,
|
|
12
16
|
List,
|
|
13
17
|
ListOrdered,
|
|
14
18
|
Minus,
|
|
@@ -21,6 +25,26 @@ import {
|
|
|
21
25
|
Undo2,
|
|
22
26
|
} from "lucide-react"
|
|
23
27
|
import { Button } from "../ui/button"
|
|
28
|
+
import {
|
|
29
|
+
DropdownMenu,
|
|
30
|
+
DropdownMenuContent,
|
|
31
|
+
DropdownMenuItem,
|
|
32
|
+
DropdownMenuTrigger,
|
|
33
|
+
} from "../ui/dropdown-menu"
|
|
34
|
+
import { UploadDropzone } from "@/lib/uploadthing"
|
|
35
|
+
import { toast } from "sonner"
|
|
36
|
+
import {
|
|
37
|
+
Dialog,
|
|
38
|
+
DialogClose,
|
|
39
|
+
DialogContent,
|
|
40
|
+
DialogDescription,
|
|
41
|
+
DialogFooter,
|
|
42
|
+
DialogHeader,
|
|
43
|
+
DialogTitle,
|
|
44
|
+
DialogTrigger,
|
|
45
|
+
} from "@/components/ui/dialog"
|
|
46
|
+
import { Label } from "../ui/label"
|
|
47
|
+
import { Input } from "../ui/input"
|
|
24
48
|
|
|
25
49
|
export const MenuBar = ({ editor }: { editor: Editor }) => {
|
|
26
50
|
const editorState = useEditorState({
|
|
@@ -32,41 +56,41 @@ export const MenuBar = ({ editor }: { editor: Editor }) => {
|
|
|
32
56
|
<div>
|
|
33
57
|
<div className="overflow-x-auto border bg-card">
|
|
34
58
|
<div className="flex w-max items-center p-2">
|
|
35
|
-
<
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
59
|
+
<DropdownMenu>
|
|
60
|
+
<DropdownMenuTrigger asChild>
|
|
61
|
+
<Button type="button" variant={"ghost"} aria-label="Toggle H1">
|
|
62
|
+
<Heading />
|
|
63
|
+
<ChevronDown size={0.5} />
|
|
64
|
+
</Button>
|
|
65
|
+
</DropdownMenuTrigger>
|
|
66
|
+
<DropdownMenuContent>
|
|
67
|
+
<DropdownMenuItem
|
|
68
|
+
onClick={() =>
|
|
69
|
+
editor.chain().focus().toggleHeading({ level: 1 }).run()
|
|
70
|
+
}
|
|
71
|
+
>
|
|
72
|
+
<Heading1 />
|
|
73
|
+
Heading 1
|
|
74
|
+
</DropdownMenuItem>
|
|
75
|
+
<DropdownMenuItem
|
|
76
|
+
onClick={() =>
|
|
77
|
+
editor.chain().focus().toggleHeading({ level: 2 }).run()
|
|
78
|
+
}
|
|
79
|
+
>
|
|
80
|
+
<Heading2 />
|
|
81
|
+
Heading 2
|
|
82
|
+
</DropdownMenuItem>
|
|
83
|
+
<DropdownMenuItem
|
|
84
|
+
onClick={() =>
|
|
85
|
+
editor.chain().focus().toggleHeading({ level: 3 }).run()
|
|
86
|
+
}
|
|
87
|
+
>
|
|
88
|
+
<Heading3 />
|
|
89
|
+
Heading 3
|
|
90
|
+
</DropdownMenuItem>
|
|
91
|
+
</DropdownMenuContent>
|
|
92
|
+
</DropdownMenu>
|
|
93
|
+
|
|
70
94
|
<Button
|
|
71
95
|
type="button"
|
|
72
96
|
variant={"ghost"}
|
|
@@ -101,7 +125,7 @@ export const MenuBar = ({ editor }: { editor: Editor }) => {
|
|
|
101
125
|
onClick={() => editor.chain().focus().toggleCode().run()}
|
|
102
126
|
disabled={!editorState.canCode}
|
|
103
127
|
>
|
|
104
|
-
<
|
|
128
|
+
<CodeXml />
|
|
105
129
|
</Button>
|
|
106
130
|
<Button
|
|
107
131
|
type="button"
|
|
@@ -140,6 +164,59 @@ export const MenuBar = ({ editor }: { editor: Editor }) => {
|
|
|
140
164
|
>
|
|
141
165
|
<SquareCode />
|
|
142
166
|
</Button>
|
|
167
|
+
<Dialog>
|
|
168
|
+
<DialogTrigger asChild>
|
|
169
|
+
<Button type="button" variant={"ghost"} size={"icon"}>
|
|
170
|
+
<Link />
|
|
171
|
+
</Button>
|
|
172
|
+
</DialogTrigger>
|
|
173
|
+
<DialogContent>
|
|
174
|
+
<DialogHeader>
|
|
175
|
+
<DialogTitle>Set Link</DialogTitle>
|
|
176
|
+
<DialogDescription>
|
|
177
|
+
Set the link in full URL like https://example.com
|
|
178
|
+
</DialogDescription>
|
|
179
|
+
</DialogHeader>
|
|
180
|
+
<div className="flex flex-col gap-4">
|
|
181
|
+
<div className="grid flex-1 gap-2">
|
|
182
|
+
<Label htmlFor="link-url">Link URL</Label>
|
|
183
|
+
<Input
|
|
184
|
+
placeholder="https://example.com"
|
|
185
|
+
id="link-url"
|
|
186
|
+
onChange={(e) =>
|
|
187
|
+
editor
|
|
188
|
+
.chain()
|
|
189
|
+
.focus()
|
|
190
|
+
.setLink({ href: e.target.value })
|
|
191
|
+
.run()
|
|
192
|
+
}
|
|
193
|
+
value={editorState.href || ""}
|
|
194
|
+
/>
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
197
|
+
<DialogFooter>
|
|
198
|
+
<DialogClose asChild>
|
|
199
|
+
<Button type="button" variant="secondary">
|
|
200
|
+
Cancel
|
|
201
|
+
</Button>
|
|
202
|
+
</DialogClose>
|
|
203
|
+
<DialogClose asChild>
|
|
204
|
+
<Button
|
|
205
|
+
type="button"
|
|
206
|
+
onClick={() =>
|
|
207
|
+
editor
|
|
208
|
+
.chain()
|
|
209
|
+
.focus()
|
|
210
|
+
.setLink({ href: editorState.href })
|
|
211
|
+
.run()
|
|
212
|
+
}
|
|
213
|
+
>
|
|
214
|
+
Set Link
|
|
215
|
+
</Button>
|
|
216
|
+
</DialogClose>
|
|
217
|
+
</DialogFooter>
|
|
218
|
+
</DialogContent>
|
|
219
|
+
</Dialog>
|
|
143
220
|
<Button
|
|
144
221
|
type="button"
|
|
145
222
|
variant={"ghost"}
|
|
@@ -149,6 +226,62 @@ export const MenuBar = ({ editor }: { editor: Editor }) => {
|
|
|
149
226
|
>
|
|
150
227
|
<Quote />
|
|
151
228
|
</Button>
|
|
229
|
+
|
|
230
|
+
<Dialog>
|
|
231
|
+
<DialogTrigger asChild>
|
|
232
|
+
<Button type="button" variant={"ghost"} size={"icon"}>
|
|
233
|
+
<ImageIcon />
|
|
234
|
+
</Button>
|
|
235
|
+
</DialogTrigger>
|
|
236
|
+
<DialogContent className="sm:max-w-md">
|
|
237
|
+
<DialogHeader>
|
|
238
|
+
<DialogTitle>Set Image</DialogTitle>
|
|
239
|
+
<DialogDescription>
|
|
240
|
+
Upload your image or provide a link.
|
|
241
|
+
</DialogDescription>
|
|
242
|
+
</DialogHeader>
|
|
243
|
+
<div className="flex flex-col gap-4">
|
|
244
|
+
<div className="grid flex-1 gap-2">
|
|
245
|
+
<Input
|
|
246
|
+
placeholder="https://example.com"
|
|
247
|
+
id="image-url"
|
|
248
|
+
onChange={(e) =>
|
|
249
|
+
editor
|
|
250
|
+
.chain()
|
|
251
|
+
.focus()
|
|
252
|
+
.setImage({ src: e.target.value })
|
|
253
|
+
.run()
|
|
254
|
+
}
|
|
255
|
+
value={editorState.image.src || ""}
|
|
256
|
+
/>
|
|
257
|
+
</div>
|
|
258
|
+
<div className="w-full cursor-pointer">
|
|
259
|
+
<UploadDropzone
|
|
260
|
+
className="h-48 w-full border-2 border-red-500/20 hover:border-red-500/30 ut-button:bg-red-500 ut-button:p-3 ut-button:text-sm ut-button:hover:bg-red-500 ut-allowed-content:hidden ut-label:text-sm ut-label:text-muted-foreground ut-upload-icon:size-40"
|
|
261
|
+
endpoint="imageUploader"
|
|
262
|
+
onClientUploadComplete={(res) => {
|
|
263
|
+
res.forEach((item) => {
|
|
264
|
+
toast.success("Image uploaded successfully")
|
|
265
|
+
editor
|
|
266
|
+
.chain()
|
|
267
|
+
.focus()
|
|
268
|
+
.setImage({ src: item.ufsUrl })
|
|
269
|
+
.run()
|
|
270
|
+
})
|
|
271
|
+
}}
|
|
272
|
+
onUploadError={(error) => {
|
|
273
|
+
toast.error(`Upload failed: ${error.message}`)
|
|
274
|
+
}}
|
|
275
|
+
/>
|
|
276
|
+
</div>
|
|
277
|
+
</div>
|
|
278
|
+
<DialogFooter className="justify-end">
|
|
279
|
+
<DialogClose asChild>
|
|
280
|
+
<Button type="button">Insert Image</Button>
|
|
281
|
+
</DialogClose>
|
|
282
|
+
</DialogFooter>
|
|
283
|
+
</DialogContent>
|
|
284
|
+
</Dialog>
|
|
152
285
|
<Button
|
|
153
286
|
type="button"
|
|
154
287
|
variant={"ghost"}
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import type { Editor } from "@tiptap/core"
|
|
2
2
|
import type { EditorStateSnapshot } from "@tiptap/react"
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
* State selector for the MenuBar component.
|
|
6
|
-
* Extracts the relevant editor state for rendering menu buttons.
|
|
7
|
-
*/
|
|
8
4
|
export function menuBarStateSelector(ctx: EditorStateSnapshot<Editor>) {
|
|
9
5
|
return {
|
|
10
6
|
// Text formatting
|
|
@@ -27,6 +23,15 @@ export function menuBarStateSelector(ctx: EditorStateSnapshot<Editor>) {
|
|
|
27
23
|
isHeading5: ctx.editor.isActive("heading", { level: 5 }) ?? false,
|
|
28
24
|
isHeading6: ctx.editor.isActive("heading", { level: 6 }) ?? false,
|
|
29
25
|
|
|
26
|
+
// others
|
|
27
|
+
href: ctx.editor.isActive("link")
|
|
28
|
+
? ctx.editor.getAttributes("link").href
|
|
29
|
+
: "",
|
|
30
|
+
|
|
31
|
+
image: ctx.editor.isActive("image")
|
|
32
|
+
? ctx.editor.getAttributes("image").src
|
|
33
|
+
: "",
|
|
34
|
+
|
|
30
35
|
// Lists and blocks
|
|
31
36
|
isBulletList: ctx.editor.isActive("bulletList") ?? false,
|
|
32
37
|
isOrderedList: ctx.editor.isActive("orderedList") ?? false,
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import {
|
|
2
|
+
generateUploadButton,
|
|
3
|
+
generateUploadDropzone,
|
|
4
|
+
generateUploader,
|
|
5
|
+
} from "@uploadthing/react"
|
|
6
|
+
|
|
7
|
+
import type { OurFileRouter } from "@/app/api/uploadthing/core"
|
|
8
|
+
|
|
9
|
+
export const UploadButton = generateUploadButton<OurFileRouter>()
|
|
10
|
+
export const UploadDropzone = generateUploadDropzone<OurFileRouter>()
|
|
11
|
+
export const useUploadThing = generateUploader<OurFileRouter>()
|
package/package.json
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shadospace/editor",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"private": false,
|
|
6
6
|
"files": [
|
|
7
7
|
"scripts/init.js",
|
|
8
|
-
"components/editor"
|
|
8
|
+
"components/editor",
|
|
9
|
+
"lib/uploadthing.ts",
|
|
10
|
+
"app/api/uploadthing"
|
|
9
11
|
],
|
|
10
12
|
"publishConfig": {
|
|
11
13
|
"access": "public"
|
|
@@ -20,20 +22,35 @@
|
|
|
20
22
|
"postinstall": "node scripts/init.js"
|
|
21
23
|
},
|
|
22
24
|
"dependencies": {
|
|
25
|
+
"@floating-ui/react": "^0.27.19",
|
|
23
26
|
"@hookform/resolvers": "^5.2.2",
|
|
24
27
|
"@neondatabase/serverless": "^1.1.0",
|
|
28
|
+
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
|
29
|
+
"@radix-ui/react-popover": "^1.1.15",
|
|
30
|
+
"@tiptap/core": "^3.23.1",
|
|
25
31
|
"@tiptap/extension-code-block-lowlight": "^3.23.1",
|
|
32
|
+
"@tiptap/extension-file-handler": "^3.23.1",
|
|
33
|
+
"@tiptap/extension-highlight": "^3.23.1",
|
|
34
|
+
"@tiptap/extension-horizontal-rule": "^3.23.1",
|
|
26
35
|
"@tiptap/extension-image": "^3.23.1",
|
|
36
|
+
"@tiptap/extension-list": "^3.23.1",
|
|
37
|
+
"@tiptap/extension-subscript": "^3.23.1",
|
|
38
|
+
"@tiptap/extension-superscript": "^3.23.1",
|
|
27
39
|
"@tiptap/extension-table": "^3.23.1",
|
|
40
|
+
"@tiptap/extension-text-align": "^3.23.1",
|
|
28
41
|
"@tiptap/extension-text-style": "^3.23.1",
|
|
42
|
+
"@tiptap/extension-typography": "^3.23.1",
|
|
43
|
+
"@tiptap/extensions": "^3.23.1",
|
|
29
44
|
"@tiptap/pm": "^3.23.1",
|
|
30
45
|
"@tiptap/react": "^3.23.1",
|
|
31
46
|
"@tiptap/starter-kit": "^3.23.1",
|
|
47
|
+
"@uploadthing/react": "^7.3.3",
|
|
32
48
|
"better-auth": "^1.6.9",
|
|
33
49
|
"class-variance-authority": "^0.7.1",
|
|
34
50
|
"clsx": "^2.1.1",
|
|
35
51
|
"dotenv": "^17.4.2",
|
|
36
52
|
"drizzle-orm": "^0.45.2",
|
|
53
|
+
"lodash.throttle": "^4.1.1",
|
|
37
54
|
"lucide-react": "^1.14.0",
|
|
38
55
|
"next": "16.1.7",
|
|
39
56
|
"next-themes": "^0.4.6",
|
|
@@ -42,15 +59,19 @@
|
|
|
42
59
|
"react": "^19.2.4",
|
|
43
60
|
"react-dom": "^19.2.4",
|
|
44
61
|
"react-hook-form": "^7.75.0",
|
|
62
|
+
"react-hotkeys-hook": "^5.3.2",
|
|
45
63
|
"shadcn": "^4.7.0",
|
|
46
64
|
"sonner": "^2.0.7",
|
|
47
65
|
"tailwind-merge": "^3.5.0",
|
|
48
66
|
"tw-animate-css": "^1.4.0",
|
|
67
|
+
"uploadthing": "^7.7.4",
|
|
49
68
|
"zod": "^4.4.3"
|
|
50
69
|
},
|
|
51
70
|
"devDependencies": {
|
|
71
|
+
"@base-ui/react": "^1.4.1",
|
|
52
72
|
"@eslint/eslintrc": "^3",
|
|
53
73
|
"@tailwindcss/postcss": "^4.2.1",
|
|
74
|
+
"@types/lodash.throttle": "^4.1.9",
|
|
54
75
|
"@types/node": "^25.5.0",
|
|
55
76
|
"@types/pg": "^8.20.0",
|
|
56
77
|
"@types/react": "^19.2.14",
|
|
@@ -61,6 +82,7 @@
|
|
|
61
82
|
"postcss": "^8",
|
|
62
83
|
"prettier": "^3.8.1",
|
|
63
84
|
"prettier-plugin-tailwindcss": "^0.7.2",
|
|
85
|
+
"sass": "^1.99.0",
|
|
64
86
|
"tailwindcss": "^4.2.1",
|
|
65
87
|
"tsx": "^4.21.0",
|
|
66
88
|
"typescript": "^5.9.3"
|
package/scripts/init.js
CHANGED
|
@@ -79,6 +79,21 @@ try {
|
|
|
79
79
|
}
|
|
80
80
|
});
|
|
81
81
|
|
|
82
|
+
// Copy UploadThing setup files
|
|
83
|
+
const uploadthingFiles = [
|
|
84
|
+
{ src: path.join(__dirname, '../lib/uploadthing.ts'), dest: path.join(baseTargetDir, 'lib/uploadthing.ts') },
|
|
85
|
+
{ src: path.join(__dirname, '../app/api/uploadthing/core.ts'), dest: path.join(baseTargetDir, 'app/api/uploadthing/core.ts') },
|
|
86
|
+
{ src: path.join(__dirname, '../app/api/uploadthing/route.ts'), dest: path.join(baseTargetDir, 'app/api/uploadthing/route.ts') }
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
uploadthingFiles.forEach(file => {
|
|
90
|
+
if (fs.existsSync(file.src)) {
|
|
91
|
+
copyFile(file.src, file.dest);
|
|
92
|
+
} else {
|
|
93
|
+
console.error(`[tiptap-starter] Source file missing: ${file.src}`);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
82
97
|
// Update globals.css
|
|
83
98
|
const possiblePaths = [
|
|
84
99
|
path.join(targetDir, 'app/globals.css'),
|