From e4eab7efba25ea20868cd8db21ed0742a349b301 Mon Sep 17 00:00:00 2001 From: juliusmarminge Date: Sun, 12 May 2024 20:58:26 +0200 Subject: [PATCH 1/8] add with-novel example --- examples/with-novel/.env.example | 3 + examples/with-novel/README.md | 27 + .../app/_styles/CalSans-SemiBold.otf | Bin 0 -> 68292 bytes examples/with-novel/app/_styles/fonts.ts | 47 + examples/with-novel/app/_styles/globals.css | 96 + .../with-novel/app/_styles/prosemirror.css | 170 ++ .../with-novel/app/api/uploadthing/route.ts | 12 + examples/with-novel/app/layout.tsx | 47 + examples/with-novel/app/menu.tsx | 57 + examples/with-novel/app/page.tsx | 37 + examples/with-novel/app/providers.tsx | 26 + examples/with-novel/editor/extensions.ts | 115 + examples/with-novel/editor/index.tsx | 210 ++ examples/with-novel/editor/node-selector.tsx | 141 ++ examples/with-novel/editor/slash-command.tsx | 160 ++ examples/with-novel/editor/text-buttons.tsx | 70 + examples/with-novel/next-env.d.ts | 5 + examples/with-novel/next.config.js | 2 + examples/with-novel/package.json | 38 + examples/with-novel/postcss.config.cjs | 5 + examples/with-novel/tailwind.config.ts | 79 + examples/with-novel/tsconfig.json | 36 + examples/with-novel/ui/button.tsx | 55 + examples/with-novel/ui/command.tsx | 185 ++ examples/with-novel/ui/dialog.tsx | 121 ++ examples/with-novel/ui/popover.tsx | 30 + examples/with-novel/uploadthing/client.ts | 6 + .../with-novel/uploadthing/novel-plugin.ts | 62 + examples/with-novel/uploadthing/server.ts | 26 + packages/react/src/useUploadThing.ts | 22 + packages/uploadthing/src/client.ts | 50 +- pnpm-lock.yaml | 1858 +++++++++++++++-- 32 files changed, 3663 insertions(+), 135 deletions(-) create mode 100644 examples/with-novel/.env.example create mode 100644 examples/with-novel/README.md create mode 100644 examples/with-novel/app/_styles/CalSans-SemiBold.otf create mode 100644 examples/with-novel/app/_styles/fonts.ts create mode 100644 examples/with-novel/app/_styles/globals.css create mode 100644 examples/with-novel/app/_styles/prosemirror.css create mode 100644 examples/with-novel/app/api/uploadthing/route.ts create mode 100644 examples/with-novel/app/layout.tsx create mode 100644 examples/with-novel/app/menu.tsx create mode 100644 examples/with-novel/app/page.tsx create mode 100644 examples/with-novel/app/providers.tsx create mode 100644 examples/with-novel/editor/extensions.ts create mode 100644 examples/with-novel/editor/index.tsx create mode 100644 examples/with-novel/editor/node-selector.tsx create mode 100644 examples/with-novel/editor/slash-command.tsx create mode 100644 examples/with-novel/editor/text-buttons.tsx create mode 100644 examples/with-novel/next-env.d.ts create mode 100644 examples/with-novel/next.config.js create mode 100644 examples/with-novel/package.json create mode 100644 examples/with-novel/postcss.config.cjs create mode 100644 examples/with-novel/tailwind.config.ts create mode 100644 examples/with-novel/tsconfig.json create mode 100644 examples/with-novel/ui/button.tsx create mode 100644 examples/with-novel/ui/command.tsx create mode 100644 examples/with-novel/ui/dialog.tsx create mode 100644 examples/with-novel/ui/popover.tsx create mode 100644 examples/with-novel/uploadthing/client.ts create mode 100644 examples/with-novel/uploadthing/novel-plugin.ts create mode 100644 examples/with-novel/uploadthing/server.ts diff --git a/examples/with-novel/.env.example b/examples/with-novel/.env.example new file mode 100644 index 0000000000..28193d9867 --- /dev/null +++ b/examples/with-novel/.env.example @@ -0,0 +1,3 @@ +# Go to https://uploadthing.com/dashboard to get your API secret +UPLOADTHING_SECRET='sk_live_xxx' +UPLOADTHING_APP_ID='xxx' \ No newline at end of file diff --git a/examples/with-novel/README.md b/examples/with-novel/README.md new file mode 100644 index 0000000000..c520ba0e00 --- /dev/null +++ b/examples/with-novel/README.md @@ -0,0 +1,27 @@ +# Integrating UploadThing with Novel + + + + + +This is a stripped down version of the Novel Web app. See the original full +source code at: https://github.com/steven-tey/novel/tree/main/apps/web + +For the UploadThing specific code in this example, see +[uploadthing/novel-plugin.ts](./uploadthing/novel-plugin.ts). + +## QuickStart + +1. Grab an API key from the UploadThing dashboard: + https://uploadthing.com/dashboard +2. `cp .env.example .env` and paste in your API key in the newly created `.env` + file +3. `pnpm i && pnpm dev` +4. Use the editor and upload files! + +## Further reference + +Check out the docs at: + +- https://docs.uploadthing.com/getting-started/appdir +- https://novel.sh/docs diff --git a/examples/with-novel/app/_styles/CalSans-SemiBold.otf b/examples/with-novel/app/_styles/CalSans-SemiBold.otf new file mode 100644 index 0000000000000000000000000000000000000000..7ad1591f120f72b52af2718e6b2231e7b8d67849 GIT binary patch literal 68292 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 label { + margin-right: 0.2rem; + user-select: none; +} + +@media screen and (max-width: 768px) { + ul[data-type="taskList"] li > label { + margin-right: 0.5rem; + } +} + +ul[data-type="taskList"] li > label input[type="checkbox"] { + -webkit-appearance: none; + appearance: none; + background-color: hsl(var(--background)); + margin: 0; + cursor: pointer; + width: 1.2em; + height: 1.2em; + position: relative; + top: 5px; + border: 2px solid hsl(var(--border)); + margin-right: 0.3rem; + display: grid; + place-content: center; + + &:hover { + background-color: hsl(var(--accent)); + } + + &:active { + background-color: hsl(var(--accent)); + } + + &::before { + content: ""; + width: 0.65em; + height: 0.65em; + transform: scale(0); + transition: 120ms transform ease-in-out; + box-shadow: inset 1em 1em; + transform-origin: center; + clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%); + } + + &:checked::before { + transform: scale(1); + } +} + +ul[data-type="taskList"] li[data-checked="true"] > div > p { + color: var(--muted-foreground); + text-decoration: line-through; + text-decoration-thickness: 2px; +} + +/* Overwrite tippy-box original max-width */ + +.tippy-box { + max-width: 400px !important; +} + +.ProseMirror:not(.dragging) .ProseMirror-selectednode { + outline: none !important; + background-color: var(--novel-highlight-blue); + transition: background-color 0.2s; + box-shadow: none; +} + +.drag-handle { + position: fixed; + opacity: 1; + transition: opacity ease-in 0.2s; + border-radius: 0.25rem; + + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 10' style='fill: rgba(0, 0, 0, 0.5)'%3E%3Cpath d='M3,2 C2.44771525,2 2,1.55228475 2,1 C2,0.44771525 2.44771525,0 3,0 C3.55228475,0 4,0.44771525 4,1 C4,1.55228475 3.55228475,2 3,2 Z M3,6 C2.44771525,6 2,5.55228475 2,5 C2,4.44771525 2.44771525,4 3,4 C3.55228475,4 4,4.44771525 4,5 C4,5.55228475 3.55228475,6 3,6 Z M3,10 C2.44771525,10 2,9.55228475 2,9 C2,8.44771525 2.44771525,8 3,8 C3.55228475,8 4,8.44771525 4,9 C4,9.55228475 3.55228475,10 3,10 Z M7,2 C6.44771525,2 6,1.55228475 6,1 C6,0.44771525 6.44771525,0 7,0 C7.55228475,0 8,0.44771525 8,1 C8,1.55228475 7.55228475,2 7,2 Z M7,6 C6.44771525,6 6,5.55228475 6,5 C6,4.44771525 6.44771525,4 7,4 C7.55228475,4 8,4.44771525 8,5 C8,5.55228475 7.55228475,6 7,6 Z M7,10 C6.44771525,10 6,9.55228475 6,9 C6,8.44771525 6.44771525,8 7,8 C7.55228475,8 8,8.44771525 8,9 C8,9.55228475 7.55228475,10 7,10 Z'%3E%3C/path%3E%3C/svg%3E"); + background-size: calc(0.5em + 0.375rem) calc(0.5em + 0.375rem); + background-repeat: no-repeat; + background-position: center; + width: 1.2rem; + height: 1.5rem; + z-index: 50; + cursor: grab; + + &:hover { + background-color: var(--novel-stone-100); + transition: background-color 0.2s; + } + + &:active { + background-color: var(--novel-stone-200); + transition: background-color 0.2s; + cursor: grabbing; + } + + &.hide { + opacity: 0; + pointer-events: none; + } + + @media screen and (max-width: 600px) { + display: none; + pointer-events: none; + } +} + +.dark .drag-handle { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 10' style='fill: rgba(255, 255, 255, 0.5)'%3E%3Cpath d='M3,2 C2.44771525,2 2,1.55228475 2,1 C2,0.44771525 2.44771525,0 3,0 C3.55228475,0 4,0.44771525 4,1 C4,1.55228475 3.55228475,2 3,2 Z M3,6 C2.44771525,6 2,5.55228475 2,5 C2,4.44771525 2.44771525,4 3,4 C3.55228475,4 4,4.44771525 4,5 C4,5.55228475 3.55228475,6 3,6 Z M3,10 C2.44771525,10 2,9.55228475 2,9 C2,8.44771525 2.44771525,8 3,8 C3.55228475,8 4,8.44771525 4,9 C4,9.55228475 3.55228475,10 3,10 Z M7,2 C6.44771525,2 6,1.55228475 6,1 C6,0.44771525 6.44771525,0 7,0 C7.55228475,0 8,0.44771525 8,1 C8,1.55228475 7.55228475,2 7,2 Z M7,6 C6.44771525,6 6,5.55228475 6,5 C6,4.44771525 6.44771525,4 7,4 C7.55228475,4 8,4.44771525 8,5 C8,5.55228475 7.55228475,6 7,6 Z M7,10 C6.44771525,10 6,9.55228475 6,9 C6,8.44771525 6.44771525,8 7,8 C7.55228475,8 8,8.44771525 8,9 C8,9.55228475 7.55228475,10 7,10 Z'%3E%3C/path%3E%3C/svg%3E"); +} diff --git a/examples/with-novel/app/api/uploadthing/route.ts b/examples/with-novel/app/api/uploadthing/route.ts new file mode 100644 index 0000000000..8b54d8b722 --- /dev/null +++ b/examples/with-novel/app/api/uploadthing/route.ts @@ -0,0 +1,12 @@ +import { uploadRouter } from "@/uploadthing/server"; + +import { createRouteHandler } from "uploadthing/next"; + +export const runtime = "edge"; + +export const { GET, POST } = createRouteHandler({ + router: uploadRouter, + config: { + logLevel: "debug", + }, +}); diff --git a/examples/with-novel/app/layout.tsx b/examples/with-novel/app/layout.tsx new file mode 100644 index 0000000000..64c8f36576 --- /dev/null +++ b/examples/with-novel/app/layout.tsx @@ -0,0 +1,47 @@ +import "./_styles/globals.css"; +import "./_styles/prosemirror.css"; + +import type { ReactNode } from "react"; +import type { Metadata, Viewport } from "next"; +import { uploadRouter } from "@/uploadthing/server"; + +import { NextSSRPlugin } from "@uploadthing/react/next-ssr-plugin"; +import { extractRouterConfig } from "uploadthing/server"; + +import Providers from "./providers"; + +const title = + "Novel - Notion-style WYSIWYG editor with AI-powered autocompletions"; +const description = + "Novel is a Notion-style WYSIWYG editor with AI-powered autocompletions. Built with Tiptap, OpenAI, and Vercel AI SDK."; + +export const metadata: Metadata = { + title, + description, + openGraph: { + title, + description, + }, + twitter: { + title, + description, + card: "summary_large_image", + creator: "@steventey", + }, + metadataBase: new URL("https://novel.sh"), +}; + +export const viewport: Viewport = { + themeColor: "#ffffff", +}; + +export default function RootLayout({ children }: { children: ReactNode }) { + return ( + + + + {children} + + + ); +} diff --git a/examples/with-novel/app/menu.tsx b/examples/with-novel/app/menu.tsx new file mode 100644 index 0000000000..f6e3ee12dd --- /dev/null +++ b/examples/with-novel/app/menu.tsx @@ -0,0 +1,57 @@ +"use client"; + +import { Button } from "@/ui/button"; +import { Popover, PopoverContent, PopoverTrigger } from "@/ui/popover"; +import { Check, Menu as MenuIcon, Monitor, Moon, SunDim } from "lucide-react"; +import { useTheme } from "next-themes"; + +const appearances = [ + { + theme: "System", + icon: , + }, + { + theme: "Light", + icon: , + }, + { + theme: "Dark", + icon: , + }, +]; +export function Menu() { + const { theme: currentTheme, setTheme } = useTheme(); + + return ( + + + + + +

+ Appearance +

+ {appearances.map(({ theme, icon }) => ( + + ))} +
+
+ ); +} diff --git a/examples/with-novel/app/page.tsx b/examples/with-novel/app/page.tsx new file mode 100644 index 0000000000..6300b48d8e --- /dev/null +++ b/examples/with-novel/app/page.tsx @@ -0,0 +1,37 @@ +import { Menu } from "@/app/menu"; +import { AdvancedEditor } from "@/editor"; +import { buttonVariants } from "@/ui/button"; +import { Github } from "lucide-react"; +import { twMerge } from "tailwind-merge"; + +export default function Page() { + return ( + + ); +} diff --git a/examples/with-novel/app/providers.tsx b/examples/with-novel/app/providers.tsx new file mode 100644 index 0000000000..55ddb5e77c --- /dev/null +++ b/examples/with-novel/app/providers.tsx @@ -0,0 +1,26 @@ +"use client"; + +import { type ReactNode } from "react"; +import { ThemeProvider, useTheme } from "next-themes"; +import { Toaster } from "sonner"; + +const ToasterProvider = () => { + const { theme } = useTheme() as { + theme: "light" | "dark" | "system"; + }; + return ; +}; + +export default function Providers({ children }: { children: ReactNode }) { + return ( + + + {children} + + ); +} diff --git a/examples/with-novel/editor/extensions.ts b/examples/with-novel/editor/extensions.ts new file mode 100644 index 0000000000..1b25b235ae --- /dev/null +++ b/examples/with-novel/editor/extensions.ts @@ -0,0 +1,115 @@ +import { cx } from "class-variance-authority"; +import { + HorizontalRule, + Placeholder, + StarterKit, + TaskItem, + TaskList, + TiptapImage, + TiptapLink, + UpdatedImage, +} from "novel/extensions"; +import { UploadImagesPlugin } from "novel/plugins"; + +//TODO I am using cx here to get tailwind autocomplete working, idk if someone else can write a regex to just capture the class key in objects +//You can overwrite the placeholder with your own configuration +const placeholder = Placeholder; +const tiptapLink = TiptapLink.configure({ + HTMLAttributes: { + class: cx( + "text-muted-foreground underline underline-offset-[3px] hover:text-primary transition-colors cursor-pointer", + ), + }, +}); + +const tiptapImage = TiptapImage.extend({ + addProseMirrorPlugins() { + return [ + UploadImagesPlugin({ + imageClass: cx("opacity-40 rounded-lg border border-stone-200"), + }), + ]; + }, +}).configure({ + allowBase64: true, + HTMLAttributes: { + class: cx("rounded-lg border border-muted"), + }, +}); + +const updatedImage = UpdatedImage.configure({ + HTMLAttributes: { + class: cx("rounded-lg border border-muted"), + }, +}); + +const taskList = TaskList.configure({ + HTMLAttributes: { + class: cx("not-prose pl-2 "), + }, +}); +const taskItem = TaskItem.configure({ + HTMLAttributes: { + class: cx("flex gap-2 items-start my-4"), + }, + nested: true, +}); + +const horizontalRule = HorizontalRule.configure({ + HTMLAttributes: { + class: cx("mt-4 mb-6 border-t border-muted-foreground"), + }, +}); + +const starterKit = StarterKit.configure({ + bulletList: { + HTMLAttributes: { + class: cx("list-disc list-outside leading-3 -mt-2"), + }, + }, + orderedList: { + HTMLAttributes: { + class: cx("list-decimal list-outside leading-3 -mt-2"), + }, + }, + listItem: { + HTMLAttributes: { + class: cx("leading-normal -mb-2"), + }, + }, + blockquote: { + HTMLAttributes: { + class: cx("border-l-4 border-primary"), + }, + }, + codeBlock: { + HTMLAttributes: { + class: cx( + "rounded-md bg-muted text-muted-foreground border p-5 font-mono font-medium", + ), + }, + }, + code: { + HTMLAttributes: { + class: cx("rounded-md bg-muted px-1.5 py-1 font-mono font-medium"), + spellcheck: "false", + }, + }, + horizontalRule: false, + dropcursor: { + color: "#DBEAFE", + width: 4, + }, + gapcursor: false, +}); + +export const defaultExtensions = [ + starterKit, + placeholder, + tiptapLink, + tiptapImage, + updatedImage, + taskList, + taskItem, + horizontalRule, +]; diff --git a/examples/with-novel/editor/index.tsx b/examples/with-novel/editor/index.tsx new file mode 100644 index 0000000000..a184ffbb8b --- /dev/null +++ b/examples/with-novel/editor/index.tsx @@ -0,0 +1,210 @@ +"use client"; + +import React, { useEffect, useState } from "react"; +import { uploadFn } from "@/uploadthing/novel-plugin"; +import { + EditorCommand, + EditorCommandEmpty, + EditorCommandItem, + EditorCommandList, + EditorContent, + EditorInstance, + EditorRoot, + type JSONContent, +} from "novel"; +import { handleCommandNavigation, ImageResizer } from "novel/extensions"; +import { handleImageDrop, handleImagePaste } from "novel/plugins"; +import { useDebouncedCallback } from "use-debounce"; + +import { defaultExtensions } from "./extensions"; +import { slashCommand, suggestionItems } from "./slash-command"; + +const extensions = [...defaultExtensions, slashCommand]; + +export const AdvancedEditor = () => { + const [initialContent, setInitialContent] = useState( + null, + ); + const [saveStatus, setSaveStatus] = useState("Saved"); + + const debouncedUpdates = useDebouncedCallback( + async (editor: EditorInstance) => { + const json = editor.getJSON(); + + window.localStorage.setItem("novel-content", JSON.stringify(json)); + setSaveStatus("Saved"); + }, + 500, + ); + + useEffect(() => { + const content = window.localStorage.getItem("novel-content"); + if (content) setInitialContent(JSON.parse(content)); + else setInitialContent(defaultEditorContent); + }, []); + + if (!initialContent) return null; + + return ( +
+
+ {saveStatus} +
+ + handleCommandNavigation(event), + }, + handlePaste: (view, event) => + handleImagePaste(view, event, uploadFn), + handleDrop: (view, event, _slice, moved) => + handleImageDrop(view, event, moved, uploadFn), + attributes: { + class: `prose prose-lg dark:prose-invert prose-headings:font-title font-default focus:outline-none max-w-full`, + }, + }} + onUpdate={({ editor }) => { + debouncedUpdates(editor); + setSaveStatus("Unsaved"); + }} + slotAfter={} + > + + + No results + + + {suggestionItems.map((item) => ( + item.command?.(val)} + className={`hover:bg-accent aria-selected:bg-accent flex w-full items-center space-x-2 rounded-md px-2 py-1 text-left text-sm `} + key={item.title} + > +
+ {item.icon} +
+
+

{item.title}

+

+ {item.description} +

+
+
+ ))} +
+
+
+
+
+ ); +}; + +const defaultEditorContent = { + type: "doc", + content: [ + { + type: "heading", + attrs: { level: 2 }, + content: [{ type: "text", text: "Novel x UploadThing" }], + }, + { + type: "paragraph", + content: [ + { + type: "text", + text: "This is a stripped down example based on ", + }, + { + type: "text", + marks: [ + { + type: "link", + attrs: { + href: "https://github.com/steven-tey/novel/apps/web", + target: "_blank", + }, + }, + ], + text: "the official Novel web app", + }, + { + type: "text", + text: " showing how to integrate it with UploadThing.", + }, + ], + }, + { + type: "heading", + attrs: { level: 3 }, + content: [{ type: "text", text: "Installation" }], + }, + { + type: "codeBlock", + attrs: { language: null }, + content: [ + { type: "text", text: "npm i novel uploadthing @uploadthing/react" }, + ], + }, + { + type: "heading", + attrs: { level: 3 }, + content: [{ type: "text", text: "Try uploading something by" }], + }, + { + type: "taskList", + content: [ + { + type: "taskItem", + attrs: { checked: false }, + content: [ + { + type: "paragraph", + content: [{ type: "text", text: "Copy-Pasting an image" }], + }, + ], + }, + { + type: "taskItem", + attrs: { checked: false }, + content: [ + { + type: "paragraph", + content: [{ type: "text", text: "Drag and drop an image" }], + }, + ], + }, + { + type: "taskItem", + attrs: { checked: false }, + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "Using the Image command from the slash menu", + }, + ], + }, + ], + }, + ], + }, + { + type: "image", + attrs: { + src: "https://docs.uploadthing.com/og.jpg", + alt: "banner.png", + title: "banner.png", + width: null, + height: null, + }, + }, + { type: "horizontalRule" }, + ], +}; diff --git a/examples/with-novel/editor/node-selector.tsx b/examples/with-novel/editor/node-selector.tsx new file mode 100644 index 0000000000..92f6009aa7 --- /dev/null +++ b/examples/with-novel/editor/node-selector.tsx @@ -0,0 +1,141 @@ +import { Button } from "@/ui/button"; +import { PopoverContent, PopoverTrigger } from "@/ui/popover"; +import { Popover } from "@radix-ui/react-popover"; +import { + Check, + CheckSquare, + ChevronDown, + Code, + Heading1, + Heading2, + Heading3, + ListOrdered, + TextIcon, + TextQuote, + type LucideIcon, +} from "lucide-react"; +import { EditorBubbleItem, useEditor } from "novel"; + +type NovelEditor = NonNullable["editor"]>; + +export type SelectorItem = { + name: string; + icon: LucideIcon; + command: (editor: NovelEditor) => void; + isActive: (editor: NovelEditor) => boolean; +}; + +const items: SelectorItem[] = [ + { + name: "Text", + icon: TextIcon, + command: (editor) => editor.chain().focus().clearNodes().run(), + // I feel like there has to be a more efficient way to do this – feel free to PR if you know how! + isActive: (editor) => + editor.isActive("paragraph") && + !editor.isActive("bulletList") && + !editor.isActive("orderedList"), + }, + { + name: "Heading 1", + icon: Heading1, + command: (editor) => + editor.chain().focus().clearNodes().toggleHeading({ level: 1 }).run(), + isActive: (editor) => editor.isActive("heading", { level: 1 }), + }, + { + name: "Heading 2", + icon: Heading2, + command: (editor) => + editor.chain().focus().clearNodes().toggleHeading({ level: 2 }).run(), + isActive: (editor) => editor.isActive("heading", { level: 2 }), + }, + { + name: "Heading 3", + icon: Heading3, + command: (editor) => + editor.chain().focus().clearNodes().toggleHeading({ level: 3 }).run(), + isActive: (editor) => editor.isActive("heading", { level: 3 }), + }, + { + name: "To-do List", + icon: CheckSquare, + command: (editor) => + editor.chain().focus().clearNodes().toggleTaskList().run(), + isActive: (editor) => editor.isActive("taskItem"), + }, + { + name: "Bullet List", + icon: ListOrdered, + command: (editor) => + editor.chain().focus().clearNodes().toggleBulletList().run(), + isActive: (editor) => editor.isActive("bulletList"), + }, + { + name: "Numbered List", + icon: ListOrdered, + command: (editor) => + editor.chain().focus().clearNodes().toggleOrderedList().run(), + isActive: (editor) => editor.isActive("orderedList"), + }, + { + name: "Quote", + icon: TextQuote, + command: (editor) => + editor.chain().focus().clearNodes().toggleBlockquote().run(), + isActive: (editor) => editor.isActive("blockquote"), + }, + { + name: "Code", + icon: Code, + command: (editor) => + editor.chain().focus().clearNodes().toggleCodeBlock().run(), + isActive: (editor) => editor.isActive("codeBlock"), + }, +]; +interface NodeSelectorProps { + open: boolean; + onOpenChange: (open: boolean) => void; +} + +export const NodeSelector = ({ open, onOpenChange }: NodeSelectorProps) => { + const { editor } = useEditor(); + if (!editor) return null; + const activeItem = items.filter((item) => item.isActive(editor)).pop() ?? { + name: "Multiple", + }; + + return ( + + + + + + {items.map((item, index) => ( + { + item.command(editor); + onOpenChange(false); + }} + className="hover:bg-accent flex cursor-pointer items-center justify-between rounded-sm px-2 py-1 text-sm" + > +
+
+ +
+ {item.name} +
+ {activeItem.name === item.name && } +
+ ))} +
+
+ ); +}; diff --git a/examples/with-novel/editor/slash-command.tsx b/examples/with-novel/editor/slash-command.tsx new file mode 100644 index 0000000000..1be463de30 --- /dev/null +++ b/examples/with-novel/editor/slash-command.tsx @@ -0,0 +1,160 @@ +import { uploadFn } from "@/uploadthing/novel-plugin"; +import { + CheckSquare, + Code, + Heading1, + Heading2, + Heading3, + ImageIcon, + List, + ListOrdered, + MessageSquarePlus, + Text, + TextQuote, +} from "lucide-react"; +import { Command, createSuggestionItems, renderItems } from "novel/extensions"; + +export const suggestionItems = createSuggestionItems([ + { + title: "Send Feedback", + description: "Let us know how we can improve.", + icon: , + command: ({ editor, range }) => { + editor.chain().focus().deleteRange(range).run(); + window.open("/feedback", "_blank"); + }, + }, + { + title: "Text", + description: "Just start typing with plain text.", + searchTerms: ["p", "paragraph"], + icon: , + command: ({ editor, range }) => { + editor + .chain() + .focus() + .deleteRange(range) + .toggleNode("paragraph", "paragraph") + .run(); + }, + }, + { + title: "To-do List", + description: "Track tasks with a to-do list.", + searchTerms: ["todo", "task", "list", "check", "checkbox"], + icon: , + command: ({ editor, range }) => { + editor.chain().focus().deleteRange(range).toggleTaskList().run(); + }, + }, + { + title: "Heading 1", + description: "Big section heading.", + searchTerms: ["title", "big", "large"], + icon: , + command: ({ editor, range }) => { + editor + .chain() + .focus() + .deleteRange(range) + .setNode("heading", { level: 1 }) + .run(); + }, + }, + { + title: "Heading 2", + description: "Medium section heading.", + searchTerms: ["subtitle", "medium"], + icon: , + command: ({ editor, range }) => { + editor + .chain() + .focus() + .deleteRange(range) + .setNode("heading", { level: 2 }) + .run(); + }, + }, + { + title: "Heading 3", + description: "Small section heading.", + searchTerms: ["subtitle", "small"], + icon: , + command: ({ editor, range }) => { + editor + .chain() + .focus() + .deleteRange(range) + .setNode("heading", { level: 3 }) + .run(); + }, + }, + { + title: "Bullet List", + description: "Create a simple bullet list.", + searchTerms: ["unordered", "point"], + icon: , + command: ({ editor, range }) => { + editor.chain().focus().deleteRange(range).toggleBulletList().run(); + }, + }, + { + title: "Numbered List", + description: "Create a list with numbering.", + searchTerms: ["ordered"], + icon: , + command: ({ editor, range }) => { + editor.chain().focus().deleteRange(range).toggleOrderedList().run(); + }, + }, + { + title: "Quote", + description: "Capture a quote.", + searchTerms: ["blockquote"], + icon: , + command: ({ editor, range }) => + editor + .chain() + .focus() + .deleteRange(range) + .toggleNode("paragraph", "paragraph") + .toggleBlockquote() + .run(), + }, + { + title: "Code", + description: "Capture a code snippet.", + searchTerms: ["codeblock"], + icon: , + command: ({ editor, range }) => + editor.chain().focus().deleteRange(range).toggleCodeBlock().run(), + }, + { + title: "Image", + description: "Upload an image from your computer.", + searchTerms: ["photo", "picture", "media"], + icon: , + command: ({ editor, range }) => { + editor.chain().focus().deleteRange(range).run(); + // upload image + const input = document.createElement("input"); + input.type = "file"; + input.accept = "image/*"; + input.onchange = async () => { + if (input.files?.length) { + const file = input.files[0]; + const pos = editor.view.state.selection.from; + uploadFn(file!, editor.view, pos); + } + }; + input.click(); + }, + }, +]); + +export const slashCommand = Command.configure({ + suggestion: { + items: () => suggestionItems, + render: renderItems, + }, +}); diff --git a/examples/with-novel/editor/text-buttons.tsx b/examples/with-novel/editor/text-buttons.tsx new file mode 100644 index 0000000000..baf1223af9 --- /dev/null +++ b/examples/with-novel/editor/text-buttons.tsx @@ -0,0 +1,70 @@ +import { Button } from "@/ui/button"; +import { + BoldIcon, + CodeIcon, + ItalicIcon, + StrikethroughIcon, + UnderlineIcon, +} from "lucide-react"; +import { EditorBubbleItem, useEditor } from "novel"; +import { twMerge } from "tailwind-merge"; + +import type { SelectorItem } from "./node-selector"; + +export const TextButtons = () => { + const { editor } = useEditor(); + if (!editor) return null; + const items: SelectorItem[] = [ + { + name: "bold", + isActive: (editor) => editor.isActive("bold"), + command: (editor) => editor.chain().focus().toggleBold().run(), + icon: BoldIcon, + }, + { + name: "italic", + isActive: (editor) => editor.isActive("italic"), + command: (editor) => editor.chain().focus().toggleItalic().run(), + icon: ItalicIcon, + }, + { + name: "underline", + isActive: (editor) => editor.isActive("underline"), + command: (editor) => editor.chain().focus().toggleUnderline().run(), + icon: UnderlineIcon, + }, + { + name: "strike", + isActive: (editor) => editor.isActive("strike"), + command: (editor) => editor.chain().focus().toggleStrike().run(), + icon: StrikethroughIcon, + }, + { + name: "code", + isActive: (editor) => editor.isActive("code"), + command: (editor) => editor.chain().focus().toggleCode().run(), + icon: CodeIcon, + }, + ]; + return ( +
+ {items.map((item, index) => ( + { + item.command(editor); + }} + > + + + ))} +
+ ); +}; diff --git a/examples/with-novel/next-env.d.ts b/examples/with-novel/next-env.d.ts new file mode 100644 index 0000000000..4f11a03dc6 --- /dev/null +++ b/examples/with-novel/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/examples/with-novel/next.config.js b/examples/with-novel/next.config.js new file mode 100644 index 0000000000..c4a407b7ce --- /dev/null +++ b/examples/with-novel/next.config.js @@ -0,0 +1,2 @@ +/** @type {import('next').NextConfig} */ +export default {}; diff --git a/examples/with-novel/package.json b/examples/with-novel/package.json new file mode 100644 index 0000000000..dbe54b2a1f --- /dev/null +++ b/examples/with-novel/package.json @@ -0,0 +1,38 @@ +{ + "name": "with-novel", + "type": "module", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@radix-ui/react-dialog": "^1.0.5", + "@radix-ui/react-popover": "^1.0.6", + "@radix-ui/react-slot": "^1.0.2", + "@tailwindcss/typography": "^0.5.10", + "@uploadthing/react": "^6.5.3", + "class-variance-authority": "^0.7.0", + "cmdk": "^0.2.1", + "lucide-react": "^0.368.0", + "next": "14.2.1", + "next-themes": "^0.3.0", + "novel": "^0.3.1", + "react": "18.2.0", + "react-dom": "18.2.0", + "sonner": "^1.4.41", + "tailwind-merge": "^2.2.1", + "uploadthing": "6.10.3", + "use-debounce": "^9.0.3" + }, + "devDependencies": { + "@types/node": "^20.11.21", + "@types/react": "18.2.78", + "@types/react-dom": "18.2.25", + "tailwindcss": "^3.4.1", + "tailwindcss-animate": "^1.0.7", + "typescript": "^5.4.5" + } +} diff --git a/examples/with-novel/postcss.config.cjs b/examples/with-novel/postcss.config.cjs new file mode 100644 index 0000000000..ee5f90b309 --- /dev/null +++ b/examples/with-novel/postcss.config.cjs @@ -0,0 +1,5 @@ +module.exports = { + plugins: { + tailwindcss: {}, + }, +}; diff --git a/examples/with-novel/tailwind.config.ts b/examples/with-novel/tailwind.config.ts new file mode 100644 index 0000000000..c309c004b7 --- /dev/null +++ b/examples/with-novel/tailwind.config.ts @@ -0,0 +1,79 @@ +import typography from "@tailwindcss/typography"; +import type { Config } from "tailwindcss"; +import animate from "tailwindcss-animate"; + +export default { + darkMode: ["class"], + content: [ + "./app/**/*.{ts,tsx}", + "./editor/**/*.{ts,tsx}", + "./ui/**/*.{ts,tsx}", + ], + prefix: "", + theme: { + container: { + center: true, + padding: "2rem", + screens: { + "2xl": "1400px", + }, + }, + extend: { + colors: { + border: "hsl(var(--border))", + input: "hsl(var(--input))", + ring: "hsl(var(--ring))", + background: "hsl(var(--background))", + foreground: "hsl(var(--foreground))", + primary: { + DEFAULT: "hsl(var(--primary))", + foreground: "hsl(var(--primary-foreground))", + }, + secondary: { + DEFAULT: "hsl(var(--secondary))", + foreground: "hsl(var(--secondary-foreground))", + }, + destructive: { + DEFAULT: "hsl(var(--destructive))", + foreground: "hsl(var(--destructive-foreground))", + }, + muted: { + DEFAULT: "hsl(var(--muted))", + foreground: "hsl(var(--muted-foreground))", + }, + accent: { + DEFAULT: "hsl(var(--accent))", + foreground: "hsl(var(--accent-foreground))", + }, + popover: { + DEFAULT: "hsl(var(--popover))", + foreground: "hsl(var(--popover-foreground))", + }, + card: { + DEFAULT: "hsl(var(--card))", + foreground: "hsl(var(--card-foreground))", + }, + }, + borderRadius: { + lg: "var(--radius)", + md: "calc(var(--radius) - 2px)", + sm: "calc(var(--radius) - 4px)", + }, + keyframes: { + "accordion-down": { + from: { height: "0" }, + to: { height: "var(--radix-accordion-content-height)" }, + }, + "accordion-up": { + from: { height: "var(--radix-accordion-content-height)" }, + to: { height: "0" }, + }, + }, + animation: { + "accordion-down": "accordion-down 0.2s ease-out", + "accordion-up": "accordion-up 0.2s ease-out", + }, + }, + }, + plugins: [animate, typography], +} satisfies Config; diff --git a/examples/with-novel/tsconfig.json b/examples/with-novel/tsconfig.json new file mode 100644 index 0000000000..cd9852d0ad --- /dev/null +++ b/examples/with-novel/tsconfig.json @@ -0,0 +1,36 @@ +{ + "compilerOptions": { + /* Base Options: */ + "skipLibCheck": true, + "target": "es2022", + "allowJs": true, + "moduleDetection": "force", + "isolatedModules": true, + + /* Strictness */ + "strict": true, + "noUncheckedIndexedAccess": true, + "checkJs": true, + + /* Bundled projects */ + "lib": ["dom", "dom.iterable", "ES2022"], + "noEmit": true, + "module": "Preserve", + "jsx": "preserve", + "plugins": [{ "name": "next" }], + "incremental": true, + + /* Path Aliases */ + "paths": { + "@/*": ["./*"] + } + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + "*.js", + ".next/types/**/*.ts" + ], + "exclude": ["node_modules"] +} diff --git a/examples/with-novel/ui/button.tsx b/examples/with-novel/ui/button.tsx new file mode 100644 index 0000000000..55bec375ed --- /dev/null +++ b/examples/with-novel/ui/button.tsx @@ -0,0 +1,55 @@ +import * as React from "react"; +import { Slot } from "@radix-ui/react-slot"; +import { cva, type VariantProps } from "class-variance-authority"; +import { twMerge } from "tailwind-merge"; + +const buttonVariants = cva( + "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground hover:bg-destructive/90", + outline: + "border border-input bg-background hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-10 px-4 py-2", + sm: "h-9 rounded-md px-3", + lg: "h-11 rounded-md px-8", + icon: "h-10 w-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + }, +); + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean; +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button"; + return ( + + ); + }, +); +Button.displayName = "Button"; + +export { Button, buttonVariants }; diff --git a/examples/with-novel/ui/command.tsx b/examples/with-novel/ui/command.tsx new file mode 100644 index 0000000000..d25bfcae0d --- /dev/null +++ b/examples/with-novel/ui/command.tsx @@ -0,0 +1,185 @@ +"use client"; + +import * as React from "react"; +import { Dialog, DialogContent } from "@/ui/dialog"; +import { type DialogProps } from "@radix-ui/react-dialog"; +import { Command as CommandPrimitive } from "cmdk"; +import { twMerge } from "tailwind-merge"; + +const Magic = ({ className }: { className: string }) => ( + + + + + +); + +const Command = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +Command.displayName = CommandPrimitive.displayName; + +interface CommandDialogProps extends DialogProps {} + +const CommandDialog = ({ children, ...props }: CommandDialogProps) => { + return ( + + + + {children} + + + + ); +}; + +const CommandInput = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( +
+ + +
+)); + +CommandInput.displayName = CommandPrimitive.Input.displayName; + +const CommandList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); + +CommandList.displayName = CommandPrimitive.List.displayName; + +const CommandEmpty = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>((props, ref) => ( + +)); + +CommandEmpty.displayName = CommandPrimitive.Empty.displayName; + +const CommandGroup = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); + +CommandGroup.displayName = CommandPrimitive.Group.displayName; + +const CommandSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +CommandSeparator.displayName = CommandPrimitive.Separator.displayName; + +const CommandItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); + +CommandItem.displayName = CommandPrimitive.Item.displayName; + +const CommandShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => { + return ( + + ); +}; +CommandShortcut.displayName = "CommandShortcut"; + +export { + Command, + CommandDialog, + CommandInput, + CommandList, + CommandEmpty, + CommandGroup, + CommandItem, + CommandShortcut, + CommandSeparator, +}; diff --git a/examples/with-novel/ui/dialog.tsx b/examples/with-novel/ui/dialog.tsx new file mode 100644 index 0000000000..5e52628d86 --- /dev/null +++ b/examples/with-novel/ui/dialog.tsx @@ -0,0 +1,121 @@ +"use client"; + +import * as React from "react"; +import * as DialogPrimitive from "@radix-ui/react-dialog"; +import { X } from "lucide-react"; +import { twMerge } from "tailwind-merge"; + +const Dialog = DialogPrimitive.Root; + +const DialogTrigger = DialogPrimitive.Trigger; + +const DialogPortal = DialogPrimitive.Portal; + +const DialogClose = DialogPrimitive.Close; + +const DialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; + +const DialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)); +DialogContent.displayName = DialogPrimitive.Content.displayName; + +const DialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +DialogHeader.displayName = "DialogHeader"; + +const DialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +DialogFooter.displayName = "DialogFooter"; + +const DialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogTitle.displayName = DialogPrimitive.Title.displayName; + +const DialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogDescription.displayName = DialogPrimitive.Description.displayName; + +export { + Dialog, + DialogPortal, + DialogOverlay, + DialogClose, + DialogTrigger, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogDescription, +}; diff --git a/examples/with-novel/ui/popover.tsx b/examples/with-novel/ui/popover.tsx new file mode 100644 index 0000000000..a38d9036d4 --- /dev/null +++ b/examples/with-novel/ui/popover.tsx @@ -0,0 +1,30 @@ +"use client"; + +import * as React from "react"; +import * as PopoverPrimitive from "@radix-ui/react-popover"; +import { twMerge } from "tailwind-merge"; + +const Popover = PopoverPrimitive.Root; + +const PopoverTrigger = PopoverPrimitive.Trigger; + +const PopoverContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( + + + +)); +PopoverContent.displayName = PopoverPrimitive.Content.displayName; + +export { Popover, PopoverTrigger, PopoverContent }; diff --git a/examples/with-novel/uploadthing/client.ts b/examples/with-novel/uploadthing/client.ts new file mode 100644 index 0000000000..2a8e4243f2 --- /dev/null +++ b/examples/with-novel/uploadthing/client.ts @@ -0,0 +1,6 @@ +import { generateReactHelpers } from "@uploadthing/react"; + +import type { UploadRouter } from "./server"; + +export const { uploadFiles, getRouteConfig } = + generateReactHelpers(); diff --git a/examples/with-novel/uploadthing/novel-plugin.ts b/examples/with-novel/uploadthing/novel-plugin.ts new file mode 100644 index 0000000000..3175fb5193 --- /dev/null +++ b/examples/with-novel/uploadthing/novel-plugin.ts @@ -0,0 +1,62 @@ +import { createImageUpload } from "novel/plugins"; +import { toast } from "sonner"; + +import { isValidFileSize, isValidFileType } from "uploadthing/client"; + +import { getRouteConfig, uploadFiles } from "./client"; + +const preloadImage = (url: string, tries = 0, maxTries = 10) => + new Promise((resolve, reject) => { + const image = new Image(); + image.src = url; + image.onload = () => resolve(url); + image.onerror = (e) => { + if (tries < maxTries) { + preloadImage(url, ++tries).then(resolve); + } else { + toast.error( + "Image was uploaded, but failed to preload. Please refresh your browser, and try again.", + ); + reject(e); + } + }; + }); + +export const uploadFn = createImageUpload({ + onUpload: (file) => { + /** + * Upload the file to the server and preload the image in the browser + */ + const uploadPromise = uploadFiles("imageUploader", { + files: [file], + skipPolling: true, + }); + + return new Promise((resolve) => { + toast.promise( + uploadPromise.then(async (res) => { + const [uploadedFileData] = res; + const imageUrl = await preloadImage(uploadedFileData!.url); + resolve(imageUrl); + }), + { + loading: "Uploading image...", + success: "Image uploaded successfully.", + error: (e) => e.message, + }, + ); + }); + }, + validateFn: (file) => { + const config = getRouteConfig("imageUploader"); + if (!isValidFileType(file, config)) { + toast.error("File type not supported."); + return false; + } + if (!isValidFileSize(file, config)) { + toast.error("File size too big."); + return false; + } + return true; + }, +}); diff --git a/examples/with-novel/uploadthing/server.ts b/examples/with-novel/uploadthing/server.ts new file mode 100644 index 0000000000..5151253a22 --- /dev/null +++ b/examples/with-novel/uploadthing/server.ts @@ -0,0 +1,26 @@ +import { createUploadthing, FileRouter } from "uploadthing/next"; + +const f = createUploadthing({ + /** + * Log out more information about the error, but don't return it to the client + * @see https://docs.uploadthing.com/errors#error-formatting + */ + errorFormatter: (err) => { + console.log("Error uploading file", err.message); + console.log(" - Above error caused by:", err.cause); + + return { message: err.message }; + }, +}); + +export const uploadRouter = { + imageUploader: f({ image: { maxFileSize: "1MB", maxFileCount: 4 } }) + .middleware(() => { + return {}; + }) + .onUploadComplete(({ file }) => { + console.log("upload completed", file); + }), +} satisfies FileRouter; + +export type UploadRouter = typeof uploadRouter; diff --git a/packages/react/src/useUploadThing.ts b/packages/react/src/useUploadThing.ts index a8c667e46c..93477285ff 100644 --- a/packages/react/src/useUploadThing.ts +++ b/packages/react/src/useUploadThing.ts @@ -70,6 +70,7 @@ export const INTERNAL_uploadthingHookGen = < initOpts.url, endpoint as string, ); + const routeConfig = permittedFileInfo?.config; type InferredInput = inferEndpointInput; type FuncInput = undefined extends InferredInput @@ -134,7 +135,11 @@ export const INTERNAL_uploadthingHookGen = < return { startUpload, isUploading, + /** + * @deprecated Use `routeConfig` instead + */ permittedFileInfo, + routeConfig, } as const; }; @@ -146,11 +151,28 @@ export const generateReactHelpers = ( ) => { const url = resolveMaybeUrlArg(initOpts?.url); + const getRouteConfig = (endpoint: keyof TRouter) => { + const maybeServerData = globalThis.__UPLOADTHING; + const config = maybeServerData?.find((x) => x.slug === endpoint)?.config; + if (!config) { + throw new Error( + `No config found for endpoint "${endpoint.toString()}". Please make sure to use the NextSSRPlugin in your Next.js app.`, + ); + } + return config; + }; + return { useUploadThing: INTERNAL_uploadthingHookGen({ url }), uploadFiles: genUploader({ url, package: "@uploadthing/react", }), + + /** + * Get the config for a given endpoint outside of React context. + * @remarks Can only be used if the NextSSRPlugin is used in the app. + */ + getRouteConfig, } as const; }; diff --git a/packages/uploadthing/src/client.ts b/packages/uploadthing/src/client.ts index b816d79e4e..0918aebd38 100644 --- a/packages/uploadthing/src/client.ts +++ b/packages/uploadthing/src/client.ts @@ -6,13 +6,20 @@ import { unsafeCoerce } from "effect/Function"; import * as Option from "effect/Option"; import * as Runtime from "effect/Runtime"; -import type { FetchError, InvalidJsonError } from "@uploadthing/shared"; +import type { + ExpandedRouteConfig, + FetchError, + InvalidJsonError, +} from "@uploadthing/shared"; import { exponentialBackoff, FetchContext, fetchEff, + fileSizeToBytes, getErrorTypeFromStatusCode, + getTypeFromFileName, isObject, + objectKeys, parseResponseJson, resolveMaybeUrlArg, RetryError, @@ -37,6 +44,8 @@ import type { UploadFilesOptions, } from "./types"; +export const version = pkgJson.version; + export { /** @public */ generateClientDropzoneAccept, @@ -46,7 +55,44 @@ export { generatePermittedFileTypes, } from "@uploadthing/shared"; -export const version = pkgJson.version; +/** + * Validate that a file is of a valid type given a route config + * @public + */ +export const isValidFileType = ( + file: File, + routeConfig: ExpandedRouteConfig, +) => { + try { + const type = Effect.runSync( + getTypeFromFileName(file.name, objectKeys(routeConfig)), + ); + return file.type.includes(type); + } catch { + return false; + } +}; + +/** + * Validate that a file is of a valid size given a route config + * @public + */ +export const isValidFileSize = ( + file: File, + routeConfig: ExpandedRouteConfig, +) => { + try { + const maxFileSize = Effect.runSync( + Effect.flatMap( + getTypeFromFileName(file.name, objectKeys(routeConfig)), + (type) => fileSizeToBytes(routeConfig[type]!.maxFileSize), + ), + ); + return file.size <= maxFileSize; + } catch { + return false; + } +}; const uploadFilesInternal = < TRouter extends FileRouter, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e64f0c48c7..39c6c8b580 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -680,6 +680,79 @@ importers: specifier: ^5.4.5 version: 5.4.5 + examples/with-novel: + dependencies: + '@radix-ui/react-dialog': + specifier: ^1.0.5 + version: 1.0.5(@types/react-dom@18.2.25)(@types/react@18.2.78)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-popover': + specifier: ^1.0.6 + version: 1.0.7(@types/react-dom@18.2.25)(@types/react@18.2.78)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-slot': + specifier: ^1.0.2 + version: 1.0.2(@types/react@18.2.78)(react@18.2.0) + '@tailwindcss/typography': + specifier: ^0.5.10 + version: 0.5.13(tailwindcss@3.4.3) + '@uploadthing/react': + specifier: ^6.5.3 + version: link:../../packages/react + class-variance-authority: + specifier: ^0.7.0 + version: 0.7.0 + cmdk: + specifier: ^0.2.1 + version: 0.2.1(@types/react@18.2.78)(react-dom@18.2.0)(react@18.2.0) + lucide-react: + specifier: ^0.368.0 + version: 0.368.0(react@18.2.0) + next: + specifier: 14.2.1 + version: 14.2.1(@babel/core@7.24.4)(react-dom@18.2.0)(react@18.2.0) + next-themes: + specifier: ^0.3.0 + version: 0.3.0(react-dom@18.2.0)(react@18.2.0) + novel: + specifier: ^0.3.1 + version: 0.3.1(@types/react@18.2.78)(react-dom@18.2.0)(react@18.2.0) + react: + specifier: 18.2.0 + version: 18.2.0 + react-dom: + specifier: 18.2.0 + version: 18.2.0(react@18.2.0) + sonner: + specifier: ^1.4.41 + version: 1.4.41(react-dom@18.2.0)(react@18.2.0) + tailwind-merge: + specifier: ^2.2.1 + version: 2.3.0 + uploadthing: + specifier: 6.10.3 + version: link:../../packages/uploadthing + use-debounce: + specifier: ^9.0.3 + version: 9.0.4(react@18.2.0) + devDependencies: + '@types/node': + specifier: ^20.11.21 + version: 20.12.7 + '@types/react': + specifier: 18.2.78 + version: 18.2.78 + '@types/react-dom': + specifier: 18.2.25 + version: 18.2.25 + tailwindcss: + specifier: ^3.4.1 + version: 3.4.3 + tailwindcss-animate: + specifier: ^1.0.7 + version: 1.0.7(tailwindcss@3.4.3) + typescript: + specifier: ^5.4.5 + version: 5.4.5 + examples/with-react-image-crop: dependencies: '@uploadthing/react': @@ -1267,7 +1340,7 @@ importers: version: link:../tsconfig tsup: specifier: 8.0.2 - version: 8.0.2(postcss@8.4.38)(typescript@5.4.5) + version: 8.0.2(typescript@5.4.5) typescript: specifier: ^5.4.5 version: 5.4.5 @@ -1283,7 +1356,6 @@ packages: /@alloc/quick-lru@5.2.0: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} - dev: true /@ampproject/remapping@2.2.1: resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==} @@ -2005,6 +2077,7 @@ packages: engines: {node: '>=6.9.0'} dependencies: regenerator-runtime: 0.14.0 + dev: false /@babel/runtime@7.24.4: resolution: {integrity: sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==} @@ -2107,10 +2180,16 @@ packages: statuses: 2.0.1 dev: false + /@cfcs/core@0.0.6: + resolution: {integrity: sha512-FxfJMwoLB8MEMConeXUCqtMGqxdtePQxRBOiGip9ULcYYam3WfCgoY6xdnMaSkYvRvmosp5iuG+TiPofm65+Pw==} + dependencies: + '@egjs/component': 3.0.5 + dev: false + /@changesets/apply-release-plan@7.0.0: resolution: {integrity: sha512-vfi69JR416qC9hWmFGSxj7N6wA5J222XNBmezSVATPWDVPIF7gkd4d8CpbEbXmRWbVrkoli3oerGS6dcL/BGsQ==} dependencies: - '@babel/runtime': 7.22.10 + '@babel/runtime': 7.24.4 '@changesets/config': 3.0.0 '@changesets/get-version-range-type': 0.4.0 '@changesets/git': 3.0.0 @@ -2128,7 +2207,7 @@ packages: /@changesets/assemble-release-plan@6.0.0: resolution: {integrity: sha512-4QG7NuisAjisbW4hkLCmGW2lRYdPrKzro+fCtZaILX+3zdUELSvYjpL4GTv0E4aM9Mef3PuIQp89VmHJ4y2bfw==} dependencies: - '@babel/runtime': 7.22.10 + '@babel/runtime': 7.24.4 '@changesets/errors': 0.2.0 '@changesets/get-dependents-graph': 2.0.0 '@changesets/types': 6.0.0 @@ -2230,7 +2309,7 @@ packages: /@changesets/get-release-plan@4.0.0: resolution: {integrity: sha512-9L9xCUeD/Tb6L/oKmpm8nyzsOzhdNBBbt/ZNcjynbHC07WW4E1eX8NMGC5g5SbM5z/V+MOrYsJ4lRW41GCbg3w==} dependencies: - '@babel/runtime': 7.22.10 + '@babel/runtime': 7.24.4 '@changesets/assemble-release-plan': 6.0.0 '@changesets/config': 3.0.0 '@changesets/pre': 2.0.0 @@ -2246,7 +2325,7 @@ packages: /@changesets/git@3.0.0: resolution: {integrity: sha512-vvhnZDHe2eiBNRFHEgMiGd2CT+164dfYyrJDhwwxTVD/OW0FUD6G7+4DIx1dNwkwjHyzisxGAU96q0sVNBns0w==} dependencies: - '@babel/runtime': 7.22.10 + '@babel/runtime': 7.24.4 '@changesets/errors': 0.2.0 '@changesets/types': 6.0.0 '@manypkg/get-packages': 1.1.3 @@ -2271,7 +2350,7 @@ packages: /@changesets/pre@2.0.0: resolution: {integrity: sha512-HLTNYX/A4jZxc+Sq8D1AMBsv+1qD6rmmJtjsCJa/9MSRybdxh0mjbTvE6JYZQ/ZiQ0mMlDOlGPXTm9KLTU3jyw==} dependencies: - '@babel/runtime': 7.22.10 + '@babel/runtime': 7.24.4 '@changesets/errors': 0.2.0 '@changesets/types': 6.0.0 '@manypkg/get-packages': 1.1.3 @@ -2281,7 +2360,7 @@ packages: /@changesets/read@0.6.0: resolution: {integrity: sha512-ZypqX8+/im1Fm98K4YcZtmLKgjs1kDQ5zHpc2U1qdtNBmZZfo/IBiG162RoP0CUF05tvp2y4IspH11PLnPxuuw==} dependencies: - '@babel/runtime': 7.22.10 + '@babel/runtime': 7.24.4 '@changesets/git': 3.0.0 '@changesets/logger': 0.1.0 '@changesets/parse': 0.4.0 @@ -2302,7 +2381,7 @@ packages: /@changesets/write@0.3.0: resolution: {integrity: sha512-slGLb21fxZVUYbyea+94uFiD6ntQW0M2hIKNznFizDhZPDgn2c/fv1UzzlW43RVzh1BEDuIqW6hzlJ1OflNmcw==} dependencies: - '@babel/runtime': 7.22.10 + '@babel/runtime': 7.24.4 '@changesets/types': 6.0.0 fs-extra: 7.0.1 human-id: 1.0.2 @@ -2577,6 +2656,10 @@ packages: '@jridgewell/trace-mapping': 0.3.9 dev: true + /@daybrush/utils@1.13.0: + resolution: {integrity: sha512-ALK12C6SQNNHw1enXK+UO8bdyQ+jaWNQ1Af7Z3FNxeAwjYhQT7do+TRE4RASAJ3ObaS2+TJ7TXR3oz2Gzbw0PQ==} + dev: false + /@deno/shim-deno-test@0.5.0: resolution: {integrity: sha512-4nMhecpGlPi0cSzT67L+Tm+GOJqvuk8gqHBziqcUQOarnuIax1z96/gJHCSIz2Z0zhxE6Rzwb3IZXPtFh51j+w==} dev: false @@ -2603,6 +2686,24 @@ packages: fast-check: 3.18.0 dev: false + /@egjs/agent@2.4.3: + resolution: {integrity: sha512-XvksSENe8wPeFlEVouvrOhKdx8HMniJ3by7sro2uPF3M6QqWwjzVcmvwoPtdjiX8O1lfRoLhQMp1a7NGlVTdIA==} + dev: false + + /@egjs/children-differ@1.0.1: + resolution: {integrity: sha512-DRvyqMf+CPCOzAopQKHtW+X8iN6Hy6SFol+/7zCUiE5y4P/OB8JP8FtU4NxtZwtafvSL4faD5KoQYPj3JHzPFQ==} + dependencies: + '@egjs/list-differ': 1.0.1 + dev: false + + /@egjs/component@3.0.5: + resolution: {integrity: sha512-cLcGizTrrUNA2EYE3MBmEDt2tQv1joVP1Q3oDisZ5nw0MZDx2kcgEXM+/kZpfa/PAkFvYVhRUZwytIQWoN3V/w==} + dev: false + + /@egjs/list-differ@1.0.1: + resolution: {integrity: sha512-OTFTDQcWS+1ZREOdCWuk5hCBgYO4OsD30lXcOCyVOAjXMhgL5rBRDnt/otb6Nz8CzU0L/igdcaQBDLWc4t9gvg==} + dev: false + /@elysiajs/cors@0.8.0(elysia@0.8.17): resolution: {integrity: sha512-NADcOsokALRv4nljA5DYJPJdIJ7iwq+ouV5lH5W+hLgZRMYsfbKShg82QSmxd7BJQFuGSv2dIIF2dfG9ieUyng==} peerDependencies: @@ -3700,6 +3801,17 @@ packages: '@floating-ui/utils': 0.2.1 dev: false + /@floating-ui/react-dom@2.0.9(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-q0umO0+LQK4+p6aGyvzASqKbKOJcAHJ7ycE9CuUvfx3s9zTHWmGJTPOIlM/hmSBfUfg/XfY5YhLBLR/LHwShQQ==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + dependencies: + '@floating-ui/dom': 1.6.3 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@floating-ui/utils@0.2.1: resolution: {integrity: sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==} dev: false @@ -4133,7 +4245,7 @@ packages: /@manypkg/find-root@1.1.0: resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} dependencies: - '@babel/runtime': 7.22.10 + '@babel/runtime': 7.24.4 '@types/node': 12.20.55 find-up: 4.1.0 fs-extra: 8.1.0 @@ -4151,7 +4263,7 @@ packages: /@manypkg/get-packages@1.1.3: resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} dependencies: - '@babel/runtime': 7.22.10 + '@babel/runtime': 7.24.4 '@changesets/types': 4.1.0 '@manypkg/find-root': 1.1.0 fs-extra: 8.1.0 @@ -5652,6 +5764,12 @@ packages: prettier: 3.2.5 dev: false + /@radix-ui/primitive@1.0.0: + resolution: {integrity: sha512-3e7rn8FDMin4CgeL7Z/49smCA3rFYY3Ha2rUQ7HRWFadS5iCRw08ZgVT1LaNTCNqgvrUiyczLflrVrF0SRQtNA==} + dependencies: + '@babel/runtime': 7.24.4 + dev: false + /@radix-ui/primitive@1.0.1: resolution: {integrity: sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==} dependencies: @@ -5687,6 +5805,27 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@radix-ui/react-arrow@1.0.3(@types/react-dom@18.2.25)(@types/react@18.2.78)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-wSP+pHsB/jQRaL6voubsQ/ZlrGBHHrOjmBnr19hxYgtS0WvAFwZhK2WP/YY5yF9uKECCEEDGxuLxq1NBK51wFA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.24.4 + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.25)(@types/react@18.2.78)(react-dom@18.2.0)(react@18.2.0) + '@types/react': 18.2.78 + '@types/react-dom': 18.2.25 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-collapsible@1.0.3(@types/react-dom@18.2.25)(@types/react@18.2.78)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-UBmVDkmR6IvDsloHVN+3rtx4Mi5TFvylYXpluuv0f37dtaz3H99bp8No0LGXRigVpl3UAT4l9j6bIchh42S/Gg==} peerDependencies: @@ -5739,6 +5878,15 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@radix-ui/react-compose-refs@1.0.0(react@18.2.0): + resolution: {integrity: sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.24.4 + react: 18.2.0 + dev: false + /@radix-ui/react-compose-refs@1.0.1(@types/react@18.2.78)(react@18.2.0): resolution: {integrity: sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==} peerDependencies: @@ -5753,6 +5901,15 @@ packages: react: 18.2.0 dev: false + /@radix-ui/react-context@1.0.0(react@18.2.0): + resolution: {integrity: sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.24.4 + react: 18.2.0 + dev: false + /@radix-ui/react-context@1.0.1(@types/react@18.2.78)(react@18.2.0): resolution: {integrity: sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==} peerDependencies: @@ -5767,22 +5924,69 @@ packages: react: 18.2.0 dev: false - /@radix-ui/react-direction@1.0.1(@types/react@18.2.78)(react@18.2.0): - resolution: {integrity: sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA==} + /@radix-ui/react-dialog@1.0.0(@types/react@18.2.78)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-Yn9YU+QlHYLWwV1XfKiqnGVpWYWk6MeBVM6x/bcoyPvxgjQGoeT35482viLPctTMWoMw0PoHgqfSox7Ig+957Q==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.24.4 + '@radix-ui/primitive': 1.0.0 + '@radix-ui/react-compose-refs': 1.0.0(react@18.2.0) + '@radix-ui/react-context': 1.0.0(react@18.2.0) + '@radix-ui/react-dismissable-layer': 1.0.0(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-focus-guards': 1.0.0(react@18.2.0) + '@radix-ui/react-focus-scope': 1.0.0(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-id': 1.0.0(react@18.2.0) + '@radix-ui/react-portal': 1.0.0(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-presence': 1.0.0(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.0(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-slot': 1.0.0(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.0.0(react@18.2.0) + aria-hidden: 1.2.4 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-remove-scroll: 2.5.4(@types/react@18.2.78)(react@18.2.0) + transitivePeerDependencies: + - '@types/react' + dev: false + + /@radix-ui/react-dialog@1.0.5(@types/react-dom@18.2.25)(@types/react@18.2.78)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==} peerDependencies: '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 peerDependenciesMeta: '@types/react': optional: true + '@types/react-dom': + optional: true dependencies: - '@babel/runtime': 7.22.10 + '@babel/runtime': 7.24.4 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.78)(react@18.2.0) + '@radix-ui/react-context': 1.0.1(@types/react@18.2.78)(react@18.2.0) + '@radix-ui/react-dismissable-layer': 1.0.5(@types/react-dom@18.2.25)(@types/react@18.2.78)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-focus-guards': 1.0.1(@types/react@18.2.78)(react@18.2.0) + '@radix-ui/react-focus-scope': 1.0.4(@types/react-dom@18.2.25)(@types/react@18.2.78)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-id': 1.0.1(@types/react@18.2.78)(react@18.2.0) + '@radix-ui/react-portal': 1.0.4(@types/react-dom@18.2.25)(@types/react@18.2.78)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-presence': 1.0.1(@types/react-dom@18.2.25)(@types/react@18.2.78)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.25)(@types/react@18.2.78)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-slot': 1.0.2(@types/react@18.2.78)(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.78)(react@18.2.0) '@types/react': 18.2.78 + '@types/react-dom': 18.2.25 + aria-hidden: 1.2.4 react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-remove-scroll: 2.5.5(@types/react@18.2.78)(react@18.2.0) dev: false - /@radix-ui/react-id@1.0.1(@types/react@18.2.78)(react@18.2.0): - resolution: {integrity: sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==} + /@radix-ui/react-direction@1.0.1(@types/react@18.2.78)(react@18.2.0): + resolution: {integrity: sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 @@ -5791,13 +5995,28 @@ packages: optional: true dependencies: '@babel/runtime': 7.22.10 - '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.78)(react@18.2.0) '@types/react': 18.2.78 react: 18.2.0 dev: false - /@radix-ui/react-presence@1.0.1(@types/react-dom@18.2.25)(@types/react@18.2.78)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==} + /@radix-ui/react-dismissable-layer@1.0.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-n7kDRfx+LB1zLueRDvZ1Pd0bxdJWDUZNQ/GWoxDn2prnuJKRdxsjulejX/ePkOsLi2tTm6P24mDqlMSgQpsT6g==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.24.4 + '@radix-ui/primitive': 1.0.0 + '@radix-ui/react-compose-refs': 1.0.0(react@18.2.0) + '@radix-ui/react-primitive': 1.0.0(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.0.0(react@18.2.0) + '@radix-ui/react-use-escape-keydown': 1.0.0(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@radix-ui/react-dismissable-layer@1.0.5(@types/react-dom@18.2.25)(@types/react@18.2.78)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -5809,53 +6028,90 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.22.10 + '@babel/runtime': 7.24.4 + '@radix-ui/primitive': 1.0.1 '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.78)(react@18.2.0) - '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.78)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.25)(@types/react@18.2.78)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.78)(react@18.2.0) + '@radix-ui/react-use-escape-keydown': 1.0.3(@types/react@18.2.78)(react@18.2.0) '@types/react': 18.2.78 '@types/react-dom': 18.2.25 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: false - /@radix-ui/react-primitive@1.0.3(@types/react-dom@18.2.25)(@types/react@18.2.78)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==} + /@radix-ui/react-focus-guards@1.0.0(react@18.2.0): + resolution: {integrity: sha512-UagjDk4ijOAnGu4WMUPj9ahi7/zJJqNZ9ZAiGPp7waUWJO0O1aWXi/udPphI0IUjvrhBsZJGSN66dR2dsueLWQ==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.24.4 + react: 18.2.0 + dev: false + + /@radix-ui/react-focus-guards@1.0.1(@types/react@18.2.78)(react@18.2.0): + resolution: {integrity: sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==} peerDependencies: '@types/react': '*' - '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 peerDependenciesMeta: '@types/react': optional: true - '@types/react-dom': - optional: true dependencies: - '@babel/runtime': 7.22.10 - '@radix-ui/react-slot': 1.0.2(@types/react@18.2.78)(react@18.2.0) + '@babel/runtime': 7.24.4 '@types/react': 18.2.78 - '@types/react-dom': 18.2.25 + react: 18.2.0 + dev: false + + /@radix-ui/react-focus-scope@1.0.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-C4SWtsULLGf/2L4oGeIHlvWQx7Rf+7cX/vKOAD2dXW0A1b5QXwi3wWeaEgW+wn+SEVrraMUk05vLU9fZZz5HbQ==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.24.4 + '@radix-ui/react-compose-refs': 1.0.0(react@18.2.0) + '@radix-ui/react-primitive': 1.0.0(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.0.0(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: false - /@radix-ui/react-slot@1.0.2(@types/react@18.2.78)(react@18.2.0): - resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==} + /@radix-ui/react-focus-scope@1.0.4(@types/react-dom@18.2.25)(@types/react@18.2.78)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==} peerDependencies: '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 peerDependenciesMeta: '@types/react': optional: true + '@types/react-dom': + optional: true dependencies: - '@babel/runtime': 7.22.10 + '@babel/runtime': 7.24.4 '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.78)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.25)(@types/react@18.2.78)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.78)(react@18.2.0) '@types/react': 18.2.78 + '@types/react-dom': 18.2.25 react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) dev: false - /@radix-ui/react-use-callback-ref@1.0.1(@types/react@18.2.78)(react@18.2.0): - resolution: {integrity: sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==} + /@radix-ui/react-id@1.0.0(react@18.2.0): + resolution: {integrity: sha512-Q6iAB/U7Tq3NTolBBQbHTgclPmGWE3OlktGGqrClPozSw4vkQ1DfQAOtzgRPecKsMdJINE05iaoDUG8tRzCBjw==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.24.4 + '@radix-ui/react-use-layout-effect': 1.0.0(react@18.2.0) + react: 18.2.0 + dev: false + + /@radix-ui/react-id@1.0.1(@types/react@18.2.78)(react@18.2.0): + resolution: {integrity: sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 @@ -5863,48 +6119,347 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.22.10 + '@babel/runtime': 7.24.4 + '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.78)(react@18.2.0) '@types/react': 18.2.78 react: 18.2.0 dev: false - /@radix-ui/react-use-controllable-state@1.0.1(@types/react@18.2.78)(react@18.2.0): - resolution: {integrity: sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==} + /@radix-ui/react-popover@1.0.7(@types/react-dom@18.2.25)(@types/react@18.2.78)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-shtvVnlsxT6faMnK/a7n0wptwBD23xc1Z5mdrtKLwVEfsEMXodS0r5s0/g5P0hX//EKYZS2sxUjqfzlg52ZSnQ==} peerDependencies: '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 peerDependenciesMeta: '@types/react': optional: true + '@types/react-dom': + optional: true dependencies: - '@babel/runtime': 7.22.10 - '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.78)(react@18.2.0) + '@babel/runtime': 7.24.4 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.78)(react@18.2.0) + '@radix-ui/react-context': 1.0.1(@types/react@18.2.78)(react@18.2.0) + '@radix-ui/react-dismissable-layer': 1.0.5(@types/react-dom@18.2.25)(@types/react@18.2.78)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-focus-guards': 1.0.1(@types/react@18.2.78)(react@18.2.0) + '@radix-ui/react-focus-scope': 1.0.4(@types/react-dom@18.2.25)(@types/react@18.2.78)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-id': 1.0.1(@types/react@18.2.78)(react@18.2.0) + '@radix-ui/react-popper': 1.1.3(@types/react-dom@18.2.25)(@types/react@18.2.78)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-portal': 1.0.4(@types/react-dom@18.2.25)(@types/react@18.2.78)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-presence': 1.0.1(@types/react-dom@18.2.25)(@types/react@18.2.78)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.25)(@types/react@18.2.78)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-slot': 1.0.2(@types/react@18.2.78)(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.78)(react@18.2.0) '@types/react': 18.2.78 + '@types/react-dom': 18.2.25 + aria-hidden: 1.2.4 react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-remove-scroll: 2.5.5(@types/react@18.2.78)(react@18.2.0) dev: false - /@radix-ui/react-use-layout-effect@1.0.1(@types/react@18.2.78)(react@18.2.0): - resolution: {integrity: sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==} + /@radix-ui/react-popper@1.1.3(@types/react-dom@18.2.25)(@types/react@18.2.78)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-cKpopj/5RHZWjrbF2846jBNacjQVwkP068DfmgrNJXpvVWrOvlAmE9xSiy5OqeE+Gi8D9fP+oDhUnPqNMY8/5w==} peerDependencies: '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 peerDependenciesMeta: '@types/react': optional: true + '@types/react-dom': + optional: true dependencies: - '@babel/runtime': 7.22.10 + '@babel/runtime': 7.24.4 + '@floating-ui/react-dom': 2.0.9(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-arrow': 1.0.3(@types/react-dom@18.2.25)(@types/react@18.2.78)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.78)(react@18.2.0) + '@radix-ui/react-context': 1.0.1(@types/react@18.2.78)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.25)(@types/react@18.2.78)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.78)(react@18.2.0) + '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.78)(react@18.2.0) + '@radix-ui/react-use-rect': 1.0.1(@types/react@18.2.78)(react@18.2.0) + '@radix-ui/react-use-size': 1.0.1(@types/react@18.2.78)(react@18.2.0) + '@radix-ui/rect': 1.0.1 '@types/react': 18.2.78 + '@types/react-dom': 18.2.25 react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) dev: false - /@replit/codemirror-css-color-picker@6.1.1(@codemirror/language@6.10.1)(@codemirror/state@6.4.1)(@codemirror/view@6.26.3): - resolution: {integrity: sha512-e/wYHcgt3HRDpvYuwqXyjv3LEY6VyFjJeDQK1UtFmaykp86R6Cbw3ULH9pvuJuelaW6nS4CVtIRHuOfbFLlqwQ==} + /@radix-ui/react-portal@1.0.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-a8qyFO/Xb99d8wQdu4o7qnigNjTPG123uADNecz0eX4usnQEj7o+cG4ZX4zkqq98NYekT7UoEQIjxBNWIFuqTA==} peerDependencies: - '@codemirror/language': ^6.0.0 - '@codemirror/state': ^6.0.0 - '@codemirror/view': ^6.0.0 + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 dependencies: - '@codemirror/language': 6.10.1 + '@babel/runtime': 7.24.4 + '@radix-ui/react-primitive': 1.0.0(react-dom@18.2.0)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@radix-ui/react-portal@1.0.4(@types/react-dom@18.2.25)(@types/react@18.2.78)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.24.4 + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.25)(@types/react@18.2.78)(react-dom@18.2.0)(react@18.2.0) + '@types/react': 18.2.78 + '@types/react-dom': 18.2.25 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@radix-ui/react-presence@1.0.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-A+6XEvN01NfVWiKu38ybawfHsBjWum42MRPnEuqPsBZ4eV7e/7K321B5VgYMPv3Xx5An6o1/l9ZuDBgmcmWK3w==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.24.4 + '@radix-ui/react-compose-refs': 1.0.0(react@18.2.0) + '@radix-ui/react-use-layout-effect': 1.0.0(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@radix-ui/react-presence@1.0.1(@types/react-dom@18.2.25)(@types/react@18.2.78)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.24.4 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.78)(react@18.2.0) + '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.78)(react@18.2.0) + '@types/react': 18.2.78 + '@types/react-dom': 18.2.25 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@radix-ui/react-primitive@1.0.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-EyXe6mnRlHZ8b6f4ilTDrXmkLShICIuOTTj0GX4w1rp+wSxf3+TD05u1UOITC8VsJ2a9nwHvdXtOXEOl0Cw/zQ==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.24.4 + '@radix-ui/react-slot': 1.0.0(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@radix-ui/react-primitive@1.0.3(@types/react-dom@18.2.25)(@types/react@18.2.78)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.24.4 + '@radix-ui/react-slot': 1.0.2(@types/react@18.2.78)(react@18.2.0) + '@types/react': 18.2.78 + '@types/react-dom': 18.2.25 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@radix-ui/react-slot@1.0.0(react@18.2.0): + resolution: {integrity: sha512-3mrKauI/tWXo1Ll+gN5dHcxDPdm/Df1ufcDLCecn+pnCIVcdWE7CujXo8QaXOWRJyZyQWWbpB8eFwHzWXlv5mQ==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.24.4 + '@radix-ui/react-compose-refs': 1.0.0(react@18.2.0) + react: 18.2.0 + dev: false + + /@radix-ui/react-slot@1.0.2(@types/react@18.2.78)(react@18.2.0): + resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.24.4 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.78)(react@18.2.0) + '@types/react': 18.2.78 + react: 18.2.0 + dev: false + + /@radix-ui/react-use-callback-ref@1.0.0(react@18.2.0): + resolution: {integrity: sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.24.4 + react: 18.2.0 + dev: false + + /@radix-ui/react-use-callback-ref@1.0.1(@types/react@18.2.78)(react@18.2.0): + resolution: {integrity: sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.24.4 + '@types/react': 18.2.78 + react: 18.2.0 + dev: false + + /@radix-ui/react-use-controllable-state@1.0.0(react@18.2.0): + resolution: {integrity: sha512-FohDoZvk3mEXh9AWAVyRTYR4Sq7/gavuofglmiXB2g1aKyboUD4YtgWxKj8O5n+Uak52gXQ4wKz5IFST4vtJHg==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.24.4 + '@radix-ui/react-use-callback-ref': 1.0.0(react@18.2.0) + react: 18.2.0 + dev: false + + /@radix-ui/react-use-controllable-state@1.0.1(@types/react@18.2.78)(react@18.2.0): + resolution: {integrity: sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.22.10 + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.78)(react@18.2.0) + '@types/react': 18.2.78 + react: 18.2.0 + dev: false + + /@radix-ui/react-use-escape-keydown@1.0.0(react@18.2.0): + resolution: {integrity: sha512-JwfBCUIfhXRxKExgIqGa4CQsiMemo1Xt0W/B4ei3fpzpvPENKpMKQ8mZSB6Acj3ebrAEgi2xiQvcI1PAAodvyg==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.24.4 + '@radix-ui/react-use-callback-ref': 1.0.0(react@18.2.0) + react: 18.2.0 + dev: false + + /@radix-ui/react-use-escape-keydown@1.0.3(@types/react@18.2.78)(react@18.2.0): + resolution: {integrity: sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.24.4 + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.78)(react@18.2.0) + '@types/react': 18.2.78 + react: 18.2.0 + dev: false + + /@radix-ui/react-use-layout-effect@1.0.0(react@18.2.0): + resolution: {integrity: sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.24.4 + react: 18.2.0 + dev: false + + /@radix-ui/react-use-layout-effect@1.0.1(@types/react@18.2.78)(react@18.2.0): + resolution: {integrity: sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.24.4 + '@types/react': 18.2.78 + react: 18.2.0 + dev: false + + /@radix-ui/react-use-rect@1.0.1(@types/react@18.2.78)(react@18.2.0): + resolution: {integrity: sha512-Cq5DLuSiuYVKNU8orzJMbl15TXilTnJKUCltMVQg53BQOF1/C5toAaGrowkgksdBQ9H+SRL23g0HDmg9tvmxXw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.24.4 + '@radix-ui/rect': 1.0.1 + '@types/react': 18.2.78 + react: 18.2.0 + dev: false + + /@radix-ui/react-use-size@1.0.1(@types/react@18.2.78)(react@18.2.0): + resolution: {integrity: sha512-ibay+VqrgcaI6veAojjofPATwledXiSmX+C0KrBk/xgpX9rBzPV3OsfwlhQdUOFbh+LKQorLYT+xTXW9V8yd0g==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.24.4 + '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.78)(react@18.2.0) + '@types/react': 18.2.78 + react: 18.2.0 + dev: false + + /@radix-ui/rect@1.0.1: + resolution: {integrity: sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==} + dependencies: + '@babel/runtime': 7.24.4 + dev: false + + /@remirror/core-constants@2.0.2: + resolution: {integrity: sha512-dyHY+sMF0ihPus3O27ODd4+agdHMEmuRdyiZJ2CCWjPV5UFmn17ZbElvk6WOGVE4rdCJKZQCrPV2BcikOMLUGQ==} + dev: false + + /@replit/codemirror-css-color-picker@6.1.1(@codemirror/language@6.10.1)(@codemirror/state@6.4.1)(@codemirror/view@6.26.3): + resolution: {integrity: sha512-e/wYHcgt3HRDpvYuwqXyjv3LEY6VyFjJeDQK1UtFmaykp86R6Cbw3ULH9pvuJuelaW6nS4CVtIRHuOfbFLlqwQ==} + peerDependencies: + '@codemirror/language': ^6.0.0 + '@codemirror/state': ^6.0.0 + '@codemirror/view': ^6.0.0 + dependencies: + '@codemirror/language': 6.10.1 '@codemirror/state': 6.4.1 '@codemirror/view': 6.26.3 dev: false @@ -6022,7 +6577,7 @@ packages: deepmerge: 4.3.1 is-builtin-module: 3.2.1 is-module: 1.0.0 - resolve: 1.22.4 + resolve: 1.22.8 rollup: 3.29.4 dev: true @@ -6502,6 +7057,25 @@ packages: vue: 3.4.25(typescript@5.4.5) dev: false + /@scena/dragscroll@1.4.0: + resolution: {integrity: sha512-3O8daaZD9VXA9CP3dra6xcgt/qrm0mg0xJCwiX6druCteQ9FFsXffkF8PrqxY4Z4VJ58fFKEa0RlKqbsi/XnRA==} + dependencies: + '@daybrush/utils': 1.13.0 + '@scena/event-emitter': 1.0.5 + dev: false + + /@scena/event-emitter@1.0.5: + resolution: {integrity: sha512-AzY4OTb0+7ynefmWFQ6hxDdk0CySAq/D4efljfhtRHCOP7MBF9zUfhKG3TJiroVjASqVgkRJFdenS8ArZo6Olg==} + dependencies: + '@daybrush/utils': 1.13.0 + dev: false + + /@scena/matrix@1.1.1: + resolution: {integrity: sha512-JVKBhN0tm2Srl+Yt+Ywqu0oLgLcdemDQlD1OxmN9jaCTwaFPZ7tY8n6dhVgMEaR9qcR7r+kAlMXnSfNyYdE+Vg==} + dependencies: + '@daybrush/utils': 1.13.0 + dev: false + /@shikijs/core@1.3.0: resolution: {integrity: sha512-7fedsBfuILDTBmrYZNFI8B6ATTxhQAasUHllHmjvSZPnoq4bULWoTpHwmuQvZ8Aq03/tAa2IGo6RXqWtHdWaCA==} dev: false @@ -6877,6 +7451,18 @@ packages: zod: 3.23.0 dev: false + /@tailwindcss/typography@0.5.13(tailwindcss@3.4.3): + resolution: {integrity: sha512-ADGcJ8dX21dVVHIwTRgzrcunY6YY9uSlAHHGVKvkA+vLc5qLwEszvKts40lx7z0qc4clpjclwLeK5rVCV2P/uw==} + peerDependencies: + tailwindcss: '>=3.0.0 || insiders' + dependencies: + lodash.castarray: 4.4.0 + lodash.isplainobject: 4.0.6 + lodash.merge: 4.6.2 + postcss-selector-parser: 6.0.10 + tailwindcss: 3.4.3 + dev: false + /@tanstack/react-virtual@3.4.0(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-GZN4xn/Tg5w7gvYeVcMVCeL4pEyUhvg+Cp6KX2Z01C4FRNxIWMgIQ9ibgMarNQfo+gt0PVLcEER4A9sNv/jlow==} peerDependencies: @@ -6888,36 +7474,376 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false - /@tanstack/virtual-core@3.4.0: - resolution: {integrity: sha512-75jXqXxqq5M5Veb9KP1STi8kA5u408uOOAefk2ftHDGCpUk3RP6zX++QqfbmHJTBiU72NQ+ghgCZVts/Wocz8Q==} + /@tanstack/virtual-core@3.4.0: + resolution: {integrity: sha512-75jXqXxqq5M5Veb9KP1STi8kA5u408uOOAefk2ftHDGCpUk3RP6zX++QqfbmHJTBiU72NQ+ghgCZVts/Wocz8Q==} + dev: false + + /@tanstack/vue-virtual@3.4.0(vue@3.4.25): + resolution: {integrity: sha512-OHZGsmE89rpouVDGDOCtJTu64gLUzVq5FGkL2YY/wtZXu5QRSi5ep3T25Ivd53HQI3A169H01uwVPD0mEXKm9A==} + peerDependencies: + vue: ^2.7.0 || ^3.0.0 + dependencies: + '@tanstack/virtual-core': 3.4.0 + vue: 3.4.25(typescript@5.4.5) + dev: false + + /@theguild/remark-mermaid@0.0.5(react@18.2.0): + resolution: {integrity: sha512-e+ZIyJkEv9jabI4m7q29wZtZv+2iwPGsXJ2d46Zi7e+QcFudiyuqhLhHG/3gX3ZEB+hxTch+fpItyMS8jwbIcw==} + peerDependencies: + react: ^18.2.0 + dependencies: + mermaid: 10.9.0 + react: 18.2.0 + unist-util-visit: 5.0.0 + transitivePeerDependencies: + - supports-color + dev: false + + /@theguild/remark-npm2yarn@0.2.1: + resolution: {integrity: sha512-jUTFWwDxtLEFtGZh/TW/w30ySaDJ8atKWH8dq2/IiQF61dPrGfETpl0WxD0VdBfuLOeU14/kop466oBSRO/5CA==} + dependencies: + npm-to-yarn: 2.2.1 + unist-util-visit: 5.0.0 + dev: false + + /@tiptap/core@2.3.2(@tiptap/pm@2.3.2): + resolution: {integrity: sha512-4sMpzYuxiG+fYMwPRXy+mLRVU315KEqzQUcBc2FEgSsmw9Kionykmkq3DvEco7rH8r0NdV/l9R49wVEtX54VqQ==} + peerDependencies: + '@tiptap/pm': ^2.0.0 + dependencies: + '@tiptap/pm': 2.3.2 + dev: false + + /@tiptap/extension-blockquote@2.3.2(@tiptap/core@2.3.2): + resolution: {integrity: sha512-dyXx1hHAW/0BSxCUNWcxc8UN+s0wRTdtH46u6IEf91z+IOWjJwmSxT00+UMYh6hdOYDDsJYxPe9gcuSWYCIkCg==} + peerDependencies: + '@tiptap/core': ^2.0.0 + dependencies: + '@tiptap/core': 2.3.2(@tiptap/pm@2.3.2) + dev: false + + /@tiptap/extension-bold@2.3.2(@tiptap/core@2.3.2): + resolution: {integrity: sha512-Mdc0qOPeJxxt5kSYKpNs7TzbQHeVpbpxwafUrxrvfD2iOnJlwlNxVWsVulc1t5EA8NpbTqYJTPmAtv2h/qmsfw==} + peerDependencies: + '@tiptap/core': ^2.0.0 + dependencies: + '@tiptap/core': 2.3.2(@tiptap/pm@2.3.2) + dev: false + + /@tiptap/extension-bubble-menu@2.3.2(@tiptap/core@2.3.2)(@tiptap/pm@2.3.2): + resolution: {integrity: sha512-hg+ncQmoNngdeoUWBQs2AWzDO8YIrlAIgLmIponC+OSCZoVrri7LZ4N1uSp5B/U0lz5fSGUvsUNUs0le+MMr/Q==} + peerDependencies: + '@tiptap/core': ^2.0.0 + '@tiptap/pm': ^2.0.0 + dependencies: + '@tiptap/core': 2.3.2(@tiptap/pm@2.3.2) + '@tiptap/pm': 2.3.2 + tippy.js: 6.3.7 + dev: false + + /@tiptap/extension-bullet-list@2.3.2(@tiptap/core@2.3.2): + resolution: {integrity: sha512-nzvXSGxJuuZdQ6NE0gJ2GC+0gjXZTgU2+Z8TEKi7TYLUAjAoiU1Iniz1XA97cuFwVrNKp031IF1LivK085NqQA==} + peerDependencies: + '@tiptap/core': ^2.0.0 + dependencies: + '@tiptap/core': 2.3.2(@tiptap/pm@2.3.2) + dev: false + + /@tiptap/extension-code-block@2.3.2(@tiptap/core@2.3.2)(@tiptap/pm@2.3.2): + resolution: {integrity: sha512-Ng5dh8+FMD3pxaqZEDSRxTjgjPCNdEEVUTJnuljZXQ9ZxI9wVsKsGs53Hunpita4Qgk0DYhlfAvGUKCM0nCH4A==} + peerDependencies: + '@tiptap/core': ^2.0.0 + '@tiptap/pm': ^2.0.0 + dependencies: + '@tiptap/core': 2.3.2(@tiptap/pm@2.3.2) + '@tiptap/pm': 2.3.2 + dev: false + + /@tiptap/extension-code@2.3.2(@tiptap/core@2.3.2): + resolution: {integrity: sha512-LyIRBFJCxbgi96ejoeewESvfUf5igfngamZJK+uegfTcznimP0AjSWs3whJwZ9QXUsQrB9tIrWIG4GBtatp6qw==} + peerDependencies: + '@tiptap/core': ^2.0.0 + dependencies: + '@tiptap/core': 2.3.2(@tiptap/pm@2.3.2) + dev: false + + /@tiptap/extension-color@2.3.2(@tiptap/core@2.3.2)(@tiptap/extension-text-style@2.3.2): + resolution: {integrity: sha512-xPJyqDFkDI/jPW0SKPhARuSgvIiIUdcMS/i+nc4wSlShvUugcGNbd00LgDHNCUW0VpKQA/MMMtWzj9ZOPcjquQ==} + peerDependencies: + '@tiptap/core': ^2.0.0 + '@tiptap/extension-text-style': ^2.0.0 + dependencies: + '@tiptap/core': 2.3.2(@tiptap/pm@2.3.2) + '@tiptap/extension-text-style': 2.3.2(@tiptap/core@2.3.2) + dev: false + + /@tiptap/extension-document@2.3.2(@tiptap/core@2.3.2): + resolution: {integrity: sha512-EQcfkvA7lkZPKllhGo2jiEYLJyXhBFK7++oRatgbfgHEJ2uLBGv6ys7WLCeRA/ntcaWTH3rlS+HR/Y8/nnyQYg==} + peerDependencies: + '@tiptap/core': ^2.0.0 + dependencies: + '@tiptap/core': 2.3.2(@tiptap/pm@2.3.2) + dev: false + + /@tiptap/extension-dropcursor@2.3.2(@tiptap/core@2.3.2)(@tiptap/pm@2.3.2): + resolution: {integrity: sha512-r7JJn9dEnIRDdbnTCAUFCWX4OPsR48+4OEm5eGlysEaD2h4z0G1AaK5XXwOoQhP3WP2LHHjL4LahlYZvltzFzw==} + peerDependencies: + '@tiptap/core': ^2.0.0 + '@tiptap/pm': ^2.0.0 + dependencies: + '@tiptap/core': 2.3.2(@tiptap/pm@2.3.2) + '@tiptap/pm': 2.3.2 + dev: false + + /@tiptap/extension-floating-menu@2.3.2(@tiptap/core@2.3.2)(@tiptap/pm@2.3.2): + resolution: {integrity: sha512-7MerFtr+7y0lThKEcNeM0B5LMWqP3RqmMZYJEOCIL20mIINYz5JzSIMQQujmeU5tcqI12O1u7jbRoxRmZrsXxw==} + peerDependencies: + '@tiptap/core': ^2.0.0 + '@tiptap/pm': ^2.0.0 + dependencies: + '@tiptap/core': 2.3.2(@tiptap/pm@2.3.2) + '@tiptap/pm': 2.3.2 + tippy.js: 6.3.7 + dev: false + + /@tiptap/extension-gapcursor@2.3.2(@tiptap/core@2.3.2)(@tiptap/pm@2.3.2): + resolution: {integrity: sha512-PSry4JHUIOhXytvYUQGtYgfIKCIhnmbKksZ8/CfCaKgGJpjOpnzqRG5FnYXZB7NiqouABreM7+IgkH0mOLq6HQ==} + peerDependencies: + '@tiptap/core': ^2.0.0 + '@tiptap/pm': ^2.0.0 + dependencies: + '@tiptap/core': 2.3.2(@tiptap/pm@2.3.2) + '@tiptap/pm': 2.3.2 + dev: false + + /@tiptap/extension-hard-break@2.3.2(@tiptap/core@2.3.2): + resolution: {integrity: sha512-Oy/Dj75kw/tyNyrcFf97r872NZggISfvabTptH8j1gFPg/XzT5ERcT2fvgpbsBx0WWlXOaFkC1owta6kS6MZpg==} + peerDependencies: + '@tiptap/core': ^2.0.0 + dependencies: + '@tiptap/core': 2.3.2(@tiptap/pm@2.3.2) + dev: false + + /@tiptap/extension-heading@2.3.2(@tiptap/core@2.3.2): + resolution: {integrity: sha512-KBew4QCnYASBPEJlZ4vKQnm4R9B206H8kE5+hq8OOyF3FVkR6FgF/AbY/E/4/+2blx82PGp+9gvPUVpEv36ifQ==} + peerDependencies: + '@tiptap/core': ^2.0.0 + dependencies: + '@tiptap/core': 2.3.2(@tiptap/pm@2.3.2) + dev: false + + /@tiptap/extension-highlight@2.3.2(@tiptap/core@2.3.2): + resolution: {integrity: sha512-OycPrcLTwRI+vi1p63J+d4ta3TESRoEBJP7qG1oxATLkCNvekNl+1LMgkdohJMBHMF3smCA9BAYUqtQqUhYD3w==} + peerDependencies: + '@tiptap/core': ^2.0.0 + dependencies: + '@tiptap/core': 2.3.2(@tiptap/pm@2.3.2) + dev: false + + /@tiptap/extension-history@2.3.2(@tiptap/core@2.3.2)(@tiptap/pm@2.3.2): + resolution: {integrity: sha512-LTon7ys+C6wLmN/nXYkr1pDxIiIv0Czn4US7I/1b8Ws2N6PU+nMm4r7Uj8hKrDYL8yPQUaS4gIs1hhOwJ8UjtA==} + peerDependencies: + '@tiptap/core': ^2.0.0 + '@tiptap/pm': ^2.0.0 + dependencies: + '@tiptap/core': 2.3.2(@tiptap/pm@2.3.2) + '@tiptap/pm': 2.3.2 + dev: false + + /@tiptap/extension-horizontal-rule@2.3.2(@tiptap/core@2.3.2)(@tiptap/pm@2.3.2): + resolution: {integrity: sha512-nz4GcYvZmJOX20GAjR5ymZgzQCbhnK/rmcunQf4zkl4LA5sXm70P70I9bDtrT/mgmz5dnBUTkVAkLTtKbovdDQ==} + peerDependencies: + '@tiptap/core': ^2.0.0 + '@tiptap/pm': ^2.0.0 + dependencies: + '@tiptap/core': 2.3.2(@tiptap/pm@2.3.2) + '@tiptap/pm': 2.3.2 + dev: false + + /@tiptap/extension-image@2.3.2(@tiptap/core@2.3.2): + resolution: {integrity: sha512-otkhqToHnjjpWOIswuotfK/PTPEOhhKRFPf1NuXvqHpMNulz+J1uIuA9R/B1m+bXkxZzCMKkWQi50vjqH9idVg==} + peerDependencies: + '@tiptap/core': ^2.0.0 + dependencies: + '@tiptap/core': 2.3.2(@tiptap/pm@2.3.2) + dev: false + + /@tiptap/extension-italic@2.3.2(@tiptap/core@2.3.2): + resolution: {integrity: sha512-6RJmexu/E+JP2+lhzJLV+5KZJiTrJE+p/hnDk13CBK2VgiwcJYmcZSVk+Yk6Suwrb1qTAosu8paKIwVJa/VMUg==} + peerDependencies: + '@tiptap/core': ^2.0.0 + dependencies: + '@tiptap/core': 2.3.2(@tiptap/pm@2.3.2) + dev: false + + /@tiptap/extension-link@2.3.2(@tiptap/core@2.3.2)(@tiptap/pm@2.3.2): + resolution: {integrity: sha512-Bs3PbYmXj5bzUzPdFkcuflxZkdI2nCIJY2YO5TykANos68FrRtxyRKCxSxyZABzKjctT/UUVSap7JUVQ+i/bSw==} + peerDependencies: + '@tiptap/core': ^2.0.0 + '@tiptap/pm': ^2.0.0 + dependencies: + '@tiptap/core': 2.3.2(@tiptap/pm@2.3.2) + '@tiptap/pm': 2.3.2 + linkifyjs: 4.1.3 + dev: false + + /@tiptap/extension-list-item@2.3.2(@tiptap/core@2.3.2): + resolution: {integrity: sha512-vgT7tkSZd99xAEph9quPlVdRkgPU4GJp9K7bNS8Y7GnSLU0KkDHbtDpb0pyz76HVpeOnt/QGmtqF14Il9T2IPQ==} + peerDependencies: + '@tiptap/core': ^2.0.0 + dependencies: + '@tiptap/core': 2.3.2(@tiptap/pm@2.3.2) + dev: false + + /@tiptap/extension-ordered-list@2.3.2(@tiptap/core@2.3.2): + resolution: {integrity: sha512-eMnQDgWpaQ3sdlFg1M85oziFYl2h/GRBjUt4JhF5kyWpHOYDj1/bX1fndZOBQ5xaoNlbcaeEkIc53xVX4ZV9tw==} + peerDependencies: + '@tiptap/core': ^2.0.0 + dependencies: + '@tiptap/core': 2.3.2(@tiptap/pm@2.3.2) + dev: false + + /@tiptap/extension-paragraph@2.3.2(@tiptap/core@2.3.2): + resolution: {integrity: sha512-bKzL4NXp0pDM/Q5ZCpjLxjQU4DwoWc6CDww1M4B4dp1sfiXiE2P7EOCMM2TfJOqNPUFpp5RcFKKcxC2Suj8W4w==} + peerDependencies: + '@tiptap/core': ^2.0.0 + dependencies: + '@tiptap/core': 2.3.2(@tiptap/pm@2.3.2) + dev: false + + /@tiptap/extension-placeholder@2.0.3(@tiptap/core@2.3.2)(@tiptap/pm@2.3.2): + resolution: {integrity: sha512-Z42jo0termRAf0S0L8oxrts94IWX5waU4isS2CUw8xCUigYyCFslkhQXkWATO1qRbjNFLKN2C9qvCgGf4UeBrw==} + peerDependencies: + '@tiptap/core': ^2.0.0 + '@tiptap/pm': ^2.0.0 + dependencies: + '@tiptap/core': 2.3.2(@tiptap/pm@2.3.2) + '@tiptap/pm': 2.3.2 + dev: false + + /@tiptap/extension-strike@2.3.2(@tiptap/core@2.3.2): + resolution: {integrity: sha512-gi16YtLnXKPubxafvcGSAELac4i8S6Eb9Av0AaH6QH9H9zzSHN7qOrX930Tp2Pod5a/a82kk7kN7IB6htAeaYA==} + peerDependencies: + '@tiptap/core': ^2.0.0 + dependencies: + '@tiptap/core': 2.3.2(@tiptap/pm@2.3.2) + dev: false + + /@tiptap/extension-task-item@2.3.2(@tiptap/core@2.3.2)(@tiptap/pm@2.3.2): + resolution: {integrity: sha512-r5WKBFVtByYMGhAB6SSAsyYd8hktiJv1I5o/fgphyGf6hdYfq6nNgb/MjAkpRFEloO36ETKR1Nn1Az7Jhu+VxA==} + peerDependencies: + '@tiptap/core': ^2.0.0 + '@tiptap/pm': ^2.0.0 + dependencies: + '@tiptap/core': 2.3.2(@tiptap/pm@2.3.2) + '@tiptap/pm': 2.3.2 + dev: false + + /@tiptap/extension-task-list@2.3.2(@tiptap/core@2.3.2): + resolution: {integrity: sha512-EP6AhjUorjwralLjSfTHU28m5AzS0Y6oRsOiK8wptwWYC6DnObuPCJEukxM3nhSM4r8SRqcAVWgCuJNBBuJRMA==} + peerDependencies: + '@tiptap/core': ^2.0.0 + dependencies: + '@tiptap/core': 2.3.2(@tiptap/pm@2.3.2) + dev: false + + /@tiptap/extension-text-style@2.3.2(@tiptap/core@2.3.2): + resolution: {integrity: sha512-y0ye1BqDSVqewLTcW9Rg4hXykZ8eTOEhb5KCLbcYYsX4LeKQv/gNQjj/Oy9+w177ts32gvbEuypOEKJJo/oBBw==} + peerDependencies: + '@tiptap/core': ^2.0.0 + dependencies: + '@tiptap/core': 2.3.2(@tiptap/pm@2.3.2) + dev: false + + /@tiptap/extension-text@2.3.2(@tiptap/core@2.3.2): + resolution: {integrity: sha512-a3whwDyyOsrmOQbfeY+Fm5XypSRgT3IGqWgz0r4U7oko57/X6Env08F1Ie2e2UkQw9B1MoW9cm3dC6jvrdzzYA==} + peerDependencies: + '@tiptap/core': ^2.0.0 + dependencies: + '@tiptap/core': 2.3.2(@tiptap/pm@2.3.2) dev: false - /@tanstack/vue-virtual@3.4.0(vue@3.4.25): - resolution: {integrity: sha512-OHZGsmE89rpouVDGDOCtJTu64gLUzVq5FGkL2YY/wtZXu5QRSi5ep3T25Ivd53HQI3A169H01uwVPD0mEXKm9A==} + /@tiptap/extension-underline@2.3.2(@tiptap/core@2.3.2): + resolution: {integrity: sha512-ZmhWG8gMXk62AhpIMuOofe8GWbkXBW1uYHG55Q9r7MmglESLJm13S5k8JVfOmOMKGzfE23A6yQkojnksAiSGoQ==} peerDependencies: - vue: ^2.7.0 || ^3.0.0 + '@tiptap/core': ^2.0.0 dependencies: - '@tanstack/virtual-core': 3.4.0 - vue: 3.4.25(typescript@5.4.5) + '@tiptap/core': 2.3.2(@tiptap/pm@2.3.2) dev: false - /@theguild/remark-mermaid@0.0.5(react@18.2.0): - resolution: {integrity: sha512-e+ZIyJkEv9jabI4m7q29wZtZv+2iwPGsXJ2d46Zi7e+QcFudiyuqhLhHG/3gX3ZEB+hxTch+fpItyMS8jwbIcw==} + /@tiptap/pm@2.3.2: + resolution: {integrity: sha512-39Bmg7XqWWJg/G5YvWc3QVEPmFNpuMa05gw0Ap7KAKHnHiwl87hosOIDD8dtdcMrtgJL9NwBfUjEJ3ne53U9Ag==} + dependencies: + prosemirror-changeset: 2.2.1 + prosemirror-collab: 1.3.1 + prosemirror-commands: 1.5.2 + prosemirror-dropcursor: 1.8.1 + prosemirror-gapcursor: 1.3.2 + prosemirror-history: 1.4.0 + prosemirror-inputrules: 1.4.0 + prosemirror-keymap: 1.2.2 + prosemirror-markdown: 1.12.0 + prosemirror-menu: 1.2.4 + prosemirror-model: 1.21.0 + prosemirror-schema-basic: 1.2.2 + prosemirror-schema-list: 1.3.0 + prosemirror-state: 1.4.3 + prosemirror-tables: 1.3.7 + prosemirror-trailing-node: 2.0.8(prosemirror-model@1.21.0)(prosemirror-state@1.4.3)(prosemirror-view@1.33.6) + prosemirror-transform: 1.9.0 + prosemirror-view: 1.33.6 + dev: false + + /@tiptap/react@2.3.2(@tiptap/core@2.3.2)(@tiptap/pm@2.3.2)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-NDvt3XfPn/6V3iAX3lqYGIuFPQgirUGKRyzfHl7ssIfpoY5VR5tRJkU4NigOr63NONrsgCgqJISG/nPY6YGw8w==} peerDependencies: - react: ^18.2.0 + '@tiptap/core': ^2.0.0 + '@tiptap/pm': ^2.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 dependencies: - mermaid: 10.9.0 + '@tiptap/core': 2.3.2(@tiptap/pm@2.3.2) + '@tiptap/extension-bubble-menu': 2.3.2(@tiptap/core@2.3.2)(@tiptap/pm@2.3.2) + '@tiptap/extension-floating-menu': 2.3.2(@tiptap/core@2.3.2)(@tiptap/pm@2.3.2) + '@tiptap/pm': 2.3.2 react: 18.2.0 - unist-util-visit: 5.0.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@tiptap/starter-kit@2.3.2(@tiptap/pm@2.3.2): + resolution: {integrity: sha512-7KdOxnYcmg2x2XGOAYssoz7iHLGDznoS5cNHjiOzuca+mO+5YutQ3j5yr/6+ithkX9/HZZwHJFQ6KORIARoNQg==} + dependencies: + '@tiptap/core': 2.3.2(@tiptap/pm@2.3.2) + '@tiptap/extension-blockquote': 2.3.2(@tiptap/core@2.3.2) + '@tiptap/extension-bold': 2.3.2(@tiptap/core@2.3.2) + '@tiptap/extension-bullet-list': 2.3.2(@tiptap/core@2.3.2) + '@tiptap/extension-code': 2.3.2(@tiptap/core@2.3.2) + '@tiptap/extension-code-block': 2.3.2(@tiptap/core@2.3.2)(@tiptap/pm@2.3.2) + '@tiptap/extension-document': 2.3.2(@tiptap/core@2.3.2) + '@tiptap/extension-dropcursor': 2.3.2(@tiptap/core@2.3.2)(@tiptap/pm@2.3.2) + '@tiptap/extension-gapcursor': 2.3.2(@tiptap/core@2.3.2)(@tiptap/pm@2.3.2) + '@tiptap/extension-hard-break': 2.3.2(@tiptap/core@2.3.2) + '@tiptap/extension-heading': 2.3.2(@tiptap/core@2.3.2) + '@tiptap/extension-history': 2.3.2(@tiptap/core@2.3.2)(@tiptap/pm@2.3.2) + '@tiptap/extension-horizontal-rule': 2.3.2(@tiptap/core@2.3.2)(@tiptap/pm@2.3.2) + '@tiptap/extension-italic': 2.3.2(@tiptap/core@2.3.2) + '@tiptap/extension-list-item': 2.3.2(@tiptap/core@2.3.2) + '@tiptap/extension-ordered-list': 2.3.2(@tiptap/core@2.3.2) + '@tiptap/extension-paragraph': 2.3.2(@tiptap/core@2.3.2) + '@tiptap/extension-strike': 2.3.2(@tiptap/core@2.3.2) + '@tiptap/extension-text': 2.3.2(@tiptap/core@2.3.2) transitivePeerDependencies: - - supports-color + - '@tiptap/pm' dev: false - /@theguild/remark-npm2yarn@0.2.1: - resolution: {integrity: sha512-jUTFWwDxtLEFtGZh/TW/w30ySaDJ8atKWH8dq2/IiQF61dPrGfETpl0WxD0VdBfuLOeU14/kop466oBSRO/5CA==} + /@tiptap/suggestion@2.3.2(@tiptap/core@2.3.2)(@tiptap/pm@2.3.2): + resolution: {integrity: sha512-3s1vDaBX4rEsVtVB0udi+v9h1koiGyIKu9BFFxaOSqHks9HvHYC3nagOFOkE4kmrbv8XJUigPuZ4K4XvH7iqKQ==} + peerDependencies: + '@tiptap/core': ^2.0.0 + '@tiptap/pm': ^2.0.0 dependencies: - npm-to-yarn: 2.2.1 - unist-util-visit: 5.0.0 + '@tiptap/core': 2.3.2(@tiptap/pm@2.3.2) + '@tiptap/pm': 2.3.2 dev: false /@trysound/sax@0.2.0: @@ -7125,6 +8051,17 @@ packages: resolution: {integrity: sha512-lZuNAY9xeJt7Bx4t4dx0rYCDqGPW8RXhQZK1td7d4H6E9zYbLoOtjBvfwdTKpsyxQI/2jv+armjX/RW+ZNpXOQ==} dev: false + /@types/linkify-it@3.0.5: + resolution: {integrity: sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==} + dev: false + + /@types/markdown-it@13.0.8: + resolution: {integrity: sha512-V+KmpgiipS+zoypeUSS9ojesWtY/0k4XfqcK2fnVrX/qInJhX7rsCxZ/rygiPH2zxlPPrhfuW0I6ddMcWTKLsg==} + dependencies: + '@types/linkify-it': 3.0.5 + '@types/mdurl': 1.0.5 + dev: false + /@types/mdast@3.0.12: resolution: {integrity: sha512-DT+iNIRNX884cx0/Q1ja7NyUPpZuv0KPyL5rGNxm1WC1OtHstl7n4Jb7nk+xacNShQMbczJjt8uFzznpp6kYBg==} dependencies: @@ -7137,6 +8074,10 @@ packages: '@types/unist': 2.0.7 dev: false + /@types/mdurl@1.0.5: + resolution: {integrity: sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==} + dev: false + /@types/mdx@2.0.6: resolution: {integrity: sha512-sVcwEG10aFU2KcM7cIA0M410UPv/DesOPyG8zMVk0QUDexHA3lYmGucpEpZ2dtWWhi2ip3CG+5g/iH0PwoW4Fw==} dev: false @@ -7191,6 +8132,10 @@ packages: resolution: {integrity: sha512-vmYJF0REqDyyU0gviezF/KHq/fYaUbFhkcNbQCuPGFQj6VTbXuHZoxs/Y7mutWe73C8AC6l9fFu8mSYiBAqkGA==} dev: false + /@types/node@18.15.3: + resolution: {integrity: sha512-p6ua9zBxz5otCmbpb5D3U4B5Nanw6Pk3PPyX05xnxbB/fRv71N7CPmORg7uAD5P70T0xmx1pzAx/FUfa5X+3cw==} + dev: false + /@types/node@20.11.30: resolution: {integrity: sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==} dependencies: @@ -8727,7 +9672,6 @@ packages: /any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} - dev: true /anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} @@ -8783,7 +9727,6 @@ packages: /arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} - dev: true /argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} @@ -8794,6 +9737,13 @@ packages: /argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + /aria-hidden@1.2.4: + resolution: {integrity: sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==} + engines: {node: '>=10'} + dependencies: + tslib: 2.6.2 + dev: false + /aria-query@5.3.0: resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} dependencies: @@ -9133,7 +10083,7 @@ packages: postcss: ^8.1.0 dependencies: browserslist: 4.21.10 - caniuse-lite: 1.0.30001520 + caniuse-lite: 1.0.30001612 fraction.js: 4.2.0 normalize-range: 0.1.2 picocolors: 1.0.0 @@ -9416,7 +10366,7 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001520 + caniuse-lite: 1.0.30001612 electron-to-chromium: 1.4.746 node-releases: 2.0.13 update-browserslist-db: 1.0.11(browserslist@4.21.10) @@ -9623,7 +10573,6 @@ packages: /camelcase-css@2.0.1: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} - dev: true /camelcase-keys@6.2.2: resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==} @@ -9651,13 +10600,10 @@ packages: resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} dependencies: browserslist: 4.23.0 - caniuse-lite: 1.0.30001520 + caniuse-lite: 1.0.30001612 lodash.memoize: 4.1.2 lodash.uniq: 4.5.0 - /caniuse-lite@1.0.30001520: - resolution: {integrity: sha512-tahF5O9EiiTzwTUqAeFjIZbn4Dnqxzz7ktrgGlMYNLH43Ul26IgTMH/zvL3DG0lZxBYnlT04axvInszUsZULdA==} - /caniuse-lite@1.0.30001612: resolution: {integrity: sha512-lFgnZ07UhaCcsSZgWW0K5j4e69dK1u/ltrL9lTUiFOwNHs12S3UMIEYgBV0Z6C6hRDev7iRnMzzYmKabYdXF9g==} @@ -9898,6 +10844,19 @@ packages: resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} engines: {node: '>=0.10.0'} + /cmdk@0.2.1(@types/react@18.2.78)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-U6//9lQ6JvT47+6OF6Gi8BvkxYQ8SCRRSKIJkthIMsFsLZRG0cKvTtuTaefyIKMQb8rvvXy0wGdpTNq/jPtm+g==} + peerDependencies: + react: ^18.0.0 + react-dom: ^18.0.0 + dependencies: + '@radix-ui/react-dialog': 1.0.0(@types/react@18.2.78)(react-dom@18.2.0)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + transitivePeerDependencies: + - '@types/react' + dev: false + /code-red@1.0.4: resolution: {integrity: sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==} dependencies: @@ -9989,7 +10948,6 @@ packages: /commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} - dev: true /commander@7.2.0: resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} @@ -10199,12 +11157,25 @@ packages: domutils: 3.1.0 nth-check: 2.1.1 + /css-styled@1.0.8: + resolution: {integrity: sha512-tCpP7kLRI8dI95rCh3Syl7I+v7PP+2JYOzWkl0bUEoSbJM+u8ITbutjlQVf0NC2/g4ULROJPi16sfwDIO8/84g==} + dependencies: + '@daybrush/utils': 1.13.0 + dev: false + + /css-to-mat@1.1.1: + resolution: {integrity: sha512-kvpxFYZb27jRd2vium35G7q5XZ2WJ9rWjDUMNT36M3Hc41qCrLXFM5iEKMGXcrPsKfXEN+8l/riB4QzwwwiEyQ==} + dependencies: + '@daybrush/utils': 1.13.0 + '@scena/matrix': 1.1.1 + dev: false + /css-tree@2.2.1: resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} dependencies: mdn-data: 2.0.28 - source-map-js: 1.0.2 + source-map-js: 1.2.0 /css-tree@2.3.1: resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} @@ -10731,7 +11702,7 @@ packages: resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} engines: {node: '>=0.11'} dependencies: - '@babel/runtime': 7.22.10 + '@babel/runtime': 7.24.4 /dax-sh@0.39.2: resolution: {integrity: sha512-gpuGEkBQM+5y6p4cWaw9+ePy5TNon+fdwFVtTI8leU3UhwhsBfPewRxMXGuQNC+M2b/MDGMlfgpqynkcd0C3FQ==} @@ -10969,6 +11940,10 @@ packages: resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==} engines: {node: '>=8'} + /detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + dev: false + /deterministic-object-hash@2.0.2: resolution: {integrity: sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ==} engines: {node: '>=18'} @@ -10991,7 +11966,6 @@ packages: /didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} - dev: true /diff-sequences@29.6.3: resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} @@ -12665,6 +13639,10 @@ packages: /fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} + /framework-utils@1.1.0: + resolution: {integrity: sha512-KAfqli5PwpFJ8o3psRNs8svpMGyCSAe8nmGcjQ0zZBWN2H6dZDnq+ABp3N3hdUmFeMrLtjOCTXD4yplUJIWceg==} + dev: false + /fresh@0.5.2: resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} engines: {node: '>= 0.6'} @@ -12789,6 +13767,13 @@ packages: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} + /gesto@1.19.4: + resolution: {integrity: sha512-hfr/0dWwh0Bnbb88s3QVJd1ZRJeOWcgHPPwmiH6NnafDYvhTsxg+SLYu+q/oPNh9JS3V+nlr6fNs8kvPAtcRDQ==} + dependencies: + '@daybrush/utils': 1.13.0 + '@scena/event-emitter': 1.0.5 + dev: false + /get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} @@ -12819,6 +13804,11 @@ packages: has-symbols: 1.0.3 hasown: 2.0.2 + /get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + dev: false + /get-own-enumerable-property-symbols@3.0.2: resolution: {integrity: sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==} dev: false @@ -12963,7 +13953,6 @@ packages: minimatch: 3.1.2 once: 1.4.0 path-is-absolute: 1.0.1 - dev: true /glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} @@ -13027,7 +14016,7 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: dir-glob: 3.0.1 - fast-glob: 3.3.1 + fast-glob: 3.3.2 ignore: 5.2.4 merge2: 1.4.1 slash: 4.0.0 @@ -13669,6 +14658,12 @@ packages: resolution: {integrity: sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==} dev: false + /invariant@2.2.4: + resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} + dependencies: + loose-envify: 1.4.0 + dev: false + /ioredis@5.3.2: resolution: {integrity: sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA==} engines: {node: '>=12.22.0'} @@ -14176,6 +15171,22 @@ packages: '@sideway/pinpoint': 2.0.0 dev: true + /jotai@2.8.0(@types/react@18.2.78)(react@18.2.0): + resolution: {integrity: sha512-yZNMC36FdLOksOr8qga0yLf14miCJlEThlp5DeFJNnqzm2+ZG7wLcJzoOyij5K6U6Xlc5ljQqPDlJRgqW0Y18g==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=17.0.0' + react: '>=17.0.0' + peerDependenciesMeta: + '@types/react': + optional: true + react: + optional: true + dependencies: + '@types/react': 18.2.78 + react: 18.2.0 + dev: false + /joycon@3.1.1: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} @@ -14297,6 +15308,19 @@ packages: commander: 8.3.0 dev: false + /keycode@2.2.1: + resolution: {integrity: sha512-Rdgz9Hl9Iv4QKi8b0OlCRQEzp4AgVxyCtz5S/+VIHezDmrDhkp2N2TqBWOLz0/gbeREXOOiI9/4b8BY9uw2vFg==} + dev: false + + /keycon@1.4.0: + resolution: {integrity: sha512-p1NAIxiRMH3jYfTeXRs2uWbVJ1WpEjpi8ktzUyBJsX7/wn2qu2VRXktneBLNtKNxJmlUYxRi9gOJt1DuthXR7A==} + dependencies: + '@cfcs/core': 0.0.6 + '@daybrush/utils': 1.13.0 + '@scena/event-emitter': 1.0.5 + keycode: 2.2.1 + dev: false + /keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} dependencies: @@ -14411,6 +15435,16 @@ packages: /lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + /linkify-it@5.0.0: + resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} + dependencies: + uc.micro: 2.1.0 + dev: false + + /linkifyjs@4.1.3: + resolution: {integrity: sha512-auMesunaJ8yfkHvK4gfg1K0SaKX/6Wn9g2Aac/NwX+l5VdmFZzo/hdPGxEOETj+ryRa4/fiOPjeeKURSAJx1sg==} + dev: false + /listhen@1.7.2: resolution: {integrity: sha512-7/HamOm5YD9Wb7CFgAZkKgVPA96WwhcTQoqtm2VTZGVbVVn3IWKRBTgrU7cchA3Q8k9iCsG8Osoi9GX4JsGM9g==} hasBin: true @@ -14483,6 +15517,10 @@ packages: resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} dev: false + /lodash.castarray@4.4.0: + resolution: {integrity: sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==} + dev: false + /lodash.defaults@4.2.0: resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} @@ -14493,6 +15531,10 @@ packages: /lodash.isarguments@3.1.0: resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==} + /lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + dev: false + /lodash.memoize@4.1.2: resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} @@ -14596,6 +15638,14 @@ packages: es5-ext: 0.10.64 dev: true + /lucide-react@0.368.0(react@18.2.0): + resolution: {integrity: sha512-soryVrCjheZs8rbXKdINw9B8iPi5OajBJZMJ1HORig89ljcOcEokKKAgGbg3QWxSXel7JwHOfDFUdDHAKyUAMw==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 + dependencies: + react: 18.2.0 + dev: false + /magic-string-ast@0.3.0: resolution: {integrity: sha512-0shqecEPgdFpnI3AP90epXyxZy9g6CRZ+SZ7BcqFwYmtFEnZ1jpevcV5HoyVnlDS9gCnc1UIg3Rsvp3Ci7r8OA==} engines: {node: '>=16.14.0'} @@ -14685,6 +15735,22 @@ packages: engines: {node: '>=0.10.0'} dev: false + /markdown-it-task-lists@2.1.1: + resolution: {integrity: sha512-TxFAc76Jnhb2OUu+n3yz9RMu4CwGfaT788br6HhEDlvWfdeJcLUsxk1Hgw2yJio0OXsxv7pyIPmvECY7bMbluA==} + dev: false + + /markdown-it@14.1.0: + resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==} + hasBin: true + dependencies: + argparse: 2.0.1 + entities: 4.5.0 + linkify-it: 5.0.0 + mdurl: 2.0.0 + punycode.js: 2.3.1 + uc.micro: 2.1.0 + dev: false + /markdown-table@3.0.3: resolution: {integrity: sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==} dev: false @@ -15041,6 +16107,10 @@ packages: /mdn-data@2.0.30: resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} + /mdurl@2.0.0: + resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} + dev: false + /media-typer@0.3.0: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} @@ -16044,7 +17114,6 @@ packages: any-promise: 1.3.0 object-assign: 4.1.1 thenify-all: 1.6.0 - dev: true /nanoid@3.3.6: resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==} @@ -16115,6 +17184,16 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /next-themes@0.3.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-/QHIrsYpd6Kfk7xakK4svpDI5mmXP0gfvCoJdGpZQ2TOrQZmsW0QxjaiLn8wbIKjtm4BTSqLoix4lxYYOnLJ/w==} + peerDependencies: + react: ^16.8 || ^17 || ^18 + react-dom: ^16.8 || ^17 || ^18 + dependencies: + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /next-tick@1.1.0: resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} dev: true @@ -16442,7 +17521,7 @@ packages: resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} dependencies: hosted-git-info: 2.8.9 - resolve: 1.22.4 + resolve: 1.22.8 semver: 5.7.2 validate-npm-package-license: 3.0.4 dev: false @@ -16469,6 +17548,46 @@ packages: engines: {node: '>=14.16'} dev: false + /novel@0.3.1(@types/react@18.2.78)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-om5ETtpo23BvKbN6wynSkSEwsJCWIEJjuBPYPk9jy9ktVqNUJjvVGGtCeah7Gr1L7Iy3Jtif1cMFokqsyzZQPw==} + peerDependencies: + react: ^18.0.0 + react-dom: ^18.0.0 + dependencies: + '@radix-ui/react-slot': 1.0.2(@types/react@18.2.78)(react@18.2.0) + '@tiptap/core': 2.3.2(@tiptap/pm@2.3.2) + '@tiptap/extension-color': 2.3.2(@tiptap/core@2.3.2)(@tiptap/extension-text-style@2.3.2) + '@tiptap/extension-highlight': 2.3.2(@tiptap/core@2.3.2) + '@tiptap/extension-horizontal-rule': 2.3.2(@tiptap/core@2.3.2)(@tiptap/pm@2.3.2) + '@tiptap/extension-image': 2.3.2(@tiptap/core@2.3.2) + '@tiptap/extension-link': 2.3.2(@tiptap/core@2.3.2)(@tiptap/pm@2.3.2) + '@tiptap/extension-placeholder': 2.0.3(@tiptap/core@2.3.2)(@tiptap/pm@2.3.2) + '@tiptap/extension-task-item': 2.3.2(@tiptap/core@2.3.2)(@tiptap/pm@2.3.2) + '@tiptap/extension-task-list': 2.3.2(@tiptap/core@2.3.2) + '@tiptap/extension-text-style': 2.3.2(@tiptap/core@2.3.2) + '@tiptap/extension-underline': 2.3.2(@tiptap/core@2.3.2) + '@tiptap/pm': 2.3.2 + '@tiptap/react': 2.3.2(@tiptap/core@2.3.2)(@tiptap/pm@2.3.2)(react-dom@18.2.0)(react@18.2.0) + '@tiptap/starter-kit': 2.3.2(@tiptap/pm@2.3.2) + '@tiptap/suggestion': 2.3.2(@tiptap/core@2.3.2)(@tiptap/pm@2.3.2) + '@types/node': 18.15.3 + cmdk: 0.2.1(@types/react@18.2.78)(react-dom@18.2.0)(react@18.2.0) + jotai: 2.8.0(@types/react@18.2.78)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-markdown: 8.0.7(@types/react@18.2.78)(react@18.2.0) + react-moveable: 0.56.0 + tippy.js: 6.3.7 + tiptap-extension-auto-joiner: 0.1.3 + tiptap-extension-global-drag-handle: 0.1.6 + tiptap-markdown: 0.8.10(@tiptap/core@2.3.2) + tunnel-rat: 0.1.2(@types/react@18.2.78)(react@18.2.0) + transitivePeerDependencies: + - '@types/react' + - immer + - supports-color + dev: false + /npm-bundled@2.0.1: resolution: {integrity: sha512-gZLxXdjEzE/+mOstGDqR6b0EkhJ+kM6fxM6vUuckuctuVPh80Q6pw/rSZj9s4Gex9GxWtIicO1pc8DB9KZWudw==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} @@ -16987,7 +18106,6 @@ packages: /object-hash@3.0.0: resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} engines: {node: '>= 6'} - dev: true /object-inspect@1.12.3: resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} @@ -17180,6 +18298,10 @@ packages: strip-ansi: 7.1.0 dev: false + /orderedmap@2.1.1: + resolution: {integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==} + dev: false + /os-tmpdir@1.0.2: resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} engines: {node: '>=0.10.0'} @@ -17193,6 +18315,12 @@ packages: resolution: {integrity: sha512-Ou3dJ6bA/UJ5GVHxah4LnqDwZRwAmWxrG3wtrHrbGnP4RnLCtA64A4F+ae7Y8ww660JaddSoArUR5HjipWSHAQ==} dev: false + /overlap-area@1.1.0: + resolution: {integrity: sha512-3dlJgJCaVeXH0/eZjYVJvQiLVVrPO4U1ZGqlATtx6QGO3b5eNM6+JgUKa7oStBTdYuGTk7gVoABCW6Tp+dhRdw==} + dependencies: + '@daybrush/utils': 1.13.0 + dev: false + /p-cancelable@3.0.0: resolution: {integrity: sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==} engines: {node: '>=12.20'} @@ -17479,7 +18607,6 @@ packages: /pify@2.3.0: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} - dev: true /pify@4.0.1: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} @@ -17514,7 +18641,6 @@ packages: /pirates@4.0.6: resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} engines: {node: '>= 6'} - dev: true /pkg-dir@4.2.0: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} @@ -17564,7 +18690,7 @@ packages: postcss: ^8.2.2 dependencies: postcss: 8.4.38 - postcss-selector-parser: 6.0.13 + postcss-selector-parser: 6.0.16 postcss-value-parser: 4.2.0 /postcss-colormin@6.0.0(postcss@8.4.38): @@ -17691,7 +18817,6 @@ packages: postcss-value-parser: 4.2.0 read-cache: 1.0.0 resolve: 1.22.4 - dev: true /postcss-js@4.0.1(postcss@8.4.38): resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} @@ -17701,7 +18826,6 @@ packages: dependencies: camelcase-css: 2.0.1 postcss: 8.4.38 - dev: true /postcss-load-config@3.1.4(postcss@8.4.38): resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} @@ -17720,6 +18844,22 @@ packages: yaml: 1.10.2 dev: false + /postcss-load-config@4.0.1: + resolution: {integrity: sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==} + engines: {node: '>= 14'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + dependencies: + lilconfig: 2.1.0 + yaml: 2.4.1 + dev: true + /postcss-load-config@4.0.1(postcss@8.4.38): resolution: {integrity: sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==} engines: {node: '>= 14'} @@ -17735,7 +18875,6 @@ packages: lilconfig: 2.1.0 postcss: 8.4.38 yaml: 2.3.1 - dev: true /postcss-load-config@5.0.3(postcss@8.4.38): resolution: {integrity: sha512-90pBBI5apUVruIEdCxZic93Wm+i9fTrp7TXbgdUCH+/L+2WnfpITSpq5dFU/IPvbv7aNiMlQISpUkAm3fEcvgQ==} @@ -17785,7 +18924,7 @@ packages: caniuse-api: 3.0.0 cssnano-utils: 4.0.0(postcss@8.4.38) postcss: 8.4.38 - postcss-selector-parser: 6.0.13 + postcss-selector-parser: 6.0.16 dev: true /postcss-merge-rules@6.1.1(postcss@8.4.38): @@ -17872,7 +19011,7 @@ packages: postcss: ^8.2.15 dependencies: postcss: 8.4.38 - postcss-selector-parser: 6.0.13 + postcss-selector-parser: 6.0.16 dev: true /postcss-minify-selectors@6.0.4(postcss@8.4.38): @@ -18143,6 +19282,14 @@ packages: postcss: 8.4.38 dev: false + /postcss-selector-parser@6.0.10: + resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==} + engines: {node: '>=4'} + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + dev: false + /postcss-selector-parser@6.0.13: resolution: {integrity: sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==} engines: {node: '>=4'} @@ -18185,7 +19332,7 @@ packages: postcss: ^8.2.15 dependencies: postcss: 8.4.38 - postcss-selector-parser: 6.0.13 + postcss-selector-parser: 6.0.16 dev: true /postcss-unique-selectors@6.0.4(postcss@8.4.38): @@ -18373,44 +19520,186 @@ packages: /process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} - /process-warning@3.0.0: - resolution: {integrity: sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==} + /process-warning@3.0.0: + resolution: {integrity: sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==} + + /process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + + /promise-inflight@1.0.1: + resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==} + peerDependencies: + bluebird: '*' + peerDependenciesMeta: + bluebird: + optional: true + + /promise-retry@2.0.1: + resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} + engines: {node: '>=10'} + dependencies: + err-code: 2.0.3 + retry: 0.12.0 + + /prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + + /prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + /property-information@6.2.0: + resolution: {integrity: sha512-kma4U7AFCTwpqq5twzC1YVIDXSqg6qQK6JN0smOw8fgRy1OkMi0CYSzFmsy6dnqSenamAtj0CyXMUJ1Mf6oROg==} + dev: false + + /prosemirror-changeset@2.2.1: + resolution: {integrity: sha512-J7msc6wbxB4ekDFj+n9gTW/jav/p53kdlivvuppHsrZXCaQdVgRghoZbSS3kwrRyAstRVQ4/+u5k7YfLgkkQvQ==} + dependencies: + prosemirror-transform: 1.9.0 + dev: false + + /prosemirror-collab@1.3.1: + resolution: {integrity: sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==} + dependencies: + prosemirror-state: 1.4.3 + dev: false + + /prosemirror-commands@1.5.2: + resolution: {integrity: sha512-hgLcPaakxH8tu6YvVAaILV2tXYsW3rAdDR8WNkeKGcgeMVQg3/TMhPdVoh7iAmfgVjZGtcOSjKiQaoeKjzd2mQ==} + dependencies: + prosemirror-model: 1.21.0 + prosemirror-state: 1.4.3 + prosemirror-transform: 1.9.0 + dev: false + + /prosemirror-dropcursor@1.8.1: + resolution: {integrity: sha512-M30WJdJZLyXHi3N8vxN6Zh5O8ZBbQCz0gURTfPmTIBNQ5pxrdU7A58QkNqfa98YEjSAL1HUyyU34f6Pm5xBSGw==} + dependencies: + prosemirror-state: 1.4.3 + prosemirror-transform: 1.9.0 + prosemirror-view: 1.33.6 + dev: false + + /prosemirror-gapcursor@1.3.2: + resolution: {integrity: sha512-wtjswVBd2vaQRrnYZaBCbyDqr232Ed4p2QPtRIUK5FuqHYKGWkEwl08oQM4Tw7DOR0FsasARV5uJFvMZWxdNxQ==} + dependencies: + prosemirror-keymap: 1.2.2 + prosemirror-model: 1.21.0 + prosemirror-state: 1.4.3 + prosemirror-view: 1.33.6 + dev: false + + /prosemirror-history@1.4.0: + resolution: {integrity: sha512-UUiGzDVcqo1lovOPdi9YxxUps3oBFWAIYkXLu3Ot+JPv1qzVogRbcizxK3LhHmtaUxclohgiOVesRw5QSlMnbQ==} + dependencies: + prosemirror-state: 1.4.3 + prosemirror-transform: 1.9.0 + prosemirror-view: 1.33.6 + rope-sequence: 1.3.4 + dev: false + + /prosemirror-inputrules@1.4.0: + resolution: {integrity: sha512-6ygpPRuTJ2lcOXs9JkefieMst63wVJBgHZGl5QOytN7oSZs3Co/BYbc3Yx9zm9H37Bxw8kVzCnDsihsVsL4yEg==} + dependencies: + prosemirror-state: 1.4.3 + prosemirror-transform: 1.9.0 + dev: false + + /prosemirror-keymap@1.2.2: + resolution: {integrity: sha512-EAlXoksqC6Vbocqc0GtzCruZEzYgrn+iiGnNjsJsH4mrnIGex4qbLdWWNza3AW5W36ZRrlBID0eM6bdKH4OStQ==} + dependencies: + prosemirror-state: 1.4.3 + w3c-keyname: 2.2.8 + dev: false + + /prosemirror-markdown@1.12.0: + resolution: {integrity: sha512-6F5HS8Z0HDYiS2VQDZzfZP6A0s/I0gbkJy8NCzzDMtcsz3qrfqyroMMeoSjAmOhDITyon11NbXSzztfKi+frSQ==} + dependencies: + markdown-it: 14.1.0 + prosemirror-model: 1.21.0 + dev: false + + /prosemirror-menu@1.2.4: + resolution: {integrity: sha512-S/bXlc0ODQup6aiBbWVsX/eM+xJgCTAfMq/nLqaO5ID/am4wS0tTCIkzwytmao7ypEtjj39i7YbJjAgO20mIqA==} + dependencies: + crelt: 1.0.6 + prosemirror-commands: 1.5.2 + prosemirror-history: 1.4.0 + prosemirror-state: 1.4.3 + dev: false + + /prosemirror-model@1.21.0: + resolution: {integrity: sha512-zLpS1mVCZLA7VTp82P+BfMiYVPcX1/z0Mf3gsjKZtzMWubwn2pN7CceMV0DycjlgE5JeXPR7UF4hJPbBV98oWA==} + dependencies: + orderedmap: 2.1.1 + dev: false - /process@0.11.10: - resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} - engines: {node: '>= 0.6.0'} + /prosemirror-schema-basic@1.2.2: + resolution: {integrity: sha512-/dT4JFEGyO7QnNTe9UaKUhjDXbTNkiWTq/N4VpKaF79bBjSExVV2NXmJpcM7z/gD7mbqNjxbmWW5nf1iNSSGnw==} + dependencies: + prosemirror-model: 1.21.0 + dev: false - /promise-inflight@1.0.1: - resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==} - peerDependencies: - bluebird: '*' - peerDependenciesMeta: - bluebird: - optional: true + /prosemirror-schema-list@1.3.0: + resolution: {integrity: sha512-Hz/7gM4skaaYfRPNgr421CU4GSwotmEwBVvJh5ltGiffUJwm7C8GfN/Bc6DR1EKEp5pDKhODmdXXyi9uIsZl5A==} + dependencies: + prosemirror-model: 1.21.0 + prosemirror-state: 1.4.3 + prosemirror-transform: 1.9.0 + dev: false - /promise-retry@2.0.1: - resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} - engines: {node: '>=10'} + /prosemirror-state@1.4.3: + resolution: {integrity: sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q==} dependencies: - err-code: 2.0.3 - retry: 0.12.0 + prosemirror-model: 1.21.0 + prosemirror-transform: 1.9.0 + prosemirror-view: 1.33.6 + dev: false - /prompts@2.4.2: - resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} - engines: {node: '>= 6'} + /prosemirror-tables@1.3.7: + resolution: {integrity: sha512-oEwX1wrziuxMtwFvdDWSFHVUWrFJWt929kVVfHvtTi8yvw+5ppxjXZkMG/fuTdFo+3DXyIPSKfid+Be1npKXDA==} dependencies: - kleur: 3.0.3 - sisteransi: 1.0.5 + prosemirror-keymap: 1.2.2 + prosemirror-model: 1.21.0 + prosemirror-state: 1.4.3 + prosemirror-transform: 1.9.0 + prosemirror-view: 1.33.6 + dev: false - /prop-types@15.8.1: - resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + /prosemirror-trailing-node@2.0.8(prosemirror-model@1.21.0)(prosemirror-state@1.4.3)(prosemirror-view@1.33.6): + resolution: {integrity: sha512-ujRYhSuhQb1Jsarh1IHqb2KoSnRiD7wAMDGucP35DN7j5af6X7B18PfdPIrbwsPTqIAj0fyOvxbuPsWhNvylmA==} + peerDependencies: + prosemirror-model: ^1.19.0 + prosemirror-state: ^1.4.2 + prosemirror-view: ^1.31.2 dependencies: - loose-envify: 1.4.0 - object-assign: 4.1.1 - react-is: 16.13.1 + '@remirror/core-constants': 2.0.2 + escape-string-regexp: 4.0.0 + prosemirror-model: 1.21.0 + prosemirror-state: 1.4.3 + prosemirror-view: 1.33.6 + dev: false - /property-information@6.2.0: - resolution: {integrity: sha512-kma4U7AFCTwpqq5twzC1YVIDXSqg6qQK6JN0smOw8fgRy1OkMi0CYSzFmsy6dnqSenamAtj0CyXMUJ1Mf6oROg==} + /prosemirror-transform@1.9.0: + resolution: {integrity: sha512-5UXkr1LIRx3jmpXXNKDhv8OyAOeLTGuXNwdVfg8x27uASna/wQkr9p6fD3eupGOi4PLJfbezxTyi/7fSJypXHg==} + dependencies: + prosemirror-model: 1.21.0 + dev: false + + /prosemirror-view@1.33.6: + resolution: {integrity: sha512-zRLUNgLIQfd8IfGprsXxWTjdA8xEAFJe8cDNrOptj6Mop9sj+BMeVbJvceyAYCm5G2dOdT2prctH7K9dfnpIMw==} + dependencies: + prosemirror-model: 1.21.0 + prosemirror-state: 1.4.3 + prosemirror-transform: 1.9.0 dev: false /proto-list@1.2.4: @@ -18453,6 +19742,11 @@ packages: dev: false optional: true + /punycode.js@2.3.1: + resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} + engines: {node: '>=6'} + dev: false + /punycode@2.3.0: resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} engines: {node: '>=6'} @@ -18541,6 +19835,13 @@ packages: strip-json-comments: 2.0.1 dev: false + /react-css-styled@1.1.9: + resolution: {integrity: sha512-M7fJZ3IWFaIHcZEkoFOnkjdiUFmwd8d+gTh2bpqMOcnxy/0Gsykw4dsL4QBiKsxcGow6tETUa4NAUcmJF+/nfw==} + dependencies: + css-styled: 1.0.8 + framework-utils: 1.1.0 + dev: false + /react-dom@18.2.0(react@18.2.0): resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} peerDependencies: @@ -18564,11 +19865,133 @@ packages: /react-is@18.2.0: resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} + /react-markdown@8.0.7(@types/react@18.2.78)(react@18.2.0): + resolution: {integrity: sha512-bvWbzG4MtOU62XqBx3Xx+zB2raaFFsq4mYiAzfjXJMEz2sixgeAfraA3tvzULF02ZdOMUOKTBFFaZJDDrq+BJQ==} + peerDependencies: + '@types/react': '>=16' + react: '>=16' + dependencies: + '@types/hast': 2.3.5 + '@types/prop-types': 15.7.5 + '@types/react': 18.2.78 + '@types/unist': 2.0.7 + comma-separated-tokens: 2.0.3 + hast-util-whitespace: 2.0.1 + prop-types: 15.8.1 + property-information: 6.2.0 + react: 18.2.0 + react-is: 18.2.0 + remark-parse: 10.0.2 + remark-rehype: 10.1.0 + space-separated-tokens: 2.0.2 + style-to-object: 0.4.2 + unified: 10.1.2 + unist-util-visit: 4.1.2 + vfile: 5.3.7 + transitivePeerDependencies: + - supports-color + dev: false + + /react-moveable@0.56.0: + resolution: {integrity: sha512-FmJNmIOsOA36mdxbrc/huiE4wuXSRlmon/o+/OrfNhSiYYYL0AV5oObtPluEhb2Yr/7EfYWBHTxF5aWAvjg1SA==} + dependencies: + '@daybrush/utils': 1.13.0 + '@egjs/agent': 2.4.3 + '@egjs/children-differ': 1.0.1 + '@egjs/list-differ': 1.0.1 + '@scena/dragscroll': 1.4.0 + '@scena/event-emitter': 1.0.5 + '@scena/matrix': 1.1.1 + css-to-mat: 1.1.1 + framework-utils: 1.1.0 + gesto: 1.19.4 + overlap-area: 1.1.0 + react-css-styled: 1.1.9 + react-selecto: 1.26.3 + dev: false + /react-refresh@0.14.0: resolution: {integrity: sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==} engines: {node: '>=0.10.0'} dev: false + /react-remove-scroll-bar@2.3.6(@types/react@18.2.78)(react@18.2.0): + resolution: {integrity: sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.2.78 + react: 18.2.0 + react-style-singleton: 2.2.1(@types/react@18.2.78)(react@18.2.0) + tslib: 2.6.2 + dev: false + + /react-remove-scroll@2.5.4(@types/react@18.2.78)(react@18.2.0): + resolution: {integrity: sha512-xGVKJJr0SJGQVirVFAUZ2k1QLyO6m+2fy0l8Qawbp5Jgrv3DeLalrfMNBFSlmz5kriGGzsVBtGVnf4pTKIhhWA==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.2.78 + react: 18.2.0 + react-remove-scroll-bar: 2.3.6(@types/react@18.2.78)(react@18.2.0) + react-style-singleton: 2.2.1(@types/react@18.2.78)(react@18.2.0) + tslib: 2.6.2 + use-callback-ref: 1.3.2(@types/react@18.2.78)(react@18.2.0) + use-sidecar: 1.1.2(@types/react@18.2.78)(react@18.2.0) + dev: false + + /react-remove-scroll@2.5.5(@types/react@18.2.78)(react@18.2.0): + resolution: {integrity: sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.2.78 + react: 18.2.0 + react-remove-scroll-bar: 2.3.6(@types/react@18.2.78)(react@18.2.0) + react-style-singleton: 2.2.1(@types/react@18.2.78)(react@18.2.0) + tslib: 2.6.2 + use-callback-ref: 1.3.2(@types/react@18.2.78)(react@18.2.0) + use-sidecar: 1.1.2(@types/react@18.2.78)(react@18.2.0) + dev: false + + /react-selecto@1.26.3: + resolution: {integrity: sha512-Ubik7kWSnZyQEBNro+1k38hZaI1tJarE+5aD/qsqCOA1uUBSjgKVBy3EWRzGIbdmVex7DcxznFZLec/6KZNvwQ==} + dependencies: + selecto: 1.26.3 + dev: false + + /react-style-singleton@2.2.1(@types/react@18.2.78)(react@18.2.0): + resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.2.78 + get-nonce: 1.0.1 + invariant: 2.2.4 + react: 18.2.0 + tslib: 2.6.2 + dev: false + /react@18.2.0: resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} engines: {node: '>=0.10.0'} @@ -18579,7 +20002,6 @@ packages: resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} dependencies: pify: 2.3.0 - dev: true /read-package-json-fast@3.0.2: resolution: {integrity: sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==} @@ -19018,7 +20440,7 @@ packages: resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} hasBin: true dependencies: - is-core-module: 2.13.0 + is-core-module: 2.13.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 @@ -19249,6 +20671,10 @@ packages: '@rollup/rollup-win32-x64-msvc': 4.16.2 fsevents: 2.3.3 + /rope-sequence@1.3.4: + resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==} + dev: false + /run-applescript@5.0.0: resolution: {integrity: sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==} engines: {node: '>=12'} @@ -19369,6 +20795,21 @@ packages: /secure-json-parse@2.7.0: resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==} + /selecto@1.26.3: + resolution: {integrity: sha512-gZHgqMy5uyB6/2YDjv3Qqaf7bd2hTDOpPdxXlrez4R3/L0GiEWDCFaUfrflomgqdb3SxHF2IXY0Jw0EamZi7cw==} + dependencies: + '@daybrush/utils': 1.13.0 + '@egjs/children-differ': 1.0.1 + '@scena/dragscroll': 1.4.0 + '@scena/event-emitter': 1.0.5 + css-styled: 1.0.8 + css-to-mat: 1.1.1 + framework-utils: 1.1.0 + gesto: 1.19.4 + keycon: 1.4.0 + overlap-area: 1.1.0 + dev: false + /selfsigned@2.4.1: resolution: {integrity: sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==} engines: {node: '>=10'} @@ -19747,6 +21188,16 @@ packages: dependencies: atomic-sleep: 1.0.0 + /sonner@1.4.41(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-uG511ggnnsw6gcn/X+YKkWPo5ep9il9wYi3QJxHsYe7yTZ4+cOd1wuodOUmOpFuXL+/RE3R04LczdNCDygTDgQ==} + peerDependencies: + react: ^18.0.0 + react-dom: ^18.0.0 + dependencies: + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /sorcery@0.11.0: resolution: {integrity: sha512-J69LQ22xrQB1cIFJhPfgtLuI6BpWRiWu1Y3vSsIwK/eAScqJxd/+CJlUuHQRdX2C9NGFamq+KqNywGgaThwfHw==} hasBin: true @@ -20126,7 +21577,7 @@ packages: dependencies: browserslist: 4.21.10 postcss: 8.4.38 - postcss-selector-parser: 6.0.13 + postcss-selector-parser: 6.0.16 dev: true /stylehacks@6.1.1(postcss@8.4.38): @@ -20155,7 +21606,6 @@ packages: mz: 2.7.0 pirates: 4.0.6 ts-interface-checker: 0.1.13 - dev: true /superjson@2.2.1: resolution: {integrity: sha512-8iGv75BYOa0xRJHK5vRLEjE2H/i4lulTjzpUXic3Eg8akftYjkmQDa8JARQ42rlczXyFR3IeRoeFCc7RxHsYZA==} @@ -20392,6 +21842,14 @@ packages: '@babel/runtime': 7.24.4 dev: false + /tailwindcss-animate@1.0.7(tailwindcss@3.4.3): + resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==} + peerDependencies: + tailwindcss: '>=3.0.0 || insiders' + dependencies: + tailwindcss: 3.4.3 + dev: true + /tailwindcss@3.4.3: resolution: {integrity: sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==} engines: {node: '>=14.0.0'} @@ -20421,7 +21879,6 @@ packages: sucrase: 3.34.0 transitivePeerDependencies: - ts-node - dev: true /tapable@2.2.1: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} @@ -20534,13 +21991,11 @@ packages: engines: {node: '>=0.8'} dependencies: thenify: 3.3.1 - dev: true /thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} dependencies: any-promise: 1.3.0 - dev: true /thread-stream@2.6.0: resolution: {integrity: sha512-t4eNiKdGwd1EV6tx76mRbrOqwvkxz+ssOiQXEXw88m4p/Xp6679vg16sf39BAstRjHOiWIqp5+J2ylHk3pU30g==} @@ -20575,6 +22030,32 @@ packages: resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==} engines: {node: '>=14.0.0'} + /tippy.js@6.3.7: + resolution: {integrity: sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==} + dependencies: + '@popperjs/core': 2.11.8 + dev: false + + /tiptap-extension-auto-joiner@0.1.3: + resolution: {integrity: sha512-nY3aKeCpVb2WjjVEZkLtEqxsK3KU1zGioyglMhK1sUFNjKDccOfRyz/YDKrHRAVsKJPGnk2A8VA1827iGEAXWQ==} + dev: false + + /tiptap-extension-global-drag-handle@0.1.6: + resolution: {integrity: sha512-kjRFd/glJGTHEglHPNWtML1iINuEBG4eWvWbNIuuXE2LbylFXYHKVgLvZgxilqXSBH/o+lKDEp7/iL3lKvnxGw==} + dev: false + + /tiptap-markdown@0.8.10(@tiptap/core@2.3.2): + resolution: {integrity: sha512-iDVkR2BjAqkTDtFX0h94yVvE2AihCXlF0Q7RIXSJPRSR5I0PA1TMuAg6FHFpmqTn4tPxJ0by0CK7PUMlnFLGEQ==} + peerDependencies: + '@tiptap/core': ^2.0.3 + dependencies: + '@tiptap/core': 2.3.2(@tiptap/pm@2.3.2) + '@types/markdown-it': 13.0.8 + markdown-it: 14.1.0 + markdown-it-task-lists: 2.1.1 + prosemirror-markdown: 1.12.0 + dev: false + /title@3.5.3: resolution: {integrity: sha512-20JyowYglSEeCvZv3EZ0nZ046vLarO37prvV0mbtQV7C8DJPGgN967r8SJkqd3XK3K3lD3/Iyfp3avjfil8Q2Q==} hasBin: true @@ -20690,7 +22171,6 @@ packages: /ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - dev: true /ts-json-schema-generator@1.5.1: resolution: {integrity: sha512-apX5qG2+NA66j7b4AJm8q/DpdTeOsjfh7A3LpKsUiil0FepkNwtN28zYgjrsiiya2/OPhsr/PSjX5FUYg79rCg==} @@ -20787,6 +22267,45 @@ packages: - ts-node dev: true + /tsup@8.0.2(typescript@5.4.5): + resolution: {integrity: sha512-NY8xtQXdH7hDUAZwcQdY/Vzlw9johQsaqf7iwZ6g1DOUlFYQ5/AtVAjTvihhEyeRlGo4dLRVHtrRaL35M1daqQ==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + '@microsoft/api-extractor': ^7.36.0 + '@swc/core': ^1 + postcss: ^8.4.12 + typescript: '>=4.5.0' + peerDependenciesMeta: + '@microsoft/api-extractor': + optional: true + '@swc/core': + optional: true + postcss: + optional: true + typescript: + optional: true + dependencies: + bundle-require: 4.0.1(esbuild@0.19.2) + cac: 6.7.14 + chokidar: 3.5.3 + debug: 4.3.4 + esbuild: 0.19.2 + execa: 5.1.1 + globby: 11.1.0 + joycon: 3.1.1 + postcss-load-config: 4.0.1 + resolve-from: 5.0.0 + rollup: 4.16.2 + source-map: 0.8.0-beta.0 + sucrase: 3.34.0 + tree-kill: 1.2.2 + typescript: 5.4.5 + transitivePeerDependencies: + - supports-color + - ts-node + dev: true + /tsx@4.7.2: resolution: {integrity: sha512-BCNd4kz6fz12fyrgCTEdZHGJ9fWTGeUzXmQysh0RVocDY3h4frk05ZNCXSy4kIenF7y/QnrdiVpTsyNRn6vlAw==} engines: {node: '>=18.0.0'} @@ -20830,6 +22349,16 @@ packages: dev: false optional: true + /tunnel-rat@0.1.2(@types/react@18.2.78)(react@18.2.0): + resolution: {integrity: sha512-lR5VHmkPhzdhrM092lI2nACsLO4QubF0/yoOhzX7c+wIpbN1GjHNzCc91QlpxBi+cnx8vVJ+Ur6vL5cEoQPFpQ==} + dependencies: + zustand: 4.5.2(@types/react@18.2.78)(react@18.2.0) + transitivePeerDependencies: + - '@types/react' + - immer + - react + dev: false + /turbo-darwin-64@1.13.2: resolution: {integrity: sha512-CCSuD8CfmtncpohCuIgq7eAzUas0IwSbHfI8/Q3vKObTdXyN8vAo01gwqXjDGpzG9bTEVedD0GmLbD23dR0MLA==} cpu: [x64] @@ -21031,6 +22560,10 @@ packages: engines: {node: '>=14.17'} hasBin: true + /uc.micro@2.1.0: + resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} + dev: false + /ufo@1.5.3: resolution: {integrity: sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==} @@ -21678,6 +23211,46 @@ packages: /urlpattern-polyfill@8.0.2: resolution: {integrity: sha512-Qp95D4TPJl1kC9SKigDcqgyM2VDVO4RiJc2d4qe5GrYm+zbIQCWWKAFaJNQ4BhdFeDGwBmAxqJBwWSJDb9T3BQ==} + /use-callback-ref@1.3.2(@types/react@18.2.78)(react@18.2.0): + resolution: {integrity: sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.2.78 + react: 18.2.0 + tslib: 2.6.2 + dev: false + + /use-debounce@9.0.4(react@18.2.0): + resolution: {integrity: sha512-6X8H/mikbrt0XE8e+JXRtZ8yYVvKkdYRfmIhWZYsP8rcNs9hk3APV8Ua2mFkKRLcJKVdnX2/Vwrmg2GWKUQEaQ==} + engines: {node: '>= 10.0.0'} + peerDependencies: + react: '>=16.8.0' + dependencies: + react: 18.2.0 + dev: false + + /use-sidecar@1.1.2(@types/react@18.2.78)(react@18.2.0): + resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.9.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.2.78 + detect-node-es: 1.1.0 + react: 18.2.0 + tslib: 2.6.2 + dev: false + /use-sync-external-store@1.2.0(react@18.2.0): resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==} peerDependencies: @@ -21923,7 +23496,7 @@ packages: chokidar: 3.6.0 commander: 8.3.0 eslint: 8.57.0 - fast-glob: 3.3.1 + fast-glob: 3.3.2 fs-extra: 11.2.0 npm-run-path: 4.0.1 semver: 7.5.4 @@ -22877,7 +24450,6 @@ packages: /yaml@2.3.1: resolution: {integrity: sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==} engines: {node: '>= 14'} - dev: true /yaml@2.4.1: resolution: {integrity: sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==} @@ -22970,6 +24542,26 @@ packages: /zod@3.23.0: resolution: {integrity: sha512-OFLT+LTocvabn6q76BTwVB0hExEBS0IduTr3cqZyMqEDbOnYmcU+y0tUAYbND4uwclpBGi4I4UUBGzylWpjLGA==} + /zustand@4.5.2(@types/react@18.2.78)(react@18.2.0): + resolution: {integrity: sha512-2cN1tPkDVkwCy5ickKrI7vijSjPksFRfqS6237NzT0vqSsztTNnQdHw9mmN7uBdk3gceVXU0a+21jFzFzAc9+g==} + engines: {node: '>=12.7.0'} + peerDependencies: + '@types/react': '>=16.8' + immer: '>=9.0.6' + react: '>=16.8' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + dependencies: + '@types/react': 18.2.78 + react: 18.2.0 + use-sync-external-store: 1.2.0(react@18.2.0) + dev: false + /zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} dev: false From 0ad4378d38a686615840dc1136d25b7f6ddaef08 Mon Sep 17 00:00:00 2001 From: juliusmarminge Date: Sun, 12 May 2024 21:05:01 +0200 Subject: [PATCH 2/8] cs --- .changeset/strong-suits-wonder.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .changeset/strong-suits-wonder.md diff --git a/.changeset/strong-suits-wonder.md b/.changeset/strong-suits-wonder.md new file mode 100644 index 0000000000..6f4ee3533c --- /dev/null +++ b/.changeset/strong-suits-wonder.md @@ -0,0 +1,8 @@ +--- +"uploadthing": minor +"@uploadthing/react": minor +--- + +feat: add `generateReactHelpers.getRouteConfig`, `isValidFileSize` and `isValidFileType` helpers + +💡 See https://github.com/pingdotgg/uploadthing/blob/main/examples/with-novel/uploadthing/novel-plugin.ts#L50-L61 for a live example utilizing these helpers. \ No newline at end of file From 57b64183838b5ffe9b8126d9e9454a2c54d6b89d Mon Sep 17 00:00:00 2001 From: juliusmarminge Date: Sun, 12 May 2024 21:19:50 +0200 Subject: [PATCH 3/8] rm try-catch --- packages/uploadthing/src/client.ts | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/packages/uploadthing/src/client.ts b/packages/uploadthing/src/client.ts index 0918aebd38..71f1d40d16 100644 --- a/packages/uploadthing/src/client.ts +++ b/packages/uploadthing/src/client.ts @@ -63,14 +63,12 @@ export const isValidFileType = ( file: File, routeConfig: ExpandedRouteConfig, ) => { - try { - const type = Effect.runSync( + return Effect.runSync( + Effect.flatMap( getTypeFromFileName(file.name, objectKeys(routeConfig)), - ); - return file.type.includes(type); - } catch { - return false; - } + (type) => Effect.succeed(file.type.includes(type)), + ).pipe(Effect.catchAll(() => Effect.succeed(false))), + ); }; /** @@ -81,17 +79,13 @@ export const isValidFileSize = ( file: File, routeConfig: ExpandedRouteConfig, ) => { - try { - const maxFileSize = Effect.runSync( - Effect.flatMap( - getTypeFromFileName(file.name, objectKeys(routeConfig)), - (type) => fileSizeToBytes(routeConfig[type]!.maxFileSize), - ), - ); - return file.size <= maxFileSize; - } catch { - return false; - } + return Effect.runSync( + getTypeFromFileName(file.name, objectKeys(routeConfig)).pipe( + Effect.andThen((type) => fileSizeToBytes(routeConfig[type]!.maxFileSize)), + Effect.andThen((maxFileSize) => Effect.succeed(file.size <= maxFileSize)), + Effect.catchAll(() => Effect.succeed(false)), + ), + ); }; const uploadFilesInternal = < From 489a2f7d84fdd417d4d401b4ccad92feef692447 Mon Sep 17 00:00:00 2001 From: juliusmarminge Date: Sun, 12 May 2024 21:40:10 +0200 Subject: [PATCH 4/8] swap --- packages/react/src/useUploadThing.ts | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/packages/react/src/useUploadThing.ts b/packages/react/src/useUploadThing.ts index 93477285ff..0e3a755d2e 100644 --- a/packages/react/src/useUploadThing.ts +++ b/packages/react/src/useUploadThing.ts @@ -1,6 +1,9 @@ import { useRef, useState } from "react"; -import type { EndpointMetadata } from "@uploadthing/shared"; +import type { + EndpointMetadata, + ExpandedRouteConfig, +} from "@uploadthing/shared"; import { INTERNAL_DO_NOT_USE__fatalClientError, resolveMaybeUrlArg, @@ -26,13 +29,16 @@ declare const globalThis: { __UPLOADTHING?: EndpointMetadata; }; -const useEndpointMetadata = (url: URL, endpoint: string) => { +const useRouteConfig = ( + url: URL, + endpoint: string, +): ExpandedRouteConfig | undefined => { const maybeServerData = globalThis.__UPLOADTHING; const { data } = useFetch( // Don't fetch if we already have the data maybeServerData ? undefined : url.href, ); - return (maybeServerData ?? data)?.find((x) => x.slug === endpoint); + return (maybeServerData ?? data)?.find((x) => x.slug === endpoint)?.config; }; export const INTERNAL_uploadthingHookGen = < @@ -66,12 +72,6 @@ export const INTERNAL_uploadthingHookGen = < const uploadProgress = useRef(0); const fileProgress = useRef>(new Map()); - const permittedFileInfo = useEndpointMetadata( - initOpts.url, - endpoint as string, - ); - const routeConfig = permittedFileInfo?.config; - type InferredInput = inferEndpointInput; type FuncInput = undefined extends InferredInput ? [files: File[], input?: undefined] @@ -132,14 +132,19 @@ export const INTERNAL_uploadthingHookGen = < } }); + const routeConfig = useRouteConfig(initOpts.url, endpoint as string); + return { startUpload, isUploading, + routeConfig, + /** * @deprecated Use `routeConfig` instead */ - permittedFileInfo, - routeConfig, + permittedFileInfo: routeConfig + ? { slug: endpoint, config: routeConfig } + : undefined, } as const; }; From ef7075ddeac930b8f40901699582283a238e4f76 Mon Sep 17 00:00:00 2001 From: juliusmarminge Date: Sun, 12 May 2024 21:44:04 +0200 Subject: [PATCH 5/8] return --- packages/uploadthing/src/client.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/uploadthing/src/client.ts b/packages/uploadthing/src/client.ts index 71f1d40d16..93c6af91e9 100644 --- a/packages/uploadthing/src/client.ts +++ b/packages/uploadthing/src/client.ts @@ -62,7 +62,7 @@ export { export const isValidFileType = ( file: File, routeConfig: ExpandedRouteConfig, -) => { +): boolean => { return Effect.runSync( Effect.flatMap( getTypeFromFileName(file.name, objectKeys(routeConfig)), @@ -78,7 +78,7 @@ export const isValidFileType = ( export const isValidFileSize = ( file: File, routeConfig: ExpandedRouteConfig, -) => { +): boolean => { return Effect.runSync( getTypeFromFileName(file.name, objectKeys(routeConfig)).pipe( Effect.andThen((type) => fileSizeToBytes(routeConfig[type]!.maxFileSize)), From 0c235659a1dcee051bc597104ca6e2c6a6636481 Mon Sep 17 00:00:00 2001 From: juliusmarminge Date: Sun, 12 May 2024 21:45:53 +0200 Subject: [PATCH 6/8] refactor efect code --- packages/uploadthing/src/client.ts | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/packages/uploadthing/src/client.ts b/packages/uploadthing/src/client.ts index 93c6af91e9..4777a340b3 100644 --- a/packages/uploadthing/src/client.ts +++ b/packages/uploadthing/src/client.ts @@ -62,14 +62,13 @@ export { export const isValidFileType = ( file: File, routeConfig: ExpandedRouteConfig, -): boolean => { - return Effect.runSync( - Effect.flatMap( - getTypeFromFileName(file.name, objectKeys(routeConfig)), - (type) => Effect.succeed(file.type.includes(type)), - ).pipe(Effect.catchAll(() => Effect.succeed(false))), +): boolean => + Effect.runSync( + getTypeFromFileName(file.name, objectKeys(routeConfig)).pipe( + Effect.flatMap((type) => Effect.succeed(file.type.includes(type))), + Effect.catchAll(() => Effect.succeed(false)), + ), ); -}; /** * Validate that a file is of a valid size given a route config @@ -78,15 +77,14 @@ export const isValidFileType = ( export const isValidFileSize = ( file: File, routeConfig: ExpandedRouteConfig, -): boolean => { - return Effect.runSync( +): boolean => + Effect.runSync( getTypeFromFileName(file.name, objectKeys(routeConfig)).pipe( - Effect.andThen((type) => fileSizeToBytes(routeConfig[type]!.maxFileSize)), - Effect.andThen((maxFileSize) => Effect.succeed(file.size <= maxFileSize)), + Effect.flatMap((type) => fileSizeToBytes(routeConfig[type]!.maxFileSize)), + Effect.flatMap((maxFileSize) => Effect.succeed(file.size <= maxFileSize)), Effect.catchAll(() => Effect.succeed(false)), ), ); -}; const uploadFilesInternal = < TRouter extends FileRouter, From e556991923335e4e56d0869fad3ea90d532a3057 Mon Sep 17 00:00:00 2001 From: juliusmarminge Date: Sun, 12 May 2024 22:12:26 +0200 Subject: [PATCH 7/8] refactor UploadButton --- examples/minimal-appdir/src/app/page.tsx | 2 +- packages/react/src/components/button.tsx | 179 +++++++++++------------ packages/react/src/utils/usePaste.ts | 15 ++ 3 files changed, 103 insertions(+), 93 deletions(-) create mode 100644 packages/react/src/utils/usePaste.ts diff --git a/examples/minimal-appdir/src/app/page.tsx b/examples/minimal-appdir/src/app/page.tsx index 68a4c4086c..bf64b7a1cd 100644 --- a/examples/minimal-appdir/src/app/page.tsx +++ b/examples/minimal-appdir/src/app/page.tsx @@ -30,7 +30,7 @@ export default function Home() { onUploadBegin={() => { console.log("upload begin"); }} - config={{ appendOnPaste: true }} + config={{ appendOnPaste: true, mode: "manual" }} /> + * * endpoint="someEndpoint" * onUploadComplete={(res) => console.log(res)} * onUploadError={(err) => console.log(err)} @@ -87,17 +96,8 @@ export function UploadButton< TRouter, TEndpoint, TSkipPolling - > & { - // props not exposed on public type - // Allow to set internal state for testing - __internal_state?: "readying" | "ready" | "uploading"; - // Allow to set upload progress for testing - __internal_upload_progress?: number; - // Allow to set ready explicitly and independently of internal state - __internal_ready?: boolean; - // Allow to disable the button - __internal_button_disabled?: boolean; - }; + > & + UploadThingInternalProps; const { mode = "auto", appendOnPaste = false } = $props.config ?? {}; @@ -107,16 +107,12 @@ export function UploadButton< const fileInputRef = useRef(null); const labelRef = useRef(null); - const [uploadProgressState, setUploadProgress] = useState( + const [uploadProgress, setUploadProgress] = useState( $props.__internal_upload_progress ?? 0, ); const [files, setFiles] = useState([]); - const [isManualTriggerDisplayed, setIsManualTriggerDisplayed] = - useState(false); - const uploadProgress = - $props.__internal_upload_progress ?? uploadProgressState; - const { startUpload, isUploading, permittedFileInfo } = useUploadThing( + const { startUpload, isUploading, routeConfig } = useUploadThing( $props.endpoint, { headers: $props.headers, @@ -125,7 +121,6 @@ export function UploadButton< if (fileInputRef.current) { fileInputRef.current.value = ""; } - setIsManualTriggerDisplayed(false); setFiles([]); $props.onClientUploadComplete?.(res); setUploadProgress(0); @@ -140,95 +135,96 @@ export function UploadButton< }, ); - const { fileTypes, multiple } = generatePermittedFileTypes( - permittedFileInfo?.config, - ); + const { fileTypes, multiple } = generatePermittedFileTypes(routeConfig); - const ready = - $props.__internal_ready ?? - ($props.__internal_state === "ready" || fileTypes.length > 0); + const fileRouteInput = "input" in $props ? $props.input : undefined; + const inputProps = useMemo( + () => ({ + type: "file", + ref: fileInputRef, + multiple, + accept: generateMimeTypes(fileTypes).join(", "), + onChange: (e: React.ChangeEvent) => { + if (!e.target.files) return; + const selectedFiles = Array.from(e.target.files); - useEffect(() => { - const handlePaste = (event: ClipboardEvent) => { - if (!appendOnPaste) return; - if (document.activeElement !== labelRef.current) return; + if (mode === "manual") { + setFiles(selectedFiles); + return; + } - const pastedFiles = getFilesFromClipboardEvent(event); - if (!pastedFiles) return; + void startUpload(selectedFiles, fileRouteInput); + }, + disabled: fileTypes.length === 0, + tabIndex: fileTypes.length === 0 ? -1 : 0, + }), + [fileRouteInput, fileTypes, mode, multiple, startUpload], + ); - setFiles((prev) => [...prev, ...pastedFiles]); + if ($props.__internal_button_disabled) inputProps.disabled = true; - if (mode === "auto") { - const input = "input" in $props ? $props.input : undefined; - void startUpload(files, input); - } - }; - - window.addEventListener("paste", handlePaste); - return () => { - window.removeEventListener("paste", handlePaste); - }; - }, [startUpload, appendOnPaste, $props, files, mode, fileTypes]); - - const getUploadButtonText = (fileTypes: string[]) => { - if (isManualTriggerDisplayed) - return `Upload ${files.length} file${files.length === 1 ? "" : "s"}`; - if (fileTypes.length === 0) return "Loading..."; - return `Choose File${multiple ? `(s)` : ``}`; - }; + const state = (() => { + if ($props.__internal_state) return $props.__internal_state; + if (inputProps.disabled) return "readying"; + if (!inputProps.disabled && !isUploading) return "ready"; + return "uploading"; + })(); - const getUploadButtonContents = (fileTypes: string[]) => { - if (state !== "uploading") { - return getUploadButtonText(fileTypes); - } - if (uploadProgress === 100) { - return ; - } - return {uploadProgress}%; - }; + usePaste((event) => { + if (!appendOnPaste) return; + if (document.activeElement !== fileInputRef.current) return; - const getInputProps = () => ({ - type: "file", - ref: fileInputRef, - multiple, - accept: generateMimeTypes(fileTypes).join(", "), - onChange: (e: React.ChangeEvent) => { - if (!e.target.files) return; - const selectedFiles = Array.from(e.target.files); - - if (mode === "manual") { - setFiles(selectedFiles); - setIsManualTriggerDisplayed(true); - return; - } + const pastedFiles = getFilesFromClipboardEvent(event); + if (!pastedFiles) return; + + let filesToUpload = pastedFiles; + setFiles((prev) => { + filesToUpload = [...prev, ...pastedFiles]; + return filesToUpload; + }); + if (mode === "auto") { const input = "input" in $props ? $props.input : undefined; - void startUpload(selectedFiles, input); - }, - disabled: $props.__internal_button_disabled ?? !ready, - ...(!($props.__internal_button_disabled ?? !ready) ? { tabIndex: 0 } : {}), + void startUpload(files, input); + } }); const styleFieldArg = { - ready: ready, - isUploading: $props.__internal_state === "uploading" || isUploading, + ready: state === "ready", + isUploading: state === "uploading", uploadProgress, fileTypes, } as ButtonStyleFieldCallbackArgs; - const state = (() => { - if ($props.__internal_state) return $props.__internal_state; - if (!ready) return "readying"; - if (ready && !isUploading) return "ready"; + const renderButton = () => { + const customContent = contentFieldToContent( + $props.content?.button, + styleFieldArg, + ); + if (customContent) return customContent; - return "uploading"; - })(); + if (state === "readying") { + return "Loading..."; + } + + if (state !== "uploading") { + if (mode === "manual" && files.length > 0) { + return `Upload ${files.length} file${files.length === 1 ? "" : "s"}`; + } + return `Choose File${inputProps.multiple ? `(s)` : ``}`; + } + + if (uploadProgress === 100) { + return ; + } + + return {uploadProgress}%; + }; const renderClearButton = () => (
); @@ -289,7 +285,7 @@ export function UploadButton< data-ut-element="button" ref={labelRef} onClick={(e) => { - if (isManualTriggerDisplayed) { + if (mode === "manual" && files.length > 0) { e.preventDefault(); e.stopPropagation(); const input = "input" in $props ? $props.input : undefined; @@ -297,9 +293,8 @@ export function UploadButton< } }} > - - {contentFieldToContent($props.content?.button, styleFieldArg) ?? - getUploadButtonContents(fileTypes)} + + {renderButton()} {mode === "manual" && files.length > 0 ? renderClearButton() diff --git a/packages/react/src/utils/usePaste.ts b/packages/react/src/utils/usePaste.ts new file mode 100644 index 0000000000..5d728fe0c7 --- /dev/null +++ b/packages/react/src/utils/usePaste.ts @@ -0,0 +1,15 @@ +import { useEffect } from "react"; + +import { useEvent } from "./useEvent"; + +type PasteCallback = (event: ClipboardEvent) => void; +export const usePaste = (callback: PasteCallback) => { + const stableCallback = useEvent(callback); + + useEffect(() => { + window.addEventListener("paste", stableCallback); + return () => { + window.removeEventListener("paste", stableCallback); + }; + }, [stableCallback]); +}; From 4e30034bb48cac086d55def5a4f53a26fc7dcb26 Mon Sep 17 00:00:00 2001 From: juliusmarminge Date: Sun, 12 May 2024 22:17:11 +0200 Subject: [PATCH 8/8] fix docs --- docs/src/pages/api/uploadthing.ts | 16 ++++++++++++++++ docs/src/pages/theming.mdx | 18 +++++++++++++++++- packages/react/src/components/button.tsx | 2 +- 3 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 docs/src/pages/api/uploadthing.ts diff --git a/docs/src/pages/api/uploadthing.ts b/docs/src/pages/api/uploadthing.ts new file mode 100644 index 0000000000..850c58564c --- /dev/null +++ b/docs/src/pages/api/uploadthing.ts @@ -0,0 +1,16 @@ +import { createRouteHandler, createUploadthing } from "uploadthing/next-legacy"; + +const f = createUploadthing(); +const router = { + mockRoute: f(["image"]) + .middleware(() => { + throw new Error("This is just a mock route, you cant use it"); + return {}; + }) + .onUploadComplete(() => {}), +}; + +export default createRouteHandler({ + router, + config: { uploadthingSecret: "sk_foo" }, +}); diff --git a/docs/src/pages/theming.mdx b/docs/src/pages/theming.mdx index ea24dd578c..5a7545db8d 100644 --- a/docs/src/pages/theming.mdx +++ b/docs/src/pages/theming.mdx @@ -242,6 +242,7 @@ component match your design in the exact way you want. ``` Upload stuff
; @@ -710,6 +724,7 @@ type UploadDropzoneProps = { ``` Upload stuff; diff --git a/packages/react/src/components/button.tsx b/packages/react/src/components/button.tsx index 2b08250199..6bb2227934 100644 --- a/packages/react/src/components/button.tsx +++ b/packages/react/src/components/button.tsx @@ -190,7 +190,7 @@ export function UploadButton< }); const styleFieldArg = { - ready: state === "ready", + ready: state !== "readying", isUploading: state === "uploading", uploadProgress, fileTypes,