@shadospace/editor 1.0.3 → 1.0.4
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 +43 -0
- package/app/api/uploadthing/route.ts +11 -0
- package/components/editor/menu-bar.tsx +63 -37
- package/lib/uploadthing.ts +11 -0
- package/package.json +24 -2
- package/scripts/init.js +15 -0
|
@@ -0,0 +1,43 @@
|
|
|
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
|
+
// Set permissions and file types for this FileRoute
|
|
22
|
+
.middleware(async ({ req }) => {
|
|
23
|
+
// This code runs on your server before upload
|
|
24
|
+
const user = await auth(req)
|
|
25
|
+
|
|
26
|
+
// If you throw, the user will not be able to upload
|
|
27
|
+
if (!user) throw new UploadThingError("Unauthorized")
|
|
28
|
+
|
|
29
|
+
// Whatever is returned here is accessible in onUploadComplete as `metadata`
|
|
30
|
+
return { userId: user.id }
|
|
31
|
+
})
|
|
32
|
+
.onUploadComplete(async ({ metadata, file }) => {
|
|
33
|
+
// This code RUNS ON YOUR SERVER after upload
|
|
34
|
+
console.log("Upload complete for userId:", metadata.userId)
|
|
35
|
+
|
|
36
|
+
console.log("file url", file.ufsUrl)
|
|
37
|
+
|
|
38
|
+
// !!! Whatever is returned here is sent to the clientside `onClientUploadComplete` callback
|
|
39
|
+
return { uploadedBy: metadata.userId }
|
|
40
|
+
}),
|
|
41
|
+
} satisfies FileRouter
|
|
42
|
+
|
|
43
|
+
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
|
+
})
|
|
@@ -4,10 +4,13 @@ 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,
|
|
12
15
|
List,
|
|
13
16
|
ListOrdered,
|
|
@@ -21,6 +24,14 @@ import {
|
|
|
21
24
|
Undo2,
|
|
22
25
|
} from "lucide-react"
|
|
23
26
|
import { Button } from "../ui/button"
|
|
27
|
+
import {
|
|
28
|
+
DropdownMenu,
|
|
29
|
+
DropdownMenuContent,
|
|
30
|
+
DropdownMenuItem,
|
|
31
|
+
DropdownMenuTrigger,
|
|
32
|
+
} from "../ui/dropdown-menu"
|
|
33
|
+
import { UploadButton } from "@/lib/uploadthing"
|
|
34
|
+
import { toast } from "sonner"
|
|
24
35
|
|
|
25
36
|
export const MenuBar = ({ editor }: { editor: Editor }) => {
|
|
26
37
|
const editorState = useEditorState({
|
|
@@ -32,41 +43,41 @@ export const MenuBar = ({ editor }: { editor: Editor }) => {
|
|
|
32
43
|
<div>
|
|
33
44
|
<div className="overflow-x-auto border bg-card">
|
|
34
45
|
<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
|
-
|
|
46
|
+
<DropdownMenu>
|
|
47
|
+
<DropdownMenuTrigger asChild>
|
|
48
|
+
<Button type="button" variant={"ghost"} aria-label="Toggle H1">
|
|
49
|
+
<Heading />
|
|
50
|
+
<ChevronDown size={0.5} />
|
|
51
|
+
</Button>
|
|
52
|
+
</DropdownMenuTrigger>
|
|
53
|
+
<DropdownMenuContent>
|
|
54
|
+
<DropdownMenuItem
|
|
55
|
+
onClick={() =>
|
|
56
|
+
editor.chain().focus().toggleHeading({ level: 1 }).run()
|
|
57
|
+
}
|
|
58
|
+
>
|
|
59
|
+
<Heading1 />
|
|
60
|
+
Heading 1
|
|
61
|
+
</DropdownMenuItem>
|
|
62
|
+
<DropdownMenuItem
|
|
63
|
+
onClick={() =>
|
|
64
|
+
editor.chain().focus().toggleHeading({ level: 2 }).run()
|
|
65
|
+
}
|
|
66
|
+
>
|
|
67
|
+
<Heading2 />
|
|
68
|
+
Heading 2
|
|
69
|
+
</DropdownMenuItem>
|
|
70
|
+
<DropdownMenuItem
|
|
71
|
+
onClick={() =>
|
|
72
|
+
editor.chain().focus().toggleHeading({ level: 3 }).run()
|
|
73
|
+
}
|
|
74
|
+
>
|
|
75
|
+
<Heading3 />
|
|
76
|
+
Heading 3
|
|
77
|
+
</DropdownMenuItem>
|
|
78
|
+
</DropdownMenuContent>
|
|
79
|
+
</DropdownMenu>
|
|
80
|
+
|
|
70
81
|
<Button
|
|
71
82
|
type="button"
|
|
72
83
|
variant={"ghost"}
|
|
@@ -101,7 +112,7 @@ export const MenuBar = ({ editor }: { editor: Editor }) => {
|
|
|
101
112
|
onClick={() => editor.chain().focus().toggleCode().run()}
|
|
102
113
|
disabled={!editorState.canCode}
|
|
103
114
|
>
|
|
104
|
-
<
|
|
115
|
+
<CodeXml />
|
|
105
116
|
</Button>
|
|
106
117
|
<Button
|
|
107
118
|
type="button"
|
|
@@ -140,6 +151,7 @@ export const MenuBar = ({ editor }: { editor: Editor }) => {
|
|
|
140
151
|
>
|
|
141
152
|
<SquareCode />
|
|
142
153
|
</Button>
|
|
154
|
+
|
|
143
155
|
<Button
|
|
144
156
|
type="button"
|
|
145
157
|
variant={"ghost"}
|
|
@@ -149,6 +161,20 @@ export const MenuBar = ({ editor }: { editor: Editor }) => {
|
|
|
149
161
|
>
|
|
150
162
|
<Quote />
|
|
151
163
|
</Button>
|
|
164
|
+
<UploadButton
|
|
165
|
+
endpoint="imageUploader"
|
|
166
|
+
onClientUploadComplete={(res) => {
|
|
167
|
+
res.forEach((item) => {
|
|
168
|
+
toast.success("Image uploaded successfully")
|
|
169
|
+
editor.chain().focus().setImage({ src: item.ufsUrl }).run()
|
|
170
|
+
})
|
|
171
|
+
}}
|
|
172
|
+
onUploadError={(error) => {
|
|
173
|
+
toast.error(`Upload failed: ${error.message}`)
|
|
174
|
+
}}
|
|
175
|
+
className="ml-2 ut-button:h-6 ut-button:w-6 ut-button:bg-card ut-button:outline-none ut-allowed-content:hidden"
|
|
176
|
+
content={{ button: <ImageIcon className="h-4 w-4" /> }}
|
|
177
|
+
/>
|
|
152
178
|
<Button
|
|
153
179
|
type="button"
|
|
154
180
|
variant={"ghost"}
|
|
@@ -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.4",
|
|
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'),
|