@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.
@@ -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
- Code,
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
- <Button
36
- type="button"
37
- variant={"ghost"}
38
- size={"icon"}
39
- aria-label="Toggle H1"
40
- onClick={() =>
41
- editor.chain().focus().toggleHeading({ level: 1 }).run()
42
- }
43
- disabled={editorState.isHeading1}
44
- >
45
- <Heading1 />
46
- </Button>
47
- <Button
48
- type="button"
49
- variant={"ghost"}
50
- size={"icon"}
51
- aria-label="Toggle H2"
52
- onClick={() =>
53
- editor.chain().focus().toggleHeading({ level: 2 }).run()
54
- }
55
- disabled={editorState.isHeading2}
56
- >
57
- <Heading2 />
58
- </Button>
59
- <Button
60
- type="button"
61
- variant={"ghost"}
62
- size={"icon"}
63
- onClick={() =>
64
- editor.chain().focus().toggleHeading({ level: 3 }).run()
65
- }
66
- disabled={editorState.isHeading3}
67
- >
68
- <Heading3 />
69
- </Button>
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
- <Code />
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",
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'),