@shadospace/editor 1.0.4 → 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.
@@ -17,6 +17,35 @@ export const ourFileRouter = {
17
17
  maxFileSize: "4MB",
18
18
  maxFileCount: 1,
19
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
+ },
20
49
  })
21
50
  // Set permissions and file types for this FileRoute
22
51
  .middleware(async ({ req }) => {
@@ -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: {
@@ -12,6 +12,7 @@ import {
12
12
  Heading3,
13
13
  ImageIcon,
14
14
  Italic,
15
+ Link,
15
16
  List,
16
17
  ListOrdered,
17
18
  Minus,
@@ -30,8 +31,20 @@ import {
30
31
  DropdownMenuItem,
31
32
  DropdownMenuTrigger,
32
33
  } from "../ui/dropdown-menu"
33
- import { UploadButton } from "@/lib/uploadthing"
34
+ import { UploadDropzone } from "@/lib/uploadthing"
34
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"
35
48
 
36
49
  export const MenuBar = ({ editor }: { editor: Editor }) => {
37
50
  const editorState = useEditorState({
@@ -151,7 +164,59 @@ export const MenuBar = ({ editor }: { editor: Editor }) => {
151
164
  >
152
165
  <SquareCode />
153
166
  </Button>
154
-
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>
155
220
  <Button
156
221
  type="button"
157
222
  variant={"ghost"}
@@ -161,20 +226,62 @@ export const MenuBar = ({ editor }: { editor: Editor }) => {
161
226
  >
162
227
  <Quote />
163
228
  </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
- />
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>
178
285
  <Button
179
286
  type="button"
180
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shadospace/editor",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "files": [