diff --git a/.env b/.env index 2b2500e91..3a88a88fd 100644 --- a/.env +++ b/.env @@ -15,6 +15,11 @@ BASIC_AUTH_PASSWORD="encrypted:BPsyMs9LTwjtb9DvNjANPL9e0Mpv+/GRdcXpQi1H3DshaUEdt DATABASE_URL="postgresql://postgres:postgres@localhost:9876/app" GOOGLE_GENERATIVE_AI_API_KEY="encrypted:BOpnkCvtUHsElQPq/bd0nWJfao3ZbDtU8/ryBS8iW0Bz7GiTjIqQ+4JZJNCwE2PhIa/G3NfKpAOYLhHcbK9zgQCU60JH8SurgdKMyfYJOHtNqg7MCCnE/BD2zd19hE+iYpl0q5N9S/f/wGYyey/zIxA6K5sQ0TjCN+SxQCHNXR4Pj5j05jrUlA==" +DISCORD_CLIENT_ID="encrypted:BExSni527Nj3Cjb9rOhdBvsI7DWr9rDo7/s2PTFzifHQJcWyb9LbpVjYx+9+MHlqMCVRecrlJFyuM3mkEU07xXITLLHUtzNQ59xGY0KUwqYvpj1dKnpmfDzmziE7TWEP7FUaMnKB3RK9OEkoVNNFdcQLJag=" +DISCORD_CLIENT_SECRET="encrypted:BPaZhLJ6I2lehHdwhprvcWZq1HndW+DgENJf1eFAnoJhzkgV3I5TPPNSJgtIOqVB712a8URmjbFjj7VOk9CEQ4K5vu/TpxCTWuPAewkpjVqhYAW+QO2kNwEZODyyIxcI/2RWDKYbhER3cTFilZ59PGuWs6ZhNgOEWe8BToqOuzcf" +GITHUB_CLIENT_ID="encrypted:BFoVE4xZMaDxlouzJ0obBmsqmWMRWOmELLDqsn0JyZewUEdhJfC26rWqmfKvpDHgehV5o9DyWKhToGeLwmM2iShXB8rR8ejunp49xQrNt8fuhug/SSFdEmmodPOvWCKRev56sTQOEl65K5h7jj06PMt0rmvf" +GITHUB_CLIENT_SECRET="encrypted:BMRyRK+HvykXTdvIG3hP1NYzgolurm76pDxz3Z//ckbItxorOy1TPX/N11ha6KiU08P+mBrCHbjNUokJQtPIl+5cFwtnudIuPAA+Vdj6pAyIlrUt+nIA5CnkrAH9VzaG9JfBhzS56dfMkNVEnVSj+Bf8+oldSQuQ1VGfRW1yfbzDnzsK2NlGWpY=" + CLOUDFLARE_API_TOKEN="encrypted:BCdyokIzh8j7Q2028lZ8ekpL0XQUEuYRDU907vzPeAb0Dfir9APnXX1f9ZszJHDtxBFX1tB2bbB97EPHGx53oHVDUuNHZ5voxPEoUd4j6TTeTYFiOQppqEpUzMgh8LdUWFdDJE48aF3D8YbC41NJTmOZ8/Yr14KcniRqCtdp6IApvYSlOk3w2f8=" CLOUDFLARE_DEFAULT_ACCOUNT_ID="encrypted:BJ6dZE7SYXyvaGfSgB8gjHkNpogMZDj5VSTXafLLsG57urj6OOh3GFde7YmWYIyMzjMaTBARTE+WzPL/8LrhxEpEx8+Y+J16SV71ZRXu2MhWDpo6Buzt+Ycn6iV4KytPHfQ221dUTHaVnqxLgYjCF3az20geiuyX2ZJjOTFSbpsT" CLOUDFLARE_ZONE_ID="encrypted:BEuw3Ja75yhNplJ/BYWOgQdcjvVRnvJri/k8XSOdAohsZPiR/cchy1vd/1SvUXvDmqqsgBdUyEFsJ2OhRECAIILa70TvgqB8M/Yw/Lnuqr6zr2bUkGrFlYIR95/MYNbeMU+ML5fUYuAWcWq7zsqeyXhwRt6J03buSf4qLVrVJjLR" diff --git a/.vscode/settings.json b/.vscode/settings.json index 3b7270ec7..351147643 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,6 +4,8 @@ "editor.quickSuggestions": { "strings": "on" }, + "typescript.enablePromptUseWorkspaceTsdk": true, + "typescript.tsdk": "node_modules/typescript/lib", "[javascript]": { "editor.defaultFormatter": "biomejs.biome" }, @@ -50,6 +52,7 @@ "arktype", "Creds", "dotenvx", + "Loro", "millis", "oklch", "orpc", diff --git a/apps/www/package.json b/apps/www/package.json index a7b31dd94..fea8d06e9 100644 --- a/apps/www/package.json +++ b/apps/www/package.json @@ -19,8 +19,11 @@ "with-env-prod": "dotenvx run -f ../../.env.production -- " }, "dependencies": { + "@ai-sdk/react": "^2.0.27", "@rectangular-labs/api": "workspace:*", "@rectangular-labs/auth": "workspace:*", + "@rectangular-labs/editor": "workspace:*", + "@rectangular-labs/result": "workspace:*", "@rectangular-labs/ui": "workspace:*", "@simplewebauthn/browser": "^13.1.2", "@t3-oss/env-core": "^0.13.8", @@ -47,6 +50,8 @@ "typescript": "^5.9.2", "vite": "^7.1.3", "vite-plugin-mkcert": "^1.17.8", + "vite-plugin-top-level-await": "^1.5.0", + "vite-plugin-wasm": "^3.4.1", "vite-tsconfig-paths": "^5.1.4", "vitest": "^3.2.4", "web-vitals": "^5.1.0" diff --git a/apps/www/src/routeTree.gen.ts b/apps/www/src/routeTree.gen.ts index e459690c5..b67b4623e 100644 --- a/apps/www/src/routeTree.gen.ts +++ b/apps/www/src/routeTree.gen.ts @@ -12,6 +12,7 @@ import { createServerRootRoute } from '@tanstack/react-start/server' import { Route as rootRouteImport } from './routes/__root' import { Route as LoginRouteImport } from './routes/login' +import { Route as EditorRouteImport } from './routes/editor' import { Route as AuthedRouteRouteImport } from './routes/_authed/route' import { Route as IndexRouteImport } from './routes/index' import { Route as AuthedOrpcRouteImport } from './routes/_authed/orpc' @@ -25,6 +26,11 @@ const LoginRoute = LoginRouteImport.update({ path: '/login', getParentRoute: () => rootRouteImport, } as any) +const EditorRoute = EditorRouteImport.update({ + id: '/editor', + path: '/editor', + getParentRoute: () => rootRouteImport, +} as any) const AuthedRouteRoute = AuthedRouteRouteImport.update({ id: '/_authed', getParentRoute: () => rootRouteImport, @@ -52,11 +58,13 @@ const ApiRpcSplatServerRoute = ApiRpcSplatServerRouteImport.update({ export interface FileRoutesByFullPath { '/': typeof IndexRoute + '/editor': typeof EditorRoute '/login': typeof LoginRoute '/orpc': typeof AuthedOrpcRoute } export interface FileRoutesByTo { '/': typeof IndexRoute + '/editor': typeof EditorRoute '/login': typeof LoginRoute '/orpc': typeof AuthedOrpcRoute } @@ -64,20 +72,22 @@ export interface FileRoutesById { __root__: typeof rootRouteImport '/': typeof IndexRoute '/_authed': typeof AuthedRouteRouteWithChildren + '/editor': typeof EditorRoute '/login': typeof LoginRoute '/_authed/orpc': typeof AuthedOrpcRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath - fullPaths: '/' | '/login' | '/orpc' + fullPaths: '/' | '/editor' | '/login' | '/orpc' fileRoutesByTo: FileRoutesByTo - to: '/' | '/login' | '/orpc' - id: '__root__' | '/' | '/_authed' | '/login' | '/_authed/orpc' + to: '/' | '/editor' | '/login' | '/orpc' + id: '__root__' | '/' | '/_authed' | '/editor' | '/login' | '/_authed/orpc' fileRoutesById: FileRoutesById } export interface RootRouteChildren { IndexRoute: typeof IndexRoute AuthedRouteRoute: typeof AuthedRouteRouteWithChildren + EditorRoute: typeof EditorRoute LoginRoute: typeof LoginRoute } export interface FileServerRoutesByFullPath { @@ -115,6 +125,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LoginRouteImport parentRoute: typeof rootRouteImport } + '/editor': { + id: '/editor' + path: '/editor' + fullPath: '/editor' + preLoaderRoute: typeof EditorRouteImport + parentRoute: typeof rootRouteImport + } '/_authed': { id: '/_authed' path: '' @@ -172,6 +189,7 @@ const AuthedRouteRouteWithChildren = AuthedRouteRoute._addFileChildren( const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, AuthedRouteRoute: AuthedRouteRouteWithChildren, + EditorRoute: EditorRoute, LoginRoute: LoginRoute, } export const routeTree = rootRouteImport diff --git a/apps/www/src/routes/editor.tsx b/apps/www/src/routes/editor.tsx new file mode 100644 index 000000000..b590efd78 --- /dev/null +++ b/apps/www/src/routes/editor.tsx @@ -0,0 +1,10 @@ +import { SimpleEditor } from "@rectangular-labs/editor"; +import { createFileRoute } from "@tanstack/react-router"; + +export const Route = createFileRoute("/editor")({ + component: RouteComponent, +}); + +function RouteComponent() { + return ; +} diff --git a/apps/www/src/routes/index.tsx b/apps/www/src/routes/index.tsx index ca0ad43ef..57cdd7cd6 100644 --- a/apps/www/src/routes/index.tsx +++ b/apps/www/src/routes/index.tsx @@ -1,136 +1,90 @@ -import * as Icons from "@rectangular-labs/ui/components/icon"; +import { ChatMessageArea } from "@rectangular-labs/ui/components/chat/chat-message-area"; +import { FilePreview } from "@rectangular-labs/ui/components/chat/file-preview"; import { ThemeToggle } from "@rectangular-labs/ui/components/theme-provider"; -import { Button } from "@rectangular-labs/ui/components/ui/button"; -import { - Card, - CardContent, - CardDescription, - CardFooter, - CardHeader, - CardTitle, -} from "@rectangular-labs/ui/components/ui/card"; import { createFileRoute } from "@tanstack/react-router"; +import { useRef, useState } from "react"; export const Route = createFileRoute("/")({ - component: App, + component: ChatInterface, }); -function App() { - const data = Route.useLoaderData(); - return ( -
- - -
-

{data}

+function ChatInterface() { + const [attachedFiles, setAttachedFiles] = useState( + undefined, + ); + const fileInputRef = useRef(null); -

- A modern, full-stack development template -

+ const handleFileChange = (event: React.ChangeEvent) => { + const files = event.target.files; + if (!files) { + return; + } + const currentFileCount = attachedFiles?.length ?? 0; + if (currentFileCount + files.length > 5) { + alert("You can attach a maximum of 5 files."); + return; + } + setAttachedFiles((prev) => { + if (prev) { + const dt = new DataTransfer(); + for (const file of prev) { + dt.items.add(file); + } + for (const file of files) { + dt.items.add(file); + } + return dt.files; + } + return files; + }); + }; -
- - - React + TanStack Start - Build modern, type-safe UIs - - -

- A powerful combination for building interactive web applications - with type safety and excellent developer experience. -

-
- - - -
+ const removeFile = (indexToRemove: number) => { + if (!attachedFiles) return; + const dt = new DataTransfer(); + let index = 0; + for (const file of attachedFiles) { + if (index !== indexToRemove) { + dt.items.add(file); + } + index++; + } - - - Monorepo Architecture - Scalable project organization - - -

- Share code between projects, maintain consistency, and scale - your development with a modern monorepo setup. -

-
- - - -
- - - Typesafe APIs with oRPC - - End-to-end typesafe APIs made simple - - - -

- oRPC is a library for building end-to-end typesafe APIs. No code - generation, no schemas, just TypeScript. -

-
- - - -
-
+ const newFileList = dt.files.length > 0 ? dt.files : undefined; + setAttachedFiles(newFileList); + }; -
- + return ( +
+ +
+ +
+
+
- +
+
+ -

- Edit{" "} - src/routes/index.tsx to - customize this page -

+ {attachedFiles && attachedFiles.length > 0 && ( +
+ {Array.from(attachedFiles).map((file, index) => ( + removeFile(index)} + /> + ))} +
+ )}
diff --git a/apps/www/src/style.css b/apps/www/src/style.css index 0eb7dc5cc..e0e7ee3bd 100644 --- a/apps/www/src/style.css +++ b/apps/www/src/style.css @@ -1,2 +1,3 @@ -@import "@rectangular-labs/ui/styles.css"; +@import "@rectangular-labs/ui/style.css"; +@import "@rectangular-labs/editor/style.css"; @source "./**/*.{ts,tsx}"; diff --git a/apps/www/vite.config.ts b/apps/www/vite.config.ts index e100f9aee..8ae8d5aac 100644 --- a/apps/www/vite.config.ts +++ b/apps/www/vite.config.ts @@ -3,6 +3,8 @@ import { tanstackStart } from "@tanstack/react-start/plugin/vite"; import viteReact from "@vitejs/plugin-react"; import { createJiti } from "jiti"; import mkcert from "vite-plugin-mkcert"; +import topLevelAwait from "vite-plugin-top-level-await"; +import wasm from "vite-plugin-wasm"; import viteTsConfigPaths from "vite-tsconfig-paths"; import { defineConfig } from "vitest/config"; import type { serverEnv } from "~/lib/env"; @@ -19,6 +21,8 @@ const config = defineConfig({ }), tailwindcss(), mkcert(), + wasm(), + topLevelAwait(), tanstackStart({ customViteReactPlugin: true, }), diff --git a/package.json b/package.json index c52446d1e..1c7a543b1 100644 --- a/package.json +++ b/package.json @@ -12,13 +12,15 @@ "db:migrate-push": "bun run --filter @rectangular-labs/db migrate-push", "db:push": "bun run --filter @rectangular-labs/db push --force", "db:studio": "bun run --filter @rectangular-labs/db studio", - "dev": "pnpm with-env-local pnpx sst dev", + "dev": "docker compose up -d && pnpm with-env-local turbo run dev", "dev:packages": "docker compose up -d && turbo run dev --filter=\"./packages/*\"", "deploy:local": "pnpm with-env-local sst deploy", + "deploy:personal": "pnpm with-env-preview sst deploy", "deploy:preview": "pnpm with-env-preview sst deploy --stage preview", "deploy:prod": "pnpm with-env-prod sst deploy --stage production", "env:get": "bun x dotenvx get", "env:set": "bun x dotenvx set", + "env:view": "bun x dotenvx decrypt --stdout", "format": "turbo run format --continue", "lint": "turbo run lint --continue", "new:package": "turbo gen package", diff --git a/packages/editor/package.json b/packages/editor/package.json new file mode 100644 index 000000000..dc73ade82 --- /dev/null +++ b/packages/editor/package.json @@ -0,0 +1,56 @@ +{ + "name": "@rectangular-labs/editor", + "version": "0.0.1", + "type": "module", + "private": true, + "sideEffects": false, + "files": [ + "style.css" + ], + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./src/index.ts" + }, + "./style.css": "./src/style.css" + }, + "scripts": { + "clean": "git clean -xdf .turbo node_modules dist .cache", + "format": "bun x @biomejs/biome format . --write", + "lint": "bun x @biomejs/biome lint . --write", + "typecheck": "tsc --noEmit --emitDeclarationOnly false" + }, + "peerDependencies": { + "react": "^19.1.0", + "react-dom": "^19.1.0" + }, + "dependencies": { + "@rectangular-labs/result": "workspace:*", + "@rectangular-labs/ui": "workspace:*", + "@tiptap/extension-highlight": "^2.11.7", + "@tiptap/extension-image": "^2.11.7", + "@tiptap/extension-link": "^2.11.7", + "@tiptap/extension-subscript": "^2.11.7", + "@tiptap/extension-superscript": "^2.11.7", + "@tiptap/extension-task-item": "^2.11.7", + "@tiptap/extension-task-list": "^2.11.7", + "@tiptap/extension-text-align": "^2.11.7", + "@tiptap/extension-typography": "^2.11.7", + "@tiptap/extension-underline": "^2.11.7", + "@tiptap/pm": "^2.11.7", + "@tiptap/react": "^2.11.7", + "@tiptap/starter-kit": "^2.11.7", + "loro-crdt": "^1.5.4", + "loro-prosemirror": "^0.2.1", + "lucide-react": "^0.542.0" + }, + "devDependencies": { + "@rectangular-labs/typescript": "workspace:*", + "@types/react": "^19.1.8", + "@types/react-dom": "^19.1.6", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "tailwindcss": "^4.1.12", + "typescript": "^5.9.2" + } +} diff --git a/packages/editor/src/components/icons.tsx b/packages/editor/src/components/icons.tsx new file mode 100644 index 000000000..d0ba0bd8c --- /dev/null +++ b/packages/editor/src/components/icons.tsx @@ -0,0 +1,77 @@ +import { + AlignCenter as AlignCenterLucide, + AlignJustify as AlignJustifyLucide, + AlignLeft as AlignLeftLucide, + AlignRight as AlignRightLucide, + ArrowLeft as ArrowLeftLucide, + Ban as BanLucide, + Bold as BoldLucide, + ChevronDown as ChevronDownLucide, + Code2 as Code2Lucide, + CodeSquare as CodeSquareLucide, + CornerDownLeft as CornerDownLeftLucide, + ExternalLink as ExternalLinkLucide, + Heading1 as Heading1Lucide, + Heading2 as Heading2Lucide, + Heading3 as Heading3Lucide, + Heading4 as Heading4Lucide, + Heading5 as Heading5Lucide, + Heading6 as Heading6Lucide, + Heading as HeadingLucide, + Highlighter as HighlighterLucide, + ImagePlus as ImagePlusLucide, + Italic as ItalicLucide, + Link as LinkLucide, + List as ListLucide, + ListOrdered as ListOrderedLucide, + ListTodo as ListTodoLucide, + MoonStar as MoonStarLucide, + Quote as QuoteLucide, + Redo2 as Redo2Lucide, + Strikethrough as StrikethroughLucide, + Subscript as SubscriptLucide, + Sun as SunLucide, + Superscript as SuperscriptLucide, + Trash as TrashLucide, + Underline as UnderlineLucide, + Undo2 as Undo2Lucide, + X as XLucide, +} from "lucide-react"; + +export const AlignCenterIcon = AlignCenterLucide; +export const AlignJustifyIcon = AlignJustifyLucide; +export const AlignLeftIcon = AlignLeftLucide; +export const AlignRightIcon = AlignRightLucide; +export const ArrowLeftIcon = ArrowLeftLucide; +export const BanIcon = BanLucide; +export const BlockQuoteIcon = QuoteLucide; +export const BoldIcon = BoldLucide; +export const ChevronDownIcon = ChevronDownLucide; +export const CloseIcon = XLucide; +export const CodeBlockIcon = CodeSquareLucide; +export const Code2Icon = Code2Lucide; +export const CornerDownLeftIcon = CornerDownLeftLucide; +export const ExternalLinkIcon = ExternalLinkLucide; +export const HeadingFiveIcon = Heading5Lucide; +export const HeadingFourIcon = Heading4Lucide; +export const HeadingIcon = HeadingLucide; +export const HeadingOneIcon = Heading1Lucide; +export const HeadingSixIcon = Heading6Lucide; +export const HeadingThreeIcon = Heading3Lucide; +export const HeadingTwoIcon = Heading2Lucide; +export const HighlighterIcon = HighlighterLucide; +export const ImagePlusIcon = ImagePlusLucide; +export const ItalicIcon = ItalicLucide; +export const LinkIcon = LinkLucide; +export const ListIcon = ListLucide; +export const ListOrderedIcon = ListOrderedLucide; +export const ListTodoIcon = ListTodoLucide; +export const MoonStarIcon = MoonStarLucide; +export const RedoIcon = Redo2Lucide; +export const StrikeIcon = StrikethroughLucide; +export const SubscriptIcon = SubscriptLucide; +export const SunIcon = SunLucide; +export const SuperscriptIcon = SuperscriptLucide; +export const TrashIcon = TrashLucide; +export const UnderlineIcon = UnderlineLucide; +export const UndoIcon = Undo2Lucide; diff --git a/packages/editor/src/components/tiptap-extension/link-extension.ts b/packages/editor/src/components/tiptap-extension/link-extension.ts new file mode 100644 index 000000000..f99a6add3 --- /dev/null +++ b/packages/editor/src/components/tiptap-extension/link-extension.ts @@ -0,0 +1,67 @@ +import TiptapLink from "@tiptap/extension-link"; +import { Plugin, TextSelection } from "@tiptap/pm/state"; +import type { EditorView } from "@tiptap/pm/view"; +import { getMarkRange } from "@tiptap/react"; + +export const Link = TiptapLink.extend({ + inclusive: false, + + parseHTML() { + return [ + { + tag: 'a[href]:not([data-type="button"]):not([href *= "javascript:" i])', + }, + ]; + }, + + addProseMirrorPlugins() { + const { editor } = this; + + return [ + ...(this.parent?.() || []), + new Plugin({ + props: { + handleKeyDown: (_: EditorView, event: KeyboardEvent) => { + const { selection } = editor.state; + + if (event.key === "Escape" && selection.empty !== true) { + editor.commands.focus(selection.to, { scrollIntoView: false }); + } + + return false; + }, + handleClick(view, pos) { + const { schema, doc, tr } = view.state; + let range: ReturnType | undefined; + + if (schema.marks.link) { + range = getMarkRange(doc.resolve(pos), schema.marks.link); + } + + if (!range) { + return; + } + + const { from, to } = range; + const start = Math.min(from, to); + const end = Math.max(from, to); + + if (pos < start || pos > end) { + return; + } + + const $start = doc.resolve(start); + const $end = doc.resolve(end); + const transaction = tr.setSelection( + new TextSelection($start, $end), + ); + + view.dispatch(transaction); + }, + }, + }), + ]; + }, +}); + +export default Link; diff --git a/packages/editor/src/components/tiptap-extension/loro-extension.ts b/packages/editor/src/components/tiptap-extension/loro-extension.ts new file mode 100644 index 000000000..d170cab39 --- /dev/null +++ b/packages/editor/src/components/tiptap-extension/loro-extension.ts @@ -0,0 +1,40 @@ +import { keymap } from "@tiptap/pm/keymap"; +import { Extension } from "@tiptap/react"; +import { LoroDoc, type LoroMap } from "loro-crdt"; + +import { + CursorAwareness, + LoroCursorPlugin, + LoroSyncPlugin, + LoroUndoPlugin, + redo, + undo, +} from "loro-prosemirror"; + +const doc = new LoroDoc<{ + doc: LoroMap; + data: LoroMap>; +}>(); +doc.subscribeLocalUpdates((update) => { + console.log("update", update); +}); +const awareness = new CursorAwareness(doc.peerIdStr); + +export const LoroCRDT = Extension.create({ + name: "loro-crdt", + addProseMirrorPlugins() { + // Return the necessary Loro plugins for Tiptap integration + return [ + // Specify the containerId to sync with the 'doc' map in LoroDoc + LoroSyncPlugin({ doc }), + LoroUndoPlugin({ doc }), // Provides collaborative undo/redo functionality + keymap({ + // Maps keyboard shortcuts to Loro undo/redo actions + "Mod-z": undo, + "Mod-y": redo, + "Mod-Shift-z": redo, + }), + LoroCursorPlugin(awareness, {}), // Manages cursor awareness among collaborators + ]; + }, +}); diff --git a/packages/editor/src/components/tiptap-extension/selection-extension.ts b/packages/editor/src/components/tiptap-extension/selection-extension.ts new file mode 100644 index 000000000..56214ad98 --- /dev/null +++ b/packages/editor/src/components/tiptap-extension/selection-extension.ts @@ -0,0 +1,40 @@ +import { Extension, isNodeSelection } from "@tiptap/react"; +import { Plugin, PluginKey } from "@tiptap/pm/state"; +import { Decoration, DecorationSet } from "@tiptap/pm/view"; + +export const Selection = Extension.create({ + name: "selection", + + addProseMirrorPlugins() { + const { editor } = this; + + return [ + new Plugin({ + key: new PluginKey("selection"), + props: { + decorations(state) { + if (state.selection.empty) { + return null; + } + + if (editor.isFocused === true || !editor.isEditable) { + return null; + } + + if (isNodeSelection(state.selection)) { + return null; + } + + return DecorationSet.create(state.doc, [ + Decoration.inline(state.selection.from, state.selection.to, { + class: "selection", + }), + ]); + }, + }, + }), + ]; + }, +}); + +export default Selection; diff --git a/packages/editor/src/components/tiptap-extension/trailing-node-extension.ts b/packages/editor/src/components/tiptap-extension/trailing-node-extension.ts new file mode 100644 index 000000000..0d76ace3d --- /dev/null +++ b/packages/editor/src/components/tiptap-extension/trailing-node-extension.ts @@ -0,0 +1,82 @@ +import { Extension } from "@tiptap/react"; +import { Plugin, PluginKey } from "@tiptap/pm/state"; +import type { Node, NodeType } from "@tiptap/pm/model"; + +function nodeEqualsType({ + types, + node, +}: { + types: NodeType | NodeType[]; + node: Node | null; +}) { + if (!node) return false; + + if (Array.isArray(types)) { + return types.includes(node.type); + } + + return node.type === types; +} + +export interface TrailingNodeOptions { + node: string; + notAfter: string[]; +} + +export const TrailingNode = Extension.create({ + name: "trailingNode", + + addOptions() { + return { + node: "paragraph", + notAfter: ["paragraph"], + }; + }, + + addProseMirrorPlugins() { + const plugin = new PluginKey(this.name); + const disabledNodes = Object.entries(this.editor.schema.nodes) + .map(([, value]) => value) + .filter((node) => this.options.notAfter.includes(node.name)); + + return [ + new Plugin({ + key: plugin, + appendTransaction: (_, __, state) => { + const { doc, tr, schema } = state; + const shouldInsertNodeAtEnd = plugin.getState(state); + const endPosition = doc.content.size; + const type = schema.nodes[this.options.node]; + + if (!shouldInsertNodeAtEnd) { + return null; + } + + if (type) { + return tr.insert(endPosition, type.create()); + } + + return null; + }, + state: { + init: (_, state) => { + const lastNode = state.tr.doc.lastChild; + + return !nodeEqualsType({ node: lastNode, types: disabledNodes }); + }, + apply: (tr, value) => { + if (!tr.docChanged) { + return value; + } + + const lastNode = tr.doc.lastChild; + + return !nodeEqualsType({ node: lastNode, types: disabledNodes }); + }, + }, + }), + ]; + }, +}); + +export default TrailingNode; diff --git a/packages/editor/src/components/tiptap-node/code-block-node/code-block-node.scss b/packages/editor/src/components/tiptap-node/code-block-node/code-block-node.scss new file mode 100644 index 000000000..d31b312f6 --- /dev/null +++ b/packages/editor/src/components/tiptap-node/code-block-node/code-block-node.scss @@ -0,0 +1,54 @@ +.tiptap.ProseMirror { + --tt-inline-code-bg-color: var(--tt-gray-light-a-100); + --tt-inline-code-text-color: var(--tt-gray-light-a-700); + --tt-inline-code-border-color: var(--tt-gray-light-a-200); + --tt-codeblock-bg: var(--tt-gray-light-a-50); + --tt-codeblock-text: var(--tt-gray-light-a-800); + --tt-codeblock-border: var(--tt-gray-light-a-200); + + .dark & { + --tt-inline-code-bg-color: var(--tt-gray-dark-a-100); + --tt-inline-code-text-color: var(--tt-gray-dark-a-700); + --tt-inline-code-border-color: var(--tt-gray-dark-a-200); + --tt-codeblock-bg: var(--tt-gray-dark-a-50); + --tt-codeblock-text: var(--tt-gray-dark-a-800); + --tt-codeblock-border: var(--tt-gray-dark-a-200); + } +} + +/* ===================== + CODE FORMATTING + ===================== */ +.tiptap.ProseMirror { + // Inline code + code { + background-color: var(--tt-inline-code-bg-color); + color: var(--tt-inline-code-text-color); + border: 1px solid var(--tt-inline-code-border-color); + font-family: "JetBrains Mono NL", monospace; + font-size: 0.875em; + line-height: 1.4; + border-radius: 6px/0.375rem; + padding: 0.1em 0.2em; + } + + // Code blocks + pre { + background-color: var(--tt-codeblock-bg); + color: var(--tt-codeblock-text); + border: 1px solid var(--tt-codeblock-border); + margin-top: 1.5em; + margin-bottom: 1.5em; + padding: 1em; + font-size: 1rem; + border-radius: 6px/0.375rem; + + code { + background-color: transparent; + border: none; + border-radius: 0; + -webkit-text-fill-color: inherit; + color: inherit; + } + } +} diff --git a/packages/editor/src/components/tiptap-node/image-node/image-node.scss b/packages/editor/src/components/tiptap-node/image-node/image-node.scss new file mode 100644 index 000000000..55265bb1a --- /dev/null +++ b/packages/editor/src/components/tiptap-node/image-node/image-node.scss @@ -0,0 +1,32 @@ +.tiptap.ProseMirror { + img { + max-width: 100%; + height: auto; + display: block; + } + + > img:not([data-type="emoji"] img) { + margin: 2rem 0; + outline: 0.125rem solid transparent; + border-radius: var(--tt-radius-xs, 0.25rem); + } + + &.ProseMirror-focused + img:not([data-type="emoji"] img).ProseMirror-selectednode { + outline-color: var(--tt-brand-color-500); + } + + // Thread image handling + .tiptap-thread:has(> img) { + margin: 2rem 0; + + img { + outline: 0.125rem solid transparent; + border-radius: var(--tt-radius-xs, 0.25rem); + } + } + + .tiptap-thread img { + margin: 0; + } +} diff --git a/packages/editor/src/components/tiptap-node/image-upload-node/image-upload-node-extension.ts b/packages/editor/src/components/tiptap-node/image-upload-node/image-upload-node-extension.ts new file mode 100644 index 000000000..f51b4f5c4 --- /dev/null +++ b/packages/editor/src/components/tiptap-node/image-upload-node/image-upload-node-extension.ts @@ -0,0 +1,147 @@ +import { Node, mergeAttributes } from "@tiptap/react"; +import { ReactNodeViewRenderer } from "@tiptap/react"; +import { ImageUploadNode as ImageUploadNodeComponent } from "./image-upload-node"; + +export type UploadFunction = ( + file: File, + onProgress?: (event: { progress: number }) => void, + abortSignal?: AbortSignal, +) => Promise; + +export interface ImageUploadNodeOptions { + /** + * Acceptable file types for upload. + * @default 'image/*' + */ + accept?: string; + /** + * Maximum number of files that can be uploaded. + * @default 1 + */ + limit?: number; + /** + * Maximum file size in bytes (0 for unlimited). + * @default 0 + */ + maxSize?: number; + /** + * Function to handle the upload process. + */ + upload?: UploadFunction; + /** + * Callback for upload errors. + */ + onError?: (error: Error) => void; + /** + * Callback for successful uploads. + */ + onSuccess?: (url: string) => void; +} + +declare module "@tiptap/react" { + interface Commands { + imageUpload: { + setImageUploadNode: (options?: ImageUploadNodeOptions) => ReturnType; + }; + } +} + +/** + * A TipTap node extension that creates an image upload component. + * @see registry/tiptap-node/image-upload-node/image-upload-node + */ +export const ImageUploadNode = Node.create({ + name: "imageUpload", + + group: "block", + + draggable: true, + + selectable: true, + + atom: true, + + addOptions() { + return { + accept: "image/*", + limit: 1, + maxSize: 0, + upload: undefined, + onError: undefined, + onSuccess: undefined, + }; + }, + + addAttributes() { + return { + accept: { + default: this.options.accept, + }, + limit: { + default: this.options.limit, + }, + maxSize: { + default: this.options.maxSize, + }, + }; + }, + + parseHTML() { + return [{ tag: 'div[data-type="image-upload"]' }]; + }, + + renderHTML({ HTMLAttributes }) { + return [ + "div", + mergeAttributes({ "data-type": "image-upload" }, HTMLAttributes), + ]; + }, + + addNodeView() { + return ReactNodeViewRenderer(ImageUploadNodeComponent); + }, + + addCommands() { + return { + setImageUploadNode: + (options = {}) => + ({ commands }) => { + return commands.insertContent({ + type: this.name, + attrs: options, + }); + }, + }; + }, + + /** + * Adds Enter key handler to trigger the upload component when it's selected. + */ + addKeyboardShortcuts() { + return { + Enter: ({ editor }) => { + const { selection } = editor.state; + const { nodeAfter } = selection.$from; + + if ( + nodeAfter && + nodeAfter.type.name === "imageUpload" && + editor.isActive("imageUpload") + ) { + const nodeEl = editor.view.nodeDOM(selection.$from.pos); + if (nodeEl && nodeEl instanceof HTMLElement) { + // Since NodeViewWrapper is wrapped with a div, we need to click the first child + const firstChild = nodeEl.firstChild; + if (firstChild && firstChild instanceof HTMLElement) { + firstChild.click(); + return true; + } + } + } + return false; + }, + }; + }, +}); + +export default ImageUploadNode; diff --git a/packages/editor/src/components/tiptap-node/image-upload-node/image-upload-node.scss b/packages/editor/src/components/tiptap-node/image-upload-node/image-upload-node.scss new file mode 100644 index 000000000..44fd56421 --- /dev/null +++ b/packages/editor/src/components/tiptap-node/image-upload-node/image-upload-node.scss @@ -0,0 +1,213 @@ +:root { + --tt-button-default-icon-color: var(--tt-gray-light-a-600); + + --tiptap-image-upload-active: var(--tt-brand-color-500); + --tiptap-image-upload-progress-bg: var(--tt-brand-color-50); + --tiptap-image-upload-icon-bg: var(--tt-brand-color-500); + + --tiptap-image-upload-text-color: var(--tt-gray-light-a-700); + --tiptap-image-upload-subtext-color: var(--tt-gray-light-a-400); + --tiptap-image-upload-border: var(--tt-gray-light-a-300); + --tiptap-image-upload-border-hover: var(--tt-gray-light-a-400); + --tiptap-image-upload-border-active: var(--tt-brand-color-500); + + --tiptap-image-upload-icon-doc-bg: var(--tt-gray-light-a-200); + --tiptap-image-upload-icon-doc-border: var(--tt-gray-light-300); + --tiptap-image-upload-icon-color: var(--white); +} + +.dark { + --tt-button-default-icon-color: var(--tt-gray-dark-a-600); + + --tiptap-image-upload-active: var(--tt-brand-color-400); + --tiptap-image-upload-progress-bg: var(--tt-brand-color-900); + --tiptap-image-upload-icon-bg: var(--tt-brand-color-400); + + --tiptap-image-upload-text-color: var(--tt-gray-dark-a-700); + --tiptap-image-upload-subtext-color: var(--tt-gray-dark-a-400); + --tiptap-image-upload-border: var(--tt-gray-dark-a-300); + --tiptap-image-upload-border-hover: var(--tt-gray-dark-a-400); + --tiptap-image-upload-border-active: var(--tt-brand-color-400); + + --tiptap-image-upload-icon-doc-bg: var(--tt-gray-dark-a-200); + --tiptap-image-upload-icon-doc-border: var(--tt-gray-dark-300); + --tiptap-image-upload-icon-color: var(--black); +} + +.tiptap-image-upload { + margin: 2rem 0; + + input[type="file"] { + display: none; + } + + .tiptap-image-upload-dropzone { + position: relative; + width: 3.125rem; + height: 3.75rem; + display: inline-flex; + align-items: flex-start; + justify-content: center; + -webkit-user-select: none; /* Safari */ + -ms-user-select: none; /* IE 10 and IE 11 */ + user-select: none; + } + + .tiptap-image-upload-icon-container { + position: absolute; + width: 1.75rem; + height: 1.75rem; + bottom: 0; + right: 0; + background-color: var(--tiptap-image-upload-icon-bg); + border-radius: var(--tt-radius-lg, 0.75rem); + display: flex; + align-items: center; + justify-content: center; + } + + .tiptap-image-upload-icon { + width: 0.875rem; + height: 0.875rem; + color: var(--tiptap-image-upload-icon-color); + } + + .tiptap-image-upload-dropzone-rect-primary { + color: var(--tiptap-image-upload-icon-doc-bg); + position: absolute; + } + + .tiptap-image-upload-dropzone-rect-secondary { + position: absolute; + top: 0; + right: 0.25rem; + bottom: 0; + color: var(--tiptap-image-upload-icon-doc-border); + } + + .tiptap-image-upload-text { + color: var(--tiptap-image-upload-text-color); + font-weight: 500; + font-size: 0.875rem; + line-height: normal; + + em { + font-style: normal; + text-decoration: underline; + } + } + + .tiptap-image-upload-subtext { + color: var(--tiptap-image-upload-subtext-color); + font-weight: 600; + line-height: normal; + font-size: 0.75rem; + } + + .tiptap-image-upload-preview { + position: relative; + border-radius: var(--tt-radius-md, 0.5rem); + overflow: hidden; + + .tiptap-image-upload-progress { + position: absolute; + inset: 0; + background-color: var(--tiptap-image-upload-progress-bg); + transition: all 300ms ease-out; + } + + .tiptap-image-upload-preview-content { + position: relative; + border: 1px solid var(--tiptap-image-upload-border); + border-radius: var(--tt-radius-md, 0.5rem); + padding: 1rem; + display: flex; + align-items: center; + justify-content: space-between; + } + + .tiptap-image-upload-file-info { + display: flex; + align-items: center; + gap: 0.75rem; + height: 2rem; + + .tiptap-image-upload-file-icon { + padding: 0.5rem; + background-color: var(--tiptap-image-upload-icon-bg); + border-radius: var(--tt-radius-lg, 0.75rem); + + svg { + width: 0.875rem; + height: 0.875rem; + color: var(--tiptap-image-upload-icon-color); + } + } + } + + .tiptap-image-upload-details { + display: flex; + flex-direction: column; + } + + .tiptap-image-upload-actions { + display: flex; + align-items: center; + + .tiptap-image-upload-progress-text { + font-size: 0.75rem; + color: var(--tiptap-image-upload-border-active); + } + + .tiptap-image-upload-close-btn { + display: flex; + align-items: center; + justify-content: center; + width: 2rem; + height: 2rem; + color: var(--tt-button-default-icon-color); + transition: color 200ms ease; + + svg { + width: 1rem; + height: 1rem; + } + } + } + } + + .tiptap-image-upload-dragger { + padding: 2rem 1.5rem; + border: 1.5px dashed var(--tiptap-image-upload-border); + border-radius: var(--tt-radius-md, 0.5rem); + text-align: center; + cursor: pointer; + position: relative; + overflow: hidden; + + &-active { + border-color: var(--tiptap-image-upload-border-active); + background-color: rgba( + var(--tiptap-image-upload-active-rgb, 0, 0, 255), + 0.05 + ); + } + } + + .tiptap-image-upload-content { + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + gap: 0.25rem; + -webkit-user-select: none; /* Safari */ + -ms-user-select: none; /* IE 10 and IE 11 */ + user-select: none; + } +} + +.tiptap.ProseMirror.ProseMirror-focused { + .ProseMirror-selectednode .tiptap-image-upload-dragger { + border-color: var(--tiptap-image-upload-active); + } +} diff --git a/packages/editor/src/components/tiptap-node/image-upload-node/image-upload-node.tsx b/packages/editor/src/components/tiptap-node/image-upload-node/image-upload-node.tsx new file mode 100644 index 000000000..fcb5ed350 --- /dev/null +++ b/packages/editor/src/components/tiptap-node/image-upload-node/image-upload-node.tsx @@ -0,0 +1,415 @@ +import type { NodeViewProps } from "@tiptap/react"; +import { NodeViewWrapper } from "@tiptap/react"; +import * as React from "react"; +import { CloseIcon } from "../../icons"; +import "./image-upload-node.scss"; + +export interface FileItem { + id: string; + file: File; + progress: number; + status: "uploading" | "success" | "error"; + url?: string; + abortController?: AbortController; +} + +interface UploadOptions { + maxSize: number; + limit: number; + accept: string; + upload: ( + file: File, + onProgress: (event: { progress: number }) => void, + signal: AbortSignal, + ) => Promise; + onSuccess?: (url: string) => void; + onError?: (error: Error) => void; +} + +function useFileUpload(options: UploadOptions) { + const [fileItem, setFileItem] = React.useState(null); + + const uploadFile = async (file: File): Promise => { + if (file.size > options.maxSize) { + const error = new Error( + `File size exceeds maximum allowed (${options.maxSize / 1024 / 1024}MB)`, + ); + options.onError?.(error); + return null; + } + + const abortController = new AbortController(); + + const newFileItem: FileItem = { + id: crypto.randomUUID(), + file, + progress: 0, + status: "uploading", + abortController, + }; + + setFileItem(newFileItem); + + try { + if (!options.upload) { + throw new Error("Upload function is not defined"); + } + + const url = await options.upload( + file, + (event: { progress: number }) => { + setFileItem((prev) => { + if (!prev) return null; + return { + ...prev, + progress: event.progress, + }; + }); + }, + abortController.signal, + ); + + if (!url) throw new Error("Upload failed: No URL returned"); + + if (!abortController.signal.aborted) { + setFileItem((prev) => { + if (!prev) return null; + return { + ...prev, + status: "success", + url, + progress: 100, + }; + }); + options.onSuccess?.(url); + return url; + } + + return null; + } catch (error) { + if (!abortController.signal.aborted) { + setFileItem((prev) => { + if (!prev) return null; + return { + ...prev, + status: "error", + progress: 0, + }; + }); + options.onError?.( + error instanceof Error ? error : new Error("Upload failed"), + ); + } + return null; + } + }; + + const uploadFiles = async (files: File[]): Promise => { + if (!files || files.length === 0) { + options.onError?.(new Error("No files to upload")); + return null; + } + + if (options.limit && files.length > options.limit) { + options.onError?.( + new Error( + `Maximum ${options.limit} file${options.limit === 1 ? "" : "s"} allowed`, + ), + ); + return null; + } + + const file = files[0]; + if (!file) { + options.onError?.(new Error("File is undefined")); + return null; + } + + return await uploadFile(file); + }; + + const clearFileItem = () => { + if (!fileItem) return; + + if (fileItem.abortController) { + fileItem.abortController.abort(); + } + if (fileItem.url) { + URL.revokeObjectURL(fileItem.url); + } + setFileItem(null); + }; + + return { + fileItem, + uploadFiles, + clearFileItem, + }; +} + +const CloudUploadIcon: React.FC = () => ( + + + + +); + +const FileIcon: React.FC = () => ( + + + +); + +const FileCornerIcon: React.FC = () => ( + + + +); + +interface ImageUploadDragAreaProps { + onFile: (files: File[]) => void; + children?: React.ReactNode; +} + +const ImageUploadDragArea: React.FC = ({ + onFile, + children, +}) => { + const [dragover, setDragover] = React.useState(false); + + const onDrop = (e: React.DragEvent) => { + setDragover(false); + e.preventDefault(); + e.stopPropagation(); + + const files = Array.from(e.dataTransfer.files); + onFile(files); + }; + + const onDragover = (e: React.DragEvent) => { + e.preventDefault(); + setDragover(true); + }; + + const onDragleave = (e: React.DragEvent) => { + e.preventDefault(); + setDragover(false); + }; + + return ( +
+ {children} +
+ ); +}; + +interface ImageUploadPreviewProps { + file: File; + progress: number; + status: "uploading" | "success" | "error"; + onRemove: () => void; +} + +const ImageUploadPreview: React.FC = ({ + file, + progress, + status, + onRemove, +}) => { + const formatFileSize = (bytes: number) => { + if (bytes === 0) return "0 Bytes"; + const k = 1024; + const sizes = ["Bytes", "KB", "MB", "GB"]; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return `${Number.parseFloat((bytes / k ** i).toFixed(2))} ${sizes[i]}`; + }; + + return ( +
+ {status === "uploading" && ( +
+ )} + +
+
+
+ +
+
+ {file.name} + + {formatFileSize(file.size)} + +
+
+
+ {status === "uploading" && ( + + {progress}% + + )} + +
+
+
+ ); +}; + +const DropZoneContent: React.FC<{ maxSize: number }> = ({ maxSize }) => ( + <> +
+ + +
+ +
+
+ +
+ + Click to upload or drag and drop + + + Maximum file size {maxSize / 1024 / 1024}MB. + +
+ +); + +export const ImageUploadNode: React.FC = (props) => { + const { accept, limit, maxSize } = props.node.attrs; + const inputRef = React.useRef(null); + const extension = props.extension; + + const uploadOptions: UploadOptions = { + maxSize, + limit, + accept, + upload: extension.options.upload, + onSuccess: extension.options.onSuccess, + onError: extension.options.onError, + }; + + const { fileItem, uploadFiles, clearFileItem } = useFileUpload(uploadOptions); + + const handleChange = (e: React.ChangeEvent) => { + const files = e.target.files; + if (!files || files.length === 0) { + extension.options.onError?.(new Error("No file selected")); + return; + } + handleUpload(Array.from(files)); + }; + + const handleUpload = async (files: File[]) => { + const url = await uploadFiles(files); + + if (url) { + const pos = props.getPos(); + const filename = files[0]?.name.replace(/\.[^/.]+$/, "") || "unknown"; + + props.editor + .chain() + .focus() + .deleteRange({ from: pos, to: pos + 1 }) + .insertContentAt(pos, [ + { + type: "image", + attrs: { src: url, alt: filename, title: filename }, + }, + ]) + .run(); + } + }; + + const handleClick = () => { + if (inputRef.current && !fileItem) { + inputRef.current.value = ""; + inputRef.current.click(); + } + }; + + return ( + + {!fileItem && ( + + + + )} + + {fileItem && ( + + )} + + ) => e.stopPropagation()} + /> + + ); +}; diff --git a/packages/editor/src/components/tiptap-node/image-upload-node/index.tsx b/packages/editor/src/components/tiptap-node/image-upload-node/index.tsx new file mode 100644 index 000000000..93f512da7 --- /dev/null +++ b/packages/editor/src/components/tiptap-node/image-upload-node/index.tsx @@ -0,0 +1 @@ +export * from "./image-upload-node-extension"; diff --git a/packages/editor/src/components/tiptap-node/list-node/list-node.scss b/packages/editor/src/components/tiptap-node/list-node/list-node.scss new file mode 100644 index 000000000..4da72c818 --- /dev/null +++ b/packages/editor/src/components/tiptap-node/list-node/list-node.scss @@ -0,0 +1,159 @@ +.tiptap.ProseMirror { + --tt-checklist-bg-color: var(--tt-gray-light-a-100); + --tt-checklist-bg-active-color: var(--tt-gray-light-a-900); + --tt-checklist-border-color: var(--tt-gray-light-a-200); + --tt-checklist-border-active-color: var(--tt-gray-light-a-900); + --tt-checklist-check-icon-color: var(--white); + --tt-checklist-text-active: var(--tt-gray-light-a-500); + + .dark & { + --tt-checklist-bg-color: var(--tt-gray-dark-a-100); + --tt-checklist-bg-active-color: var(--tt-gray-dark-a-900); + --tt-checklist-border-color: var(--tt-gray-dark-a-200); + --tt-checklist-border-active-color: var(--tt-gray-dark-a-900); + --tt-checklist-check-icon-color: var(--black); + --tt-checklist-text-active: var(--tt-gray-dark-a-500); + } +} + +/* ===================== + LISTS + ===================== */ +.tiptap.ProseMirror { + // Common list styles + ol, + ul { + margin-top: 1.5em; + margin-bottom: 1.5em; + padding-left: 1.5em; + + &:first-child { + margin-top: 0; + } + + &:last-child { + margin-bottom: 0; + } + + ol, + ul { + margin-top: 0; + margin-bottom: 0; + } + } + + li { + p { + margin-top: 0; + } + } + + // Ordered lists + ol { + list-style: decimal; + + ol { + list-style: lower-alpha; + + ol { + list-style: lower-roman; + } + } + } + + // Unordered lists + ul:not([data-type="taskList"]) { + list-style: disc; + + ul { + list-style: circle; + + ul { + list-style: disc; + } + } + } + + // Task lists + ul[data-type="taskList"] { + padding-left: 0.25em; + + li { + display: flex; + flex-direction: row; + align-items: flex-start; + + &:not(:has(> p:first-child)) { + list-style-type: none; + } + + &[data-checked="true"] { + > div > p { + opacity: 0.5; + text-decoration: line-through; + } + + > div > p span { + text-decoration: line-through; + } + } + + label { + position: relative; + padding-top: 4px; + padding-right: 8px; + + input[type="checkbox"] { + position: absolute; + opacity: 0; + width: 0; + height: 0; + } + + span { + display: block; + width: 1em; + height: 1em; + border: 1px solid var(--tt-checklist-border-color); + border-radius: var(--tt-radius-xs, 0.25rem); + position: relative; + cursor: pointer; + background-color: var(--tt-checklist-bg-color); + transition: + background-color 80ms ease-out, + border-color 80ms ease-out; + + &::before { + content: ""; + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + width: 0.75em; + height: 0.75em; + background-color: var(--tt-checklist-check-icon-color); + opacity: 0; + -webkit-mask: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22currentColor%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M21.4142%204.58579C22.1953%205.36683%2022.1953%206.63317%2021.4142%207.41421L10.4142%2018.4142C9.63317%2019.1953%208.36684%2019.1953%207.58579%2018.4142L2.58579%2013.4142C1.80474%2012.6332%201.80474%2011.3668%202.58579%2010.5858C3.36683%209.80474%204.63317%209.80474%205.41421%2010.5858L9%2014.1716L18.5858%204.58579C19.3668%203.80474%2020.6332%203.80474%2021.4142%204.58579Z%22%20fill%3D%22currentColor%22%2F%3E%3C%2Fsvg%3E") + center/contain no-repeat; + mask: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22currentColor%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M21.4142%204.58579C22.1953%205.36683%2022.1953%206.63317%2021.4142%207.41421L10.4142%2018.4142C9.63317%2019.1953%208.36684%2019.1953%207.58579%2018.4142L2.58579%2013.4142C1.80474%2012.6332%201.80474%2011.3668%202.58579%2010.5858C3.36683%209.80474%204.63317%209.80474%205.41421%2010.5858L9%2014.1716L18.5858%204.58579C19.3668%203.80474%2020.6332%203.80474%2021.4142%204.58579Z%22%20fill%3D%22currentColor%22%2F%3E%3C%2Fsvg%3E") + center/contain no-repeat; + } + } + + input[type="checkbox"]:checked + span { + background: var(--tt-checklist-bg-active-color); + border-color: var(--tt-checklist-border-active-color); + + &::before { + opacity: 1; + } + } + } + + div { + flex: 1 1 0%; + min-width: 0; + } + } + } +} diff --git a/packages/editor/src/components/tiptap-node/paragraph-node/paragraph-node.scss b/packages/editor/src/components/tiptap-node/paragraph-node/paragraph-node.scss new file mode 100644 index 000000000..abb5a7e5c --- /dev/null +++ b/packages/editor/src/components/tiptap-node/paragraph-node/paragraph-node.scss @@ -0,0 +1,402 @@ +.tiptap.ProseMirror { + --blockquote-bg-color: var(--tt-gray-light-900); + --link-text-color: var(--tt-brand-color-500); + --separator-color: var(--tt-gray-light-a-200); + --thread-text: var(--tt-gray-light-900); + --placeholder-color: var(--tt-gray-light-a-400); + + // Highlight variables + --tt-highlight-green: #dcfce7; + --tt-highlight-green-contrast: #c7fad8; + --tt-highlight-blue: #e0f2fe; + --tt-highlight-blue-contrast: #ceeafd; + --tt-highlight-red: #ffe4e6; + --tt-highlight-red-contrast: #ffccd0; + --tt-highlight-purple: #f3e8ff; + --tt-highlight-purple-contrast: #e4ccff; + --tt-highlight-yellow: #fef9c3; + --tt-highlight-yellow-contrast: #fbe604; + + // Mathematics variables + --tiptap-mathematics-bg-color: var(--tt-gray-light-a-200); + --tiptap-mathematics-border-color: var(--tt-brand-color-500); + + .dark & { + --blockquote-bg-color: var(--tt-gray-dark-900); + --link-text-color: var(--tt-brand-color-400); + --separator-color: var(--tt-gray-dark-a-200); + --thread-text: var(--tt-gray-dark-900); + --placeholder-color: var(--tt-gray-dark-a-400); + + --tt-highlight-green: #509568; + --tt-highlight-green-contrast: #47855d; + --tt-highlight-blue: #6e92aa; + --tt-highlight-blue-contrast: #5e86a1; + --tt-highlight-red: #743e42; + --tt-highlight-red-contrast: #643539; + --tt-highlight-purple: #583e74; + --tt-highlight-purple-contrast: #4c3564; + --tt-highlight-yellow: #6b6524; + --tt-highlight-yellow-contrast: #58531e; + + --tiptap-mathematics-bg-color: var(--tt-gray-dark-a-200); + --tiptap-mathematics-border-color: var(--tt-brand-color-400); + } +} + +/* ===================== + CORE EDITOR STYLES + ===================== */ +.tiptap.ProseMirror { + white-space: pre-wrap; + outline: none; + caret-color: var(--tt-cursor-color); + + // Paragraph spacing + p:not(:first-child) { + font-size: 1rem; + line-height: 1.6; + font-weight: normal; + margin-top: 20px; + } + + // Selection styles + &:not(.readonly):not(.ProseMirror-hideselection) { + ::selection { + background-color: var(--tt-selection-color); + } + + .selection::selection { + background: transparent; + } + } + + .selection { + display: inline; + background-color: var(--tt-selection-color); + } + + .ProseMirror-hideselection { + caret-color: transparent; + } + + // Placeholder + > p.is-editor-empty::before { + content: attr(data-placeholder); + pointer-events: none; + color: var(--placeholder-color); + float: left; + height: 0; + } + + // Resize cursor + &.resize-cursor { + cursor: ew-resize; + cursor: col-resize; + } +} + +/* ===================== + GAP CURSOR + ===================== */ +.tiptap.ProseMirror { + .ProseMirror-gapcursor { + display: none; + pointer-events: none; + position: absolute; + + &:after { + content: ""; + display: block; + position: absolute; + top: 1em; + width: 1.25em; + border-top: 1px solid black; + animation: ProseMirror-cursor-blink 1.1s steps(2, start) infinite; + } + } + + &.ProseMirror-focused, + &.ProseMirror.ProseMirror-focused { + .ProseMirror-gapcursor { + display: block; + } + } +} + +@keyframes ProseMirror-cursor-blink { + to { + visibility: hidden; + } +} + +/* ===================== + TEXT DECORATION + ===================== */ +.tiptap.ProseMirror { + // Text decoration inheritance for spans + a span { + text-decoration: underline; + } + + s span { + text-decoration: line-through; + } + + u span { + text-decoration: underline; + } +} + +/* ===================== + BLOCKQUOTE + ===================== */ +.tiptap.ProseMirror { + blockquote { + position: relative; + padding-left: 1em; + padding-top: 0.375em; + padding-bottom: 0.375em; + margin: 1.5rem 0; + + p { + margin-top: 0; + } + + &::before, + &.is-empty::before { + position: absolute; + bottom: 0; + left: 0; + top: 0; + height: 100%; + width: 0.25em; + background-color: var(--blockquote-bg-color); + content: ""; + border-radius: 0; + } + } +} + +/* ===================== + COLLABORATION + ===================== */ +.tiptap.ProseMirror { + .collaboration-cursor { + &__caret { + border-right: 1px solid transparent; + border-left: 1px solid transparent; + pointer-events: none; + margin-left: -1px; + margin-right: -1px; + position: relative; + word-break: normal; + } + + &__label { + border-radius: 0.25rem; + border-bottom-left-radius: 0; + font-size: 0.75rem; + font-weight: 600; + left: -1px; + line-height: 1; + padding: 0.125rem 0.375rem; + position: absolute; + top: -1.3em; + user-select: none; + white-space: nowrap; + } + } +} + +/* ===================== + EMOJI + ===================== */ +.tiptap.ProseMirror [data-type="emoji"] img { + display: inline-block; + width: 1.25em; + height: 1.25em; + cursor: text; +} + +/* ===================== + HEADINGS + ===================== */ +.tiptap.ProseMirror { + h1, + h2, + h3, + h4 { + position: relative; + color: inherit; + font-style: inherit; + + &:first-child { + margin-top: 0; + } + } + + h1 { + font-size: 1.5em; + font-weight: 700; + margin-top: 3em; + } + + h2 { + font-size: 1.25em; + font-weight: 700; + margin-top: 2.5em; + } + + h3 { + font-size: 1.125em; + font-weight: 600; + margin-top: 2em; + } + + h4 { + font-size: 1em; + font-weight: 600; + margin-top: 2em; + } +} + +/* ===================== + HORIZONTAL RULE + ===================== */ +.tiptap.ProseMirror { + hr { + margin-top: 3em; + margin-bottom: 3em; + border: none; + height: 1px; + background-color: var(--separator-color); + } + + &.ProseMirror-focused { + hr.ProseMirror-selectednode { + border-radius: 9999px; + outline: 3px solid var(--tt-brand-color-500); + outline-offset: 2px; + } + } +} + +/* ===================== + LINKS + ===================== */ +.tiptap.ProseMirror { + a { + color: var(--link-text-color); + text-decoration: underline; + } +} + +/* ===================== + MENTION + ===================== */ +.tiptap.ProseMirror { + [data-type="mention"] { + display: inline-block; + color: var(--tt-brand-color-500); + } +} + +/* ===================== + THREADS + ===================== */ +.tiptap.ProseMirror { + // Base styles for inline threads + .tiptap-thread.tiptap-thread--unresolved.tiptap-thread--inline { + transition: + color 0.2s ease-in-out, + background-color 0.2s ease-in-out; + color: var(--thread-text); + border-bottom: 2px dashed var(--tt-color-yellow-base); + font-weight: 600; + + &.tiptap-thread--selected, + &.tiptap-thread--hovered { + background-color: var(--tt-color-yellow-inc-2); + border-bottom-color: transparent; + } + } + + // Block thread styles with images + .tiptap-thread.tiptap-thread--unresolved.tiptap-thread--block { + &:has(img) { + outline: 0.125rem solid var(--tt-color-yellow-base); + border-radius: var(--tt-radius-xs, 0.25rem); + overflow: hidden; + width: fit-content; + + &.tiptap-thread--selected { + outline-width: 0.25rem; + outline-color: var(--tt-color-yellow-base); + } + + &.tiptap-thread--hovered { + outline-width: 0.25rem; + } + } + + // Block thread styles without images + &:not(:has(img)) { + border-radius: 0.25rem; + border-bottom: 0.125rem dashed var(--tt-color-yellow-base); + padding-bottom: 0.5rem; + outline: 0.25rem solid transparent; + + &.tiptap-thread--hovered, + &.tiptap-thread--selected { + background-color: var(--tt-color-yellow-base); + outline-color: var(--tt-color-yellow-base); + } + } + } + + // Resolved thread styles + .tiptap-thread.tiptap-thread--resolved.tiptap-thread--inline.tiptap-thread--selected { + background-color: var(--tt-color-yellow-base); + border-color: transparent; + opacity: 0.5; + } + + // React renderer specific styles + .tiptap-thread.tiptap-thread--block:has(.react-renderer) { + margin-top: 3rem; + margin-bottom: 3rem; + } +} + +/* ===================== + Mathematics + ===================== */ +.tiptap.ProseMirror { + .Tiptap-mathematics-editor { + padding: 0 0.25rem; + margin: 0 0.25rem; + border: 1px solid var(--tiptap-mathematics-border-color); + font-family: monospace; + font-size: 0.875rem; + } + + .Tiptap-mathematics-render { + padding: 0 0.25rem; + + &--editable { + cursor: pointer; + transition: background 0.2s; + + &:hover { + background: var(--tiptap-mathematics-bg-color); + } + } + } + + .Tiptap-mathematics-editor, + .Tiptap-mathematics-render { + border-radius: var(--tt-radius-xs); + display: inline-block; + } +} diff --git a/packages/editor/src/components/tiptap-templates/simple/data/content.json b/packages/editor/src/components/tiptap-templates/simple/data/content.json new file mode 100644 index 000000000..b4e2ae918 --- /dev/null +++ b/packages/editor/src/components/tiptap-templates/simple/data/content.json @@ -0,0 +1,477 @@ +{ + "type": "doc", + "content": [ + { + "type": "heading", + "attrs": { + "textAlign": null, + "level": 1 + }, + "content": [ + { + "type": "text", + "text": "Getting started" + } + ] + }, + { + "type": "paragraph", + "attrs": { + "textAlign": null + }, + "content": [ + { + "type": "text", + "text": "Welcome to the " + }, + { + "type": "text", + "marks": [ + { + "type": "italic" + }, + { + "type": "highlight", + "attrs": { + "color": "var(--tt-highlight-yellow)" + } + } + ], + "text": "Simple Editor" + }, + { + "type": "text", + "text": " template! This template integrates " + }, + { + "type": "text", + "marks": [ + { + "type": "bold" + } + ], + "text": "open source" + }, + { + "type": "text", + "text": " UI components and Tiptap extensions licensed under " + }, + { + "type": "text", + "marks": [ + { + "type": "bold" + } + ], + "text": "MIT" + }, + { + "type": "text", + "text": "." + } + ] + }, + { + "type": "paragraph", + "attrs": { + "textAlign": null + }, + "content": [ + { + "type": "text", + "text": "Integrate it by following the " + }, + { + "type": "text", + "marks": [ + { + "type": "link", + "attrs": { + "href": "https://tiptap.dev/docs/ui-components/templates/simple-editor", + "target": "_blank", + "rel": "noopener noreferrer nofollow", + "class": null + } + } + ], + "text": "Tiptap UI Components docs" + }, + { + "type": "text", + "text": " or using our CLI tool." + } + ] + }, + { + "type": "codeBlock", + "attrs": { + "language": null + }, + "content": [ + { + "type": "text", + "text": "npx @tiptap/cli init" + } + ] + }, + { + "type": "heading", + "attrs": { + "textAlign": null, + "level": 2 + }, + "content": [ + { + "type": "text", + "text": "Features" + } + ] + }, + { + "type": "blockquote", + "content": [ + { + "type": "paragraph", + "attrs": { + "textAlign": null + }, + "content": [ + { + "type": "text", + "marks": [ + { + "type": "italic" + } + ], + "text": "A fully responsive rich text editor with built-in support for common formatting and layout tools. Type markdown " + }, + { + "type": "text", + "marks": [ + { + "type": "code" + } + ], + "text": "**" + }, + { + "type": "text", + "marks": [ + { + "type": "italic" + } + ], + "text": " or use keyboard shortcuts " + }, + { + "type": "text", + "marks": [ + { + "type": "code" + } + ], + "text": "⌘+B" + }, + { + "type": "text", + "text": " for " + }, + { + "type": "text", + "marks": [ + { + "type": "strike" + } + ], + "text": "most" + }, + { + "type": "text", + "text": " all common markdown marks. 🪄" + } + ] + } + ] + }, + { + "type": "paragraph", + "attrs": { + "textAlign": "left" + }, + "content": [ + { + "type": "text", + "text": "Add images, customize alignment, and apply " + }, + { + "type": "text", + "marks": [ + { + "type": "highlight", + "attrs": { + "color": "var(--tt-highlight-blue)" + } + } + ], + "text": "advanced formatting" + }, + { + "type": "text", + "text": " to make your writing more engaging and professional." + } + ] + }, + { + "type": "image", + "attrs": { + "src": "/images/placeholder-image.png", + "alt": "placeholder-image", + "title": "placeholder-image" + } + }, + { + "type": "bulletList", + "content": [ + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "attrs": { + "textAlign": "left" + }, + "content": [ + { + "type": "text", + "marks": [ + { + "type": "bold" + } + ], + "text": "Superscript" + }, + { + "type": "text", + "text": " (x" + }, + { + "type": "text", + "marks": [ + { + "type": "superscript" + } + ], + "text": "2" + }, + { + "type": "text", + "text": ") and " + }, + { + "type": "text", + "marks": [ + { + "type": "bold" + } + ], + "text": "Subscript" + }, + { + "type": "text", + "text": " (H" + }, + { + "type": "text", + "marks": [ + { + "type": "subscript" + } + ], + "text": "2" + }, + { + "type": "text", + "text": "O) for precision." + } + ] + } + ] + }, + { + "type": "listItem", + "content": [ + { + "type": "paragraph", + "attrs": { + "textAlign": "left" + }, + "content": [ + { + "type": "text", + "marks": [ + { + "type": "bold" + } + ], + "text": "Typographic conversion" + }, + { + "type": "text", + "text": ": automatically convert to " + }, + { + "type": "text", + "marks": [ + { + "type": "code" + } + ], + "text": "->" + }, + { + "type": "text", + "text": " an arrow " + }, + { + "type": "text", + "marks": [ + { + "type": "bold" + } + ], + "text": "→" + }, + { + "type": "text", + "text": "." + } + ] + } + ] + } + ] + }, + { + "type": "paragraph", + "attrs": { + "textAlign": "left" + }, + "content": [ + { + "type": "text", + "marks": [ + { + "type": "italic" + } + ], + "text": "→ " + }, + { + "type": "text", + "marks": [ + { + "type": "link", + "attrs": { + "href": "https://tiptap.dev/docs/ui-components/templates/simple-editor#features", + "target": "_blank", + "rel": "noopener noreferrer nofollow", + "class": null + } + } + ], + "text": "Learn more" + } + ] + }, + { + "type": "horizontalRule" + }, + { + "type": "heading", + "attrs": { + "textAlign": "left", + "level": 2 + }, + "content": [ + { + "type": "text", + "text": "Make it your own" + } + ] + }, + { + "type": "paragraph", + "attrs": { + "textAlign": "left" + }, + "content": [ + { + "type": "text", + "text": "Switch between light and dark modes, and tailor the editor's appearance with customizable CSS to match your style." + } + ] + }, + { + "type": "taskList", + "content": [ + { + "type": "taskItem", + "attrs": { + "checked": true + }, + "content": [ + { + "type": "paragraph", + "attrs": { + "textAlign": "left" + }, + "content": [ + { + "type": "text", + "text": "Test template" + } + ] + } + ] + }, + { + "type": "taskItem", + "attrs": { + "checked": false + }, + "content": [ + { + "type": "paragraph", + "attrs": { + "textAlign": "left" + }, + "content": [ + { + "type": "text", + "marks": [ + { + "type": "link", + "attrs": { + "href": "https://tiptap.dev/docs/ui-components/templates/simple-editor", + "target": "_blank", + "rel": "noopener noreferrer nofollow", + "class": null + } + } + ], + "text": "Integrate the free template" + } + ] + } + ] + } + ] + }, + { + "type": "paragraph", + "attrs": { + "textAlign": "left" + } + } + ] +} diff --git a/packages/editor/src/components/tiptap-templates/simple/simple-editor.scss b/packages/editor/src/components/tiptap-templates/simple/simple-editor.scss new file mode 100644 index 000000000..8306b5a36 --- /dev/null +++ b/packages/editor/src/components/tiptap-templates/simple/simple-editor.scss @@ -0,0 +1,32 @@ +@import url("https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap"); + + + +body { + font-family: "Inter", sans-serif; + font-optical-sizing: auto; +} + +.tiptap.ProseMirror { + font-family: "DM Sans", sans-serif; +} + +.content-wrapper { + &::-webkit-scrollbar { + display: block; + width: 0.5rem; + } + + &::-webkit-scrollbar-track { + background: transparent; + } + + &::-webkit-scrollbar-thumb { + background-color: var(--tt-scrollbar-color); + border-radius: 4px; + } + + /* Firefox scrollbar */ + scrollbar-width: thin; + scrollbar-color: var(--tt-scrollbar-color) transparent; +} \ No newline at end of file diff --git a/packages/editor/src/components/tiptap-templates/simple/simple-editor.tsx b/packages/editor/src/components/tiptap-templates/simple/simple-editor.tsx new file mode 100644 index 000000000..a1856cc05 --- /dev/null +++ b/packages/editor/src/components/tiptap-templates/simple/simple-editor.tsx @@ -0,0 +1,294 @@ +import { EditorContent, EditorContext, useEditor } from "@tiptap/react"; +import * as React from "react"; + +import { Highlight } from "@tiptap/extension-highlight"; +import { Image } from "@tiptap/extension-image"; +import { Subscript } from "@tiptap/extension-subscript"; +import { Superscript } from "@tiptap/extension-superscript"; +import { TaskItem } from "@tiptap/extension-task-item"; +import { TaskList } from "@tiptap/extension-task-list"; +import { TextAlign } from "@tiptap/extension-text-align"; +import { Typography } from "@tiptap/extension-typography"; +import { Underline } from "@tiptap/extension-underline"; +// --- Tiptap Core Extensions --- +import { StarterKit } from "@tiptap/starter-kit"; + +// --- Custom Extensions --- +import { Link } from "../../tiptap-extension/link-extension"; +import { Selection } from "../../tiptap-extension/selection-extension"; +import { TrailingNode } from "../../tiptap-extension/trailing-node-extension"; +import "../../tiptap-node/code-block-node/code-block-node.scss"; +import "../../tiptap-node/list-node/list-node.scss"; +import "../../tiptap-node/image-node/image-node.scss"; +import "../../tiptap-node/paragraph-node/paragraph-node.scss"; + +import { ThemeToggle } from "@rectangular-labs/ui/components/theme-provider"; +// --- Tiptap UI --- +import { Button } from "@rectangular-labs/ui/components/ui/button"; +import { Spacer } from "@rectangular-labs/ui/components/ui/spacer"; +import { Toolbar } from "../../tiptap-ui-primitive/toolbar"; +import { HeadingDropdownMenu } from "../../tiptap-ui/heading/heading-dropdown-menu"; +import { + LinkButton, + LinkContent, + LinkPopover, +} from "../../tiptap-ui/link-popover"; +import { ListDropdownMenu } from "../../tiptap-ui/list/list-dropdown-menu"; +import { MarkButton } from "../../tiptap-ui/mark-button"; +import { NodeButton } from "../../tiptap-ui/node-button"; +import { TextAlignButton } from "../../tiptap-ui/text-align-button"; +import { UndoRedoButton } from "../../tiptap-ui/undo-redo-button"; + +// --- Icons --- +import { ArrowLeftIcon, HighlighterIcon, LinkIcon } from "../../icons"; + +// --- Hooks --- +import { useIsMobile } from "@rectangular-labs/ui/hooks/use-is-mobile"; +import { useWindowSize } from "@rectangular-labs/ui/hooks/use-window-size"; + +// --- Styles --- +import "./simple-editor.scss"; +import { Separator } from "@rectangular-labs/ui/components/ui/separator"; +import { LoroCRDT } from "../../tiptap-extension/loro-extension"; + +const MainToolbarContent = ({ + onHighlighterClick, + onLinkClick, + isMobile, +}: { + onHighlighterClick: () => void; + onLinkClick: () => void; + isMobile: boolean; +}) => { + return ( + <> + + +
+ + +
+ + + +
+ + + + +
+ + + +
+ + + + + + {/* {isMobile ? ( + + ) : ( + + )} */} + {isMobile ? : } +
+ + + +
+ + +
+ + + +
+ + + + +
+ + + + {/* + + */} + + + + {isMobile && } + +
+ +
+ + ); +}; + +const MobileToolbarContent = ({ + type, + onBack, +}: { + type: "highlighter" | "link"; + onBack: () => void; +}) => ( + <> +
+ +
+ + + + + {/* {type === "highlighter" ? : } */} + +); + +export function SimpleEditor() { + const isMobile = useIsMobile(); + const windowSize = useWindowSize(); + const [mobileView, setMobileView] = React.useState< + "main" | "highlighter" | "link" + >("main"); + const [rect, setRect] = React.useState< + Pick + >({ + x: 0, + y: 0, + width: 0, + height: 0, + }); + const toolbarRef = React.useRef(null); + + React.useEffect(() => { + const updateRect = () => { + setRect(document.body.getBoundingClientRect()); + }; + + updateRect(); + + const resizeObserver = new ResizeObserver(updateRect); + resizeObserver.observe(document.body); + + window.addEventListener("scroll", updateRect); + + return () => { + resizeObserver.disconnect(); + window.removeEventListener("scroll", updateRect); + }; + }, []); + + const editor = useEditor({ + immediatelyRender: false, + editorProps: { + attributes: { + autocomplete: "off", + autocorrect: "off", + autocapitalize: "off", + "aria-label": "Main content area, start typing to enter text.", + }, + }, + extensions: [ + StarterKit, + TextAlign.configure({ types: ["heading", "paragraph"] }), + Underline, + TaskList, + TaskItem.configure({ nested: true }), + Highlight.configure({ multicolor: true }), + Image, + Typography, + Superscript, + Subscript, + + Selection, + // ImageUploadNode.configure({ + // accept: "image/*", + // maxSize: MAX_FILE_SIZE, + // limit: 3, + // upload: handleImageUpload, + // onError: (error) => console.error("Upload failed:", error), + // }), + TrailingNode, + Link.configure({ openOnClick: false }), + LoroCRDT, + ], + // content: content, + }); + + React.useEffect(() => { + const checkCursorVisibility = () => { + if (!editor || !toolbarRef.current) return; + + const { state, view } = editor; + if (!view.hasFocus()) return; + + const { from } = state.selection; + const cursorCoords = view.coordsAtPos(from); + + if (windowSize.height < rect.height) { + if (cursorCoords && toolbarRef.current) { + const toolbarHeight = + toolbarRef.current.getBoundingClientRect().height; + const isEnoughSpace = + windowSize.height - cursorCoords.top - toolbarHeight > 0; + + // If not enough space, scroll until the cursor is the middle of the screen + if (!isEnoughSpace) { + const scrollY = + cursorCoords.top - windowSize.height / 2 + toolbarHeight; + window.scrollTo({ + top: scrollY, + behavior: "smooth", + }); + } + } + } + }; + + checkCursorVisibility(); + }, [editor, rect.height, windowSize.height]); + + React.useEffect(() => { + if (!isMobile && mobileView !== "main") { + setMobileView("main"); + } + }, [isMobile, mobileView]); + + return ( + +
+ + {mobileView === "main" ? ( + setMobileView("highlighter")} + onLinkClick={() => setMobileView("link")} + isMobile={isMobile} + /> + ) : ( + setMobileView("main")} + /> + )} + + +
+ +
+
+
+ ); +} diff --git a/packages/editor/src/components/tiptap-ui-primitive/toolbar/index.tsx b/packages/editor/src/components/tiptap-ui-primitive/toolbar/index.tsx new file mode 100644 index 000000000..cd2701dba --- /dev/null +++ b/packages/editor/src/components/tiptap-ui-primitive/toolbar/index.tsx @@ -0,0 +1 @@ +export * from "./toolbar"; diff --git a/packages/editor/src/components/tiptap-ui-primitive/toolbar/toolbar.scss b/packages/editor/src/components/tiptap-ui-primitive/toolbar/toolbar.scss new file mode 100644 index 000000000..897be5733 --- /dev/null +++ b/packages/editor/src/components/tiptap-ui-primitive/toolbar/toolbar.scss @@ -0,0 +1,96 @@ +:root { + --tt-toolbar-height: 2.75rem; + --tt-safe-area-bottom: env(safe-area-inset-bottom, 0px); + --tt-toolbar-bg-color: var(--white); + --tt-toolbar-border-color: var(--tt-gray-light-a-100); +} + +.dark { + --tt-toolbar-bg-color: var(--black); + --tt-toolbar-border-color: var(--tt-gray-dark-a-50); +} + +.tiptap-toolbar { + display: flex; + align-items: center; + gap: 0.25rem; + + &-group { + display: flex; + align-items: center; + gap: 0.125rem; + + &:empty { + display: none; + } + + &:empty+.tiptap-separator, + .tiptap-separator+&:empty { + display: none; + } + } + + &[data-variant="fixed"] { + position: sticky; + top: 0; + z-index: 10; + width: 100%; + min-height: var(--tt-toolbar-height); + background: var(--tt-toolbar-bg-color); + border-bottom: 1px solid var(--tt-toolbar-border-color); + padding: 0 0.5rem; + overflow-x: auto; + overscroll-behavior-x: contain; + -webkit-overflow-scrolling: touch; + scrollbar-width: none; + -ms-overflow-style: none; + + &::-webkit-scrollbar { + display: none; + } + + @media (max-width: 480px) { + position: fixed; + top: auto; + bottom: 0; + height: calc(var(--tt-toolbar-height) + var(--tt-safe-area-bottom)); + border-top: 1px solid var(--tt-toolbar-border-color); + border-bottom: none; + padding: 0 0.5rem var(--tt-safe-area-bottom); + flex-wrap: nowrap; + justify-content: flex-start; + + .tiptap-toolbar-group { + flex: 0 0 auto; + } + } + } + + &[data-variant="floating"] { + --tt-toolbar-padding: 0.25rem; + --tt-toolbar-border-width: 1px; + + padding: 0.188rem; + border-radius: calc(var(--tt-toolbar-padding) + var(--tt-radius-lg) + var(--tt-toolbar-border-width)); + border: var(--tt-toolbar-border-width) solid var(--tt-toolbar-border-color); + background-color: var(--tt-toolbar-bg-color); + box-shadow: var(--tt-shadow-elevated-md); + outline: none; + overflow: visible; + + &[data-plain="true"] { + padding: 0; + border-radius: 0; + border: none; + box-shadow: none; + background-color: transparent; + } + + @media screen and (max-width: 768px) { + width: 100%; + border-radius: 0; + border: none; + box-shadow: none; + } + } +} \ No newline at end of file diff --git a/packages/editor/src/components/tiptap-ui-primitive/toolbar/toolbar.tsx b/packages/editor/src/components/tiptap-ui-primitive/toolbar/toolbar.tsx new file mode 100644 index 000000000..3a25b6b52 --- /dev/null +++ b/packages/editor/src/components/tiptap-ui-primitive/toolbar/toolbar.tsx @@ -0,0 +1,118 @@ +import { cn } from "@rectangular-labs/ui/utils/cn"; +import * as React from "react"; + +type BaseProps = React.HTMLAttributes; + +interface ToolbarProps extends BaseProps { + variant?: "floating" | "fixed"; +} + +const mergeRefs = ( + refs: Array | React.Ref | null | undefined>, +): React.RefCallback => { + return (value) => { + for (const ref of refs) { + if (typeof ref === "function") { + ref(value); + } else if (ref != null) { + (ref as React.MutableRefObject).current = value; + } + } + }; +}; + +const useToolbarKeyboardNav = ( + toolbarRef: React.RefObject, +): void => { + React.useEffect(() => { + const toolbar = toolbarRef.current; + if (!toolbar) return; + + const getFocusableElements = () => + Array.from( + toolbar.querySelectorAll( + 'button:not([disabled]), [role="button"]:not([disabled]), [tabindex="0"]:not([disabled])', + ), + ); + + const navigateToIndex = ( + e: KeyboardEvent, + targetIndex: number, + elements: HTMLElement[], + ) => { + e.preventDefault(); + let nextIndex = targetIndex; + + if (nextIndex >= elements.length) { + nextIndex = 0; + } else if (nextIndex < 0) { + nextIndex = elements.length - 1; + } + + elements[nextIndex]?.focus(); + }; + + const handleKeyDown = (e: KeyboardEvent) => { + const focusableElements = getFocusableElements(); + if (!focusableElements.length) return; + + const currentElement = document.activeElement as HTMLElement; + const currentIndex = focusableElements.indexOf(currentElement); + + if (!toolbar.contains(currentElement)) return; + + const keyActions: Record void> = { + ArrowRight: () => + navigateToIndex(e, currentIndex + 1, focusableElements), + ArrowDown: () => + navigateToIndex(e, currentIndex + 1, focusableElements), + ArrowLeft: () => + navigateToIndex(e, currentIndex - 1, focusableElements), + ArrowUp: () => navigateToIndex(e, currentIndex - 1, focusableElements), + Home: () => navigateToIndex(e, 0, focusableElements), + End: () => + navigateToIndex(e, focusableElements.length - 1, focusableElements), + }; + + const action = keyActions[e.key]; + if (action) { + action(); + } + }; + + toolbar.addEventListener("keydown", handleKeyDown); + return () => toolbar.removeEventListener("keydown", handleKeyDown); + }, [toolbarRef]); +}; + +export const Toolbar = React.forwardRef( + ({ children, className, variant = "floating", ...props }, ref) => { + const toolbarRef = React.useRef(null); + + useToolbarKeyboardNav(toolbarRef); + + return ( +
+ {children} +
+ ); + }, +); + +Toolbar.displayName = "Toolbar"; diff --git a/packages/editor/src/components/tiptap-ui/heading/heading-button.tsx b/packages/editor/src/components/tiptap-ui/heading/heading-button.tsx new file mode 100644 index 000000000..724360c27 --- /dev/null +++ b/packages/editor/src/components/tiptap-ui/heading/heading-button.tsx @@ -0,0 +1,176 @@ +import { + ShortcutDisplay, + type ShortcutKeys, +} from "@rectangular-labs/ui/components/ui/shortcut"; +import * as React from "react"; +import { + HeadingFiveIcon, + HeadingFourIcon, + HeadingOneIcon, + HeadingSixIcon, + HeadingThreeIcon, + HeadingTwoIcon, +} from "../../icons"; // Adjusted path + +// --- Hooks --- +import { useTiptapEditor } from "../../../hooks/use-tiptap-editor"; // Adjusted path + +import { + Toggle, + type ToggleProps, +} from "@rectangular-labs/ui/components/ui/toggle"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@rectangular-labs/ui/components/ui/tooltip"; +// --- Lib --- +import { isNodeInSchema } from "../../../tiptap-utils"; // Corrected path + +export type Level = 1 | 2 | 3 | 4 | 5 | 6; + +// --- Constants --- +export const HeadingOptions: Record< + Level, + { + icon: React.ElementType; + shortcutKey: ShortcutKeys; + label: string; + } +> = { + 1: { + icon: HeadingOneIcon, + shortcutKey: "ctrl-alt-1", + label: "Heading 1", + }, + 2: { + icon: HeadingTwoIcon, + shortcutKey: "ctrl-alt-2", + label: "Heading 2", + }, + 3: { + icon: HeadingThreeIcon, + shortcutKey: "ctrl-alt-3", + label: "Heading 3", + }, + 4: { + icon: HeadingFourIcon, + shortcutKey: "ctrl-alt-4", + label: "Heading 4", + }, + 5: { + icon: HeadingFiveIcon, + shortcutKey: "ctrl-alt-5", + label: "Heading 5", + }, + 6: { + icon: HeadingSixIcon, + shortcutKey: "ctrl-alt-6", + label: "Heading 6", + }, +}; + +export function useHeading(level: Level, manuallyDisabled = false) { + const editor = useTiptapEditor(); + + const isDisabled = React.useMemo(() => { + const isHeadingInSchema = isNodeInSchema("heading", editor); + if (!isHeadingInSchema) { + console.warn( + `Heading ${level} node is not available in the editor schema. Make sure it is included in your editor configuration.`, + ); + } + if ( + !editor || + !isHeadingInSchema || + manuallyDisabled || + editor.isActive("codeBlock") + ) { + return true; + } + + try { + return !editor.can().toggleNode("heading", "paragraph", { level }); + } catch (e) { + console.error("Error checking heading toggle", e); + return true; + } + }, [editor, level, manuallyDisabled]); + + const isActive = React.useMemo(() => { + if (!editor) return false; + return editor.isActive("heading", { level }); + }, [editor, level]); + + const handleToggle = React.useCallback(() => { + if (!editor || isDisabled) return; + if (editor.isActive("heading", { level })) { + return editor.chain().focus().setNode("paragraph").run(); + } + return editor + .chain() + .focus() + .toggleNode("heading", "paragraph", { level }) + .run(); + }, [editor, level, isDisabled]); + + const displayOptions = HeadingOptions[level]; + + return { + isDisabled, + isActive, + handleToggle, + displayOptions, + }; +} + +// --- Component Props --- +export interface HeadingButtonProps + extends Omit { + /** + * The heading level. + */ + level: Level; + /** + * Whether to display the text label next to the icon. + * @default false + */ + showText?: boolean; +} + +export const HeadingButton = React.forwardRef< + HTMLButtonElement, + HeadingButtonProps +>(({ level, showText = false, disabled, ...buttonProps }, ref) => { + const { isDisabled, isActive, handleToggle, displayOptions } = useHeading( + level, + disabled, + ); + + return ( + + + + + {showText && {displayOptions.label}} + + + + {displayOptions.label} + + + + ); +}); + +HeadingButton.displayName = "HeadingButton"; + +export default HeadingButton; diff --git a/packages/editor/src/components/tiptap-ui/heading/heading-dropdown-menu.tsx b/packages/editor/src/components/tiptap-ui/heading/heading-dropdown-menu.tsx new file mode 100644 index 000000000..132526054 --- /dev/null +++ b/packages/editor/src/components/tiptap-ui/heading/heading-dropdown-menu.tsx @@ -0,0 +1,76 @@ +import { + Button, + type ButtonProps, +} from "@rectangular-labs/ui/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@rectangular-labs/ui/components/ui/dropdown-menu"; +import * as React from "react"; +import { useTiptapEditor } from "../../../hooks/use-tiptap-editor"; +import { ChevronDownIcon, HeadingIcon } from "../../icons"; +import HeadingButton, { HeadingOptions, type Level } from "./heading-button"; + +interface HeadingDropdownMenuProps extends Omit { + /** + * The levels to display in the dropdown. + * @default [1, 2, 3, 4, 5, 6] + */ + levels?: Level[]; +} + +export function HeadingDropdownMenu({ + levels = [1, 2, 3, 4, 5, 6], + ...props +}: HeadingDropdownMenuProps) { + const [isOpen, setIsOpen] = React.useState(false); + const editor = useTiptapEditor(); + + const currentDisplayOption = React.useMemo(() => { + for (const level of levels) { + if (editor?.isActive("heading", { level })) { + return { ...HeadingOptions[level], isActive: true }; + } + } + return { + icon: HeadingIcon, + label: "Heading", + isActive: false, + }; + }, [editor, levels]); + + return ( + + + + + + + + {levels.map((level) => ( + + + + ))} + + + + ); +} diff --git a/packages/editor/src/components/tiptap-ui/highlight-popover/highlight-popover.scss b/packages/editor/src/components/tiptap-ui/highlight-popover/highlight-popover.scss new file mode 100644 index 000000000..bd94961d1 --- /dev/null +++ b/packages/editor/src/components/tiptap-ui/highlight-popover/highlight-popover.scss @@ -0,0 +1,72 @@ +:root { + --tt-highlight-green: #dcfce7; + --tt-highlight-blue: #e0f2fe; + --tt-highlight-red: #ffe4e6; + --tt-highlight-purple: #f3e8ff; + --tt-highlight-yellow: #fef9c3; +} + +.dark { + --tt-highlight-green: #509568; + --tt-highlight-blue: #6e92aa; + --tt-highlight-red: #743e42; + --tt-highlight-purple: #583e74; + --tt-highlight-yellow: #6b6524; +} + +.tiptap-highlight-content { + display: flex; + align-items: center; + gap: 0.25rem; + outline: none; +} + +.tiptap-button-highlight { + position: relative; + width: 1.25rem; + height: 1.25rem; + margin: 0 -0.175rem; + border-radius: 100%; + background-color: var(--highlight-color); + transition: transform 0.2s ease; + + &::after { + content: ""; + position: absolute; + width: 100%; + height: 100%; + left: 0; + top: 0; + border-radius: inherit; + box-sizing: border-box; + border: 1px solid var(--highlight-color); + filter: brightness(95%); + mix-blend-mode: multiply; + + .dark & { + filter: brightness(140%); + mix-blend-mode: lighten; + } + } +} + +.tiptap-button { + &[data-active-state="on"] { + .tiptap-button-highlight { + &::after { + filter: brightness(80%); + } + } + } + + .dark & { + &[data-active-state="on"] { + .tiptap-button-highlight { + &::after { + // Andere Eigenschaft für .dark Kontext + filter: brightness(180%); + } + } + } + } +} diff --git a/packages/editor/src/components/tiptap-ui/highlight-popover/highlight-popover.tsx b/packages/editor/src/components/tiptap-ui/highlight-popover/highlight-popover.tsx new file mode 100644 index 000000000..16c9be584 --- /dev/null +++ b/packages/editor/src/components/tiptap-ui/highlight-popover/highlight-popover.tsx @@ -0,0 +1,283 @@ +import { type Editor, isNodeSelection } from "@tiptap/react"; +import * as React from "react"; +// --- Icons --- +import { BanIcon } from "@/components/tiptap-icons/ban-icon"; +import { HighlighterIcon } from "@/components/tiptap-icons/highlighter-icon"; +// --- UI Primitives --- +import { + Button, + type ButtonProps, +} from "@/components/tiptap-ui-primitive/button"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/tiptap-ui-primitive/popover"; +import { Separator } from "@/components/tiptap-ui-primitive/separator"; +// --- Hooks --- +import { useMenuNavigation } from "@/hooks/use-menu-navigation"; +import { useTiptapEditor } from "@/hooks/use-tiptap-editor"; +// --- Lib --- +import { isMarkInSchema } from "@/lib/tiptap-utils"; + +// --- Styles --- +import "@/components/tiptap-ui/highlight-popover/highlight-popover.scss"; + +export interface HighlightColor { + label: string; + value: string; + border?: string; +} + +export interface HighlightContentProps { + editor?: Editor | null; + colors?: HighlightColor[]; + activeNode?: number; +} + +export const DEFAULT_HIGHLIGHT_COLORS: HighlightColor[] = [ + { + label: "Green", + value: "var(--tt-highlight-green)", + border: "var(--tt-highlight-green-contrast)", + }, + { + label: "Blue", + value: "var(--tt-highlight-blue)", + border: "var(--tt-highlight-blue-contrast)", + }, + { + label: "Red", + value: "var(--tt-highlight-red)", + border: "var(--tt-highlight-red-contrast)", + }, + { + label: "Purple", + value: "var(--tt-highlight-purple)", + border: "var(--tt-highlight-purple-contrast)", + }, + { + label: "Yellow", + value: "var(--tt-highlight-yellow)", + border: "var(--tt-highlight-yellow-contrast)", + }, +]; + +export const useHighlighter = (editor: Editor | null) => { + const markAvailable = isMarkInSchema("highlight", editor); + + const getActiveColor = React.useCallback(() => { + if (!editor) return null; + if (!editor.isActive("highlight")) return null; + const attrs = editor.getAttributes("highlight"); + return attrs.color || null; + }, [editor]); + + const toggleHighlight = React.useCallback( + (color: string) => { + if (!markAvailable || !editor) return; + if (color === "none") { + editor.chain().focus().unsetMark("highlight").run(); + } else { + editor.chain().focus().toggleMark("highlight", { color }).run(); + } + }, + [markAvailable, editor], + ); + + return { + markAvailable, + getActiveColor, + toggleHighlight, + }; +}; + +export const HighlighterButton = React.forwardRef< + HTMLButtonElement, + ButtonProps +>(({ className, children, ...props }, ref) => { + return ( + + ); +}); + +export function HighlightContent({ + editor: providedEditor, + colors = DEFAULT_HIGHLIGHT_COLORS, + onClose, +}: { + editor?: Editor | null; + colors?: HighlightColor[]; + onClose?: () => void; +}) { + const editor = useTiptapEditor(providedEditor); + + const containerRef = React.useRef(null); + + const { getActiveColor, toggleHighlight } = useHighlighter(editor); + const activeColor = getActiveColor(); + + const menuItems = React.useMemo( + () => [...colors, { label: "Remove highlight", value: "none" }], + [colors], + ); + + const { selectedIndex } = useMenuNavigation({ + containerRef, + items: menuItems, + orientation: "both", + onSelect: (item) => { + toggleHighlight(item.value); + onClose?.(); + }, + onClose, + autoSelectFirstItem: false, + }); + + return ( +
+
+ {colors.map((color, index) => ( + + ))} +
+ + + +
+ +
+
+ ); +} + +export interface HighlightPopoverProps extends Omit { + /** + * The TipTap editor instance. + */ + editor?: Editor | null; + /** + * The highlight colors to display in the popover. + */ + colors?: HighlightColor[]; + /** + * Whether to hide the highlight popover. + */ + hideWhenUnavailable?: boolean; +} + +export function HighlightPopover({ + editor: providedEditor, + colors = DEFAULT_HIGHLIGHT_COLORS, + hideWhenUnavailable = false, + ...props +}: HighlightPopoverProps) { + const editor = useTiptapEditor(providedEditor); + + const { markAvailable } = useHighlighter(editor); + const [isOpen, setIsOpen] = React.useState(false); + + const isDisabled = React.useMemo(() => { + if (!markAvailable || !editor) { + return true; + } + + return ( + editor.isActive("code") || + editor.isActive("codeBlock") || + editor.isActive("imageUpload") + ); + }, [markAvailable, editor]); + + const canSetMark = React.useMemo(() => { + if (!editor || !markAvailable) return false; + + try { + return editor.can().setMark("highlight"); + } catch { + return false; + } + }, [editor, markAvailable]); + + const isActive = editor?.isActive("highlight") ?? false; + + const show = React.useMemo(() => { + if (hideWhenUnavailable) { + if (isNodeSelection(editor?.state.selection) || !canSetMark) { + return false; + } + } + + return true; + }, [hideWhenUnavailable, editor, canSetMark]); + + if (!show || !editor || !editor.isEditable) { + return null; + } + + return ( + + + + + + + setIsOpen(false)} + /> + + + ); +} + +HighlighterButton.displayName = "HighlighterButton"; + +export default HighlightPopover; diff --git a/packages/editor/src/components/tiptap-ui/highlight-popover/index.tsx b/packages/editor/src/components/tiptap-ui/highlight-popover/index.tsx new file mode 100644 index 000000000..1583a5dee --- /dev/null +++ b/packages/editor/src/components/tiptap-ui/highlight-popover/index.tsx @@ -0,0 +1 @@ +export * from "./highlight-popover"; diff --git a/packages/editor/src/components/tiptap-ui/image-upload-button/image-upload-button.tsx b/packages/editor/src/components/tiptap-ui/image-upload-button/image-upload-button.tsx new file mode 100644 index 000000000..bf01bac71 --- /dev/null +++ b/packages/editor/src/components/tiptap-ui/image-upload-button/image-upload-button.tsx @@ -0,0 +1,125 @@ +import type { Editor } from "@tiptap/react"; +import * as React from "react"; +// --- Icons --- +import { ImagePlusIcon } from "@/components/tiptap-icons/image-plus-icon"; +// --- UI Primitives --- +import { + Button, + type ButtonProps, +} from "@/components/tiptap-ui-primitive/button"; +// --- Hooks --- +import { useTiptapEditor } from "@/hooks/use-tiptap-editor"; + +export interface ImageUploadButtonProps extends ButtonProps { + editor?: Editor | null; + text?: string; + extensionName?: string; +} + +export function isImageActive( + editor: Editor | null, + extensionName: string, +): boolean { + if (!editor) return false; + return editor.isActive(extensionName); +} + +export function insertImage( + editor: Editor | null, + extensionName: string, +): boolean { + if (!editor) return false; + + return editor + .chain() + .focus() + .insertContent({ + type: extensionName, + }) + .run(); +} + +export function useImageUploadButton( + editor: Editor | null, + extensionName = "imageUpload", + disabled = false, +) { + const isActive = isImageActive(editor, extensionName); + const handleInsertImage = React.useCallback(() => { + if (disabled) return false; + return insertImage(editor, extensionName); + }, [editor, extensionName, disabled]); + + return { + isActive, + handleInsertImage, + }; +} + +export const ImageUploadButton = React.forwardRef< + HTMLButtonElement, + ImageUploadButtonProps +>( + ( + { + editor: providedEditor, + extensionName = "imageUpload", + text, + className = "", + disabled, + onClick, + children, + ...buttonProps + }, + ref, + ) => { + const editor = useTiptapEditor(providedEditor); + const { isActive, handleInsertImage } = useImageUploadButton( + editor, + extensionName, + disabled, + ); + + const handleClick = React.useCallback( + (e: React.MouseEvent) => { + onClick?.(e); + + if (!e.defaultPrevented && !disabled) { + handleInsertImage(); + } + }, + [onClick, disabled, handleInsertImage], + ); + + if (!editor || !editor.isEditable) { + return null; + } + + return ( + + ); + }, +); + +ImageUploadButton.displayName = "ImageUploadButton"; + +export default ImageUploadButton; diff --git a/packages/editor/src/components/tiptap-ui/image-upload-button/index.tsx b/packages/editor/src/components/tiptap-ui/image-upload-button/index.tsx new file mode 100644 index 000000000..0a65ee9ca --- /dev/null +++ b/packages/editor/src/components/tiptap-ui/image-upload-button/index.tsx @@ -0,0 +1 @@ +export * from "./image-upload-button"; diff --git a/packages/editor/src/components/tiptap-ui/link-popover/index.tsx b/packages/editor/src/components/tiptap-ui/link-popover/index.tsx new file mode 100644 index 000000000..7113a42ee --- /dev/null +++ b/packages/editor/src/components/tiptap-ui/link-popover/index.tsx @@ -0,0 +1 @@ +export * from "./link-popover"; diff --git a/packages/editor/src/components/tiptap-ui/link-popover/link-popover.scss b/packages/editor/src/components/tiptap-ui/link-popover/link-popover.scss new file mode 100644 index 000000000..eb894ecda --- /dev/null +++ b/packages/editor/src/components/tiptap-ui/link-popover/link-popover.scss @@ -0,0 +1,27 @@ +.tiptap-input { + display: block; + width: 100%; + height: 2rem; + font-size: 1rem; + line-height: 1.5rem; + padding: 0.375rem 0.75rem; + border-radius: 0.375rem; + background: none; + + &:focus { + outline: none; + } + + &-clamp { + min-width: 12rem; + padding-right: 0; + + text-overflow: ellipsis; + white-space: nowrap; + + &:focus { + text-overflow: clip; + overflow: visible; + } + } +} diff --git a/packages/editor/src/components/tiptap-ui/link-popover/link-popover.tsx b/packages/editor/src/components/tiptap-ui/link-popover/link-popover.tsx new file mode 100644 index 000000000..109eca328 --- /dev/null +++ b/packages/editor/src/components/tiptap-ui/link-popover/link-popover.tsx @@ -0,0 +1,311 @@ +import { type Editor, isNodeSelection } from "@tiptap/react"; +import * as React from "react"; + +import "./link-popover.scss"; +import { + Button, + type ButtonProps, +} from "@rectangular-labs/ui/components/ui/button"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@rectangular-labs/ui/components/ui/popover"; +import { Separator } from "@rectangular-labs/ui/components/ui/separator"; +import { + CornerDownLeftIcon, + ExternalLinkIcon, + LinkIcon, + TrashIcon, +} from "lucide-react"; +import { useTiptapEditor } from "../../../hooks/use-tiptap-editor"; +import { isMarkInSchema } from "../../../tiptap-utils"; + +const useLinkHandler = (props: { + editor: Editor | null; + onSetLink?: () => void; + onLinkActive?: () => void; +}) => { + const { editor, onSetLink, onLinkActive } = props; + const [url, setUrl] = React.useState(""); + + React.useEffect(() => { + if (!editor) return; + + // Get URL immediately on mount + const { href } = editor.getAttributes("link"); + + if (editor.isActive("link") && !url) { + setUrl(href || ""); + onLinkActive?.(); + } + }, [editor, onLinkActive, url]); + + React.useEffect(() => { + if (!editor) return; + + const updateLinkState = () => { + const { href } = editor.getAttributes("link"); + setUrl(href || ""); + + if (editor.isActive("link") && !url) { + onLinkActive?.(); + } + }; + + editor.on("selectionUpdate", updateLinkState); + return () => { + editor.off("selectionUpdate", updateLinkState); + }; + }, [editor, onLinkActive, url]); + + const setLink = React.useCallback(() => { + if (!url || !editor) return; + + const { from, to } = editor.state.selection; + const text = editor.state.doc.textBetween(from, to); + + editor + .chain() + .focus() + .extendMarkRange("link") + .insertContent({ + type: "text", + text: text || url, + marks: [{ type: "link", attrs: { href: url } }], + }) + .run(); + + onSetLink?.(); + }, [editor, onSetLink, url]); + + const removeLink = React.useCallback(() => { + if (!editor) return; + editor + .chain() + .focus() + .unsetMark("link", { extendEmptyMarkRange: true }) + .setMeta("preventAutolink", true) + .run(); + setUrl(""); + }, [editor]); + + return { + url, + setUrl, + setLink, + removeLink, + isActive: editor?.isActive("link") || false, + }; +}; + +export const LinkButton = React.forwardRef( + ({ className, children, ...props }, ref) => { + return ( + + ); + }, +); + +export function LinkContent() { + const editor = useTiptapEditor(); + + const linkHandler = useLinkHandler({ + editor: editor, + }); + + return ; +} + +interface LinkMainProps { + url: string; + setUrl: React.Dispatch>; + setLink: () => void; + removeLink: () => void; + isActive: boolean; +} + +const LinkMain: React.FC = ({ + url, + setUrl, + setLink, + removeLink, + isActive, +}) => { + const handleKeyDown = (event: React.KeyboardEvent) => { + if (event.key === "Enter") { + event.preventDefault(); + setLink(); + } + }; + + return ( + <> + setUrl(e.target.value)} + onKeyDown={handleKeyDown} + autoComplete="off" + autoCorrect="off" + autoCapitalize="off" + className="tiptap-input tiptap-input-clamp" + /> + +
+ +
+ + + +
+ + + +
+ + ); +}; + +export interface LinkPopoverProps extends Omit { + /** + * The TipTap editor instance. + */ + editor?: Editor | null; + /** + * Whether to hide the link popover. + * @default false + */ + hideWhenUnavailable?: boolean; + /** + * Callback for when the popover opens or closes. + */ + onOpenChange?: (isOpen: boolean) => void; + /** + * Whether to automatically open the popover when a link is active. + * @default true + */ + autoOpenOnLinkActive?: boolean; +} + +export function LinkPopover({ + editor: providedEditor, + hideWhenUnavailable = false, + onOpenChange, + autoOpenOnLinkActive = true, + ...props +}: LinkPopoverProps) { + const editor = useTiptapEditor(providedEditor); + + const linkInSchema = isMarkInSchema("link", editor); + + const [isOpen, setIsOpen] = React.useState(false); + + const onSetLink = () => { + setIsOpen(false); + }; + + const onLinkActive = () => setIsOpen(autoOpenOnLinkActive); + + const linkHandler = useLinkHandler({ + editor: editor, + onSetLink, + onLinkActive, + }); + + const isDisabled = React.useMemo(() => { + if (!editor) return true; + if (editor.isActive("codeBlock")) return true; + return !editor.can().setLink?.({ href: "" }); + }, [editor]); + + const canSetLink = React.useMemo(() => { + if (!editor) return false; + try { + return editor.can().setMark("link"); + } catch { + return false; + } + }, [editor]); + + const isActive = editor?.isActive("link") ?? false; + + const handleOnOpenChange = React.useCallback( + (nextIsOpen: boolean) => { + setIsOpen(nextIsOpen); + onOpenChange?.(nextIsOpen); + }, + [onOpenChange], + ); + + const show = React.useMemo(() => { + if (!linkInSchema) { + return false; + } + + if (hideWhenUnavailable) { + if (isNodeSelection(editor?.state.selection) || !canSetLink) { + return false; + } + } + + return true; + }, [linkInSchema, hideWhenUnavailable, editor, canSetLink]); + + if (!show || !editor || !editor.isEditable) { + return null; + } + + return ( + + + + + + + + + + ); +} + +LinkButton.displayName = "LinkButton"; diff --git a/packages/editor/src/components/tiptap-ui/list/list-button.tsx b/packages/editor/src/components/tiptap-ui/list/list-button.tsx new file mode 100644 index 000000000..178310c7a --- /dev/null +++ b/packages/editor/src/components/tiptap-ui/list/list-button.tsx @@ -0,0 +1,154 @@ +"use client"; +import { + ShortcutDisplay, + type ShortcutKeys, +} from "@rectangular-labs/ui/components/ui/shortcut"; +import { + Toggle, + type ToggleProps, +} from "@rectangular-labs/ui/components/ui/toggle"; +import { + TooltipContent, + TooltipTrigger, +} from "@rectangular-labs/ui/components/ui/tooltip"; +import { Tooltip } from "@rectangular-labs/ui/components/ui/tooltip"; +import * as React from "react"; +import { useTiptapEditor } from "../../../hooks/use-tiptap-editor"; +import { isNodeInSchema } from "../../../tiptap-utils"; +import { ListIcon, ListOrderedIcon, ListTodoIcon } from "../../icons"; + +export type ListType = "bulletList" | "orderedList" | "taskList"; + +export const ListOptions: Record< + ListType, + { + shortcutKey: ShortcutKeys; + label: string; + icon: React.ElementType; + } +> = { + bulletList: { + shortcutKey: "ctrl-shift-7", + label: "Bullet List", + icon: ListIcon, + }, + orderedList: { + shortcutKey: "ctrl-shift-8", + label: "Ordered List", + icon: ListOrderedIcon, + }, + taskList: { + shortcutKey: "ctrl-shift-9", + label: "Task List", + icon: ListTodoIcon, + }, +}; + +function useListState(type: ListType, manuallyDisabled = false) { + const editor = useTiptapEditor(); + + const isDisabled = React.useMemo(() => { + const listInSchema = isNodeInSchema(type, editor); + + if (!listInSchema) { + console.warn( + `List type ${type} is not available. Make sure it is included in your editor configuration.`, + ); + } + if ( + !editor || + !listInSchema || + manuallyDisabled || + editor.isActive("codeBlock") + ) + return true; + + try { + switch (type) { + case "bulletList": + return !editor.can().toggleBulletList(); + case "orderedList": + return !editor.can().toggleOrderedList(); + case "taskList": + return !editor.can().toggleList("taskList", "taskItem"); + default: + return true; + } + } catch (error) { + console.error("Error checking mark toggle", error); + return true; + } + }, [editor, type, manuallyDisabled]); + + const isActive = React.useMemo(() => { + if (!editor) return false; + return editor.isActive(type); + }, [editor, type]); + + const toggleList = React.useCallback(() => { + if (!editor) return; + switch (type) { + case "bulletList": + editor.chain().focus().toggleBulletList().run(); + break; + case "orderedList": + editor.chain().focus().toggleOrderedList().run(); + break; + case "taskList": + editor.chain().focus().toggleList("taskList", "taskItem").run(); + break; + } + }, [editor, type]); + + const displayOptions = ListOptions[type]; + + return { + isDisabled, + isActive, + toggleList, + displayOptions, + }; +} + +interface ListToggleProps + extends Omit { + /** + * The type of list to toggle. + */ + type: ListType; + showText?: boolean; +} +export const ListToggle = React.forwardRef( + ({ type, showText, ...buttonProps }, ref) => { + const { toggleList, isActive, displayOptions } = useListState( + type, + buttonProps.disabled, + ); + + return ( + + + + + {showText && {displayOptions.label}} + + + + {displayOptions.label} + + + + ); + }, +); + +ListToggle.displayName = "ListButton"; + +export default ListToggle; diff --git a/packages/editor/src/components/tiptap-ui/list/list-dropdown-menu.tsx b/packages/editor/src/components/tiptap-ui/list/list-dropdown-menu.tsx new file mode 100644 index 000000000..072b33f5b --- /dev/null +++ b/packages/editor/src/components/tiptap-ui/list/list-dropdown-menu.tsx @@ -0,0 +1,75 @@ +import { + Button, + type ButtonProps, +} from "@rectangular-labs/ui/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@rectangular-labs/ui/components/ui/dropdown-menu"; +import * as React from "react"; +import { useTiptapEditor } from "../../../hooks/use-tiptap-editor"; +import { ChevronDownIcon, ListIcon } from "../../icons"; +import ListToggle, { ListOptions, type ListType } from "./list-button"; + +interface ListDropdownMenuProps extends Omit { + /** + * The list types to display in the dropdown. + */ + types?: ListType[]; +} + +export function ListDropdownMenu({ + types = ["bulletList", "orderedList", "taskList"], + ...props +}: ListDropdownMenuProps) { + const editor = useTiptapEditor(); + const [isOpen, setIsOpen] = React.useState(false); + + const currentDisplayOption = React.useMemo(() => { + for (const listType of types) { + if (editor?.isActive(listType)) { + return { ...ListOptions[listType], isActive: true }; + } + } + return { + icon: ListIcon, + label: "List", + isActive: false, + }; + }, [editor, types]); + + return ( + + + + + + + + {types.map((listType) => ( + + + + ))} + + + + ); +} diff --git a/packages/editor/src/components/tiptap-ui/mark-button.tsx b/packages/editor/src/components/tiptap-ui/mark-button.tsx new file mode 100644 index 000000000..e7f52b18e --- /dev/null +++ b/packages/editor/src/components/tiptap-ui/mark-button.tsx @@ -0,0 +1,171 @@ +import { + ShortcutDisplay, + type ShortcutKeys, +} from "@rectangular-labs/ui/components/ui/shortcut"; + +import { + Toggle, + type ToggleProps, +} from "@rectangular-labs/ui/components/ui/toggle"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@rectangular-labs/ui/components/ui/tooltip"; +import * as React from "react"; +import { useTiptapEditor } from "../../hooks/use-tiptap-editor"; +import { isMarkInSchema } from "../../tiptap-utils"; +import { + BoldIcon, + Code2Icon, + ItalicIcon, + StrikeIcon, + SubscriptIcon, + SuperscriptIcon, + UnderlineIcon, +} from "../icons"; + +type Mark = + | "bold" + | "italic" + | "strike" + | "code" + | "underline" + | "superscript" + | "subscript"; + +const markOptions: Record< + Mark, + { + icon: React.ElementType; + shortcutKey: ShortcutKeys; + label: string; + } +> = { + bold: { + icon: BoldIcon, + shortcutKey: "ctrl-b", + label: "Bold", + }, + italic: { + icon: ItalicIcon, + shortcutKey: "ctrl-i", + label: "Italic", + }, + underline: { + icon: UnderlineIcon, + shortcutKey: "ctrl-u", + label: "Underline", + }, + strike: { + icon: StrikeIcon, + shortcutKey: "ctrl-shift-s", + label: "Strike", + }, + code: { + icon: Code2Icon, + shortcutKey: "ctrl-e", + label: "Code", + }, + superscript: { + icon: SuperscriptIcon, + shortcutKey: "ctrl-.", + label: "Superscript", + }, + subscript: { + icon: SubscriptIcon, + shortcutKey: "ctrl-,", + label: "Subscript", + }, +}; + +export function useMark(type: Mark, manuallyDisabled = false) { + const editor = useTiptapEditor(); + + const isDisabled = (() => { + if (!editor) return true; + const markInSchema = isMarkInSchema(type, editor); + + if (!markInSchema) { + console.warn( + `Mark type ${type} is not available. Make sure it is included in your editor configuration.`, + ); + } + if (manuallyDisabled || !markInSchema || editor.isActive("codeBlock")) + return true; + + try { + return !editor.can().toggleMark(type); + } catch (error) { + console.error("Error checking mark toggle", error); + return true; + } + })(); + + const isActive = (() => { + if (!editor) return false; + return editor.isActive(type); + })(); + + const handleToggleMark = React.useCallback(() => { + if (isDisabled || !editor) return false; + return editor.chain().focus().toggleMark(type).run(); + }, [editor, type, isDisabled]); + + const displayOptions = markOptions[type]; + + return { + isDisabled, + isActive, + handleToggleMark, + displayOptions, + }; +} + +interface MarkButtonProps + extends Omit { + /** + * The type of mark to toggle + */ + type: Mark; + showText?: boolean; +} + +export const MarkButton = React.forwardRef( + ({ type, showText, disabled, ...buttonProps }, ref) => { + const { isDisabled, isActive, handleToggleMark, displayOptions } = useMark( + type, + disabled, + ); + + return ( + + + + + + {showText && {displayOptions.label}} + + + + + {displayOptions.label} + + + + ); + }, +); + +MarkButton.displayName = "MarkButton"; + +export default MarkButton; diff --git a/packages/editor/src/components/tiptap-ui/node-button.tsx b/packages/editor/src/components/tiptap-ui/node-button.tsx new file mode 100644 index 000000000..b51310c62 --- /dev/null +++ b/packages/editor/src/components/tiptap-ui/node-button.tsx @@ -0,0 +1,130 @@ +import { + ShortcutDisplay, + type ShortcutKeys, +} from "@rectangular-labs/ui/components/ui/shortcut"; +import { + Toggle, + type ToggleProps, +} from "@rectangular-labs/ui/components/ui/toggle"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@rectangular-labs/ui/components/ui/tooltip"; +import * as React from "react"; +import { useTiptapEditor } from "../../hooks/use-tiptap-editor"; +import { isNodeInSchema } from "../../tiptap-utils"; +import { BlockQuoteIcon, CodeBlockIcon } from "../icons"; + +type NodeType = "codeBlock" | "blockquote"; + +const nodeOptions: Record< + NodeType, + { + icon: React.ElementType; + shortcutKey: ShortcutKeys; + label: string; + } +> = { + codeBlock: { + icon: CodeBlockIcon, + shortcutKey: "ctrl-alt-c", + label: "Code Block", + }, + blockquote: { + icon: BlockQuoteIcon, + shortcutKey: "ctrl-shift-b", + label: "Blockquote", + }, +}; + +function useNode(type: NodeType, manuallyDisabled = false) { + const editor = useTiptapEditor(); + + const isDisabled = React.useMemo(() => { + const nodeInSchema = isNodeInSchema(type, editor); + + if (!nodeInSchema) { + console.warn( + `Node type ${type} is not available. Make sure it is included in your editor configuration.`, + ); + } + if (!editor || manuallyDisabled || !nodeInSchema) return true; + + try { + return type === "codeBlock" + ? !editor.can().toggleNode("codeBlock", "paragraph") + : !editor.can().toggleWrap("blockquote"); + } catch { + return true; + } + }, [editor, type, manuallyDisabled]); + + const isActive = React.useMemo(() => { + if (!editor) return false; + return editor.isActive(type); + }, [editor, type]); + + const handleToggle = React.useCallback(() => { + if (!editor || isDisabled) return false; + + if (type === "codeBlock") { + return editor.chain().focus().toggleNode("codeBlock", "paragraph").run(); + } + return editor.chain().focus().toggleWrap("blockquote").run(); + }, [editor, type, isDisabled]); + + const displayOptions = nodeOptions[type]; + + return { + isDisabled, + isActive, + handleToggle, + displayOptions, + }; +} + +interface NodeButtonProps + extends Omit { + /** + * The type of node to toggle. + */ + type: NodeType; + showText?: boolean; +} +export const NodeButton = React.forwardRef( + ({ type, disabled, showText, ...buttonProps }, ref) => { + const { isDisabled, isActive, handleToggle, displayOptions } = useNode( + type, + disabled, + ); + + return ( + + + + + {showText && {displayOptions.label}} + + + + {displayOptions.label} + + + + ); + }, +); + +NodeButton.displayName = "NodeButton"; + +export default NodeButton; diff --git a/packages/editor/src/components/tiptap-ui/text-align-button.tsx b/packages/editor/src/components/tiptap-ui/text-align-button.tsx new file mode 100644 index 000000000..2922617d2 --- /dev/null +++ b/packages/editor/src/components/tiptap-ui/text-align-button.tsx @@ -0,0 +1,159 @@ +import { + ShortcutDisplay, + type ShortcutKeys, +} from "@rectangular-labs/ui/components/ui/shortcut"; +import * as React from "react"; + +import { + Toggle, + type ToggleProps, +} from "@rectangular-labs/ui/components/ui/toggle"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@rectangular-labs/ui/components/ui/tooltip"; +import type { Editor } from "@tiptap/react"; +import { useTiptapEditor } from "../../hooks/use-tiptap-editor"; +import { + AlignCenterIcon, + AlignJustifyIcon, + AlignLeftIcon, + AlignRightIcon, +} from "../icons"; + +type TextAlign = "left" | "center" | "right" | "justify"; + +const textAlignOptions: Record< + TextAlign, + { + icon: React.ElementType; + shortcutKey: ShortcutKeys; + label: string; + } +> = { + left: { + icon: AlignLeftIcon, + shortcutKey: "ctrl-shift-l", + label: "Left", + }, + center: { + icon: AlignCenterIcon, + shortcutKey: "ctrl-shift-c", + label: "Center", + }, + right: { + icon: AlignRightIcon, + shortcutKey: "ctrl-shift-r", + label: "Right", + }, + justify: { + icon: AlignJustifyIcon, + shortcutKey: "ctrl-shift-j", + label: "Justify", + }, +}; + +function isTextAlignExtensionAvailable(editor: Editor | null) { + return editor?.extensionManager.extensions.some( + (extension) => extension.name === "textAlign", + ); +} + +function isTextAlignDisabled( + editor: Editor | null, + align: TextAlign, + manuallyDisabled = false, +) { + const hasExtension = isTextAlignExtensionAvailable(editor); + + if (!hasExtension) { + console.warn( + `TextAlign extension for ${align} is not available. Make sure it is included in your editor configuration.`, + ); + } + if (!editor || !hasExtension || manuallyDisabled) return true; + + try { + return !editor?.can().setTextAlign(align); + } catch (error) { + console.error("Error checking if text align is disabled", error); + return true; + } +} + +function isTextAlignActive(editor: Editor | null, align: TextAlign) { + return editor?.isActive({ textAlign: align }) ?? false; +} + +export function useTextAlign(align: TextAlign, manuallyDisabled = false) { + const editor = useTiptapEditor(); + + const isDisabled = isTextAlignDisabled(editor, align, manuallyDisabled); + + const isActive = isTextAlignActive(editor, align); + + const handleAlignment = React.useCallback(() => { + if (!editor || isDisabled) return false; + const chain = editor.chain().focus(); + return chain.setTextAlign(align).run(); + }, [editor, isDisabled, align]); + + const displayOptions = textAlignOptions[align]; + + return { + isDisabled, + isActive, + handleAlignment, + displayOptions, + }; +} + +interface TextAlignButtonProps + extends Omit { + /** + * The text alignment to apply. + */ + align: TextAlign; + showText?: boolean; +} +export const TextAlignButton = React.forwardRef< + HTMLButtonElement, + TextAlignButtonProps +>( + ( + { align, showText, className = "", disabled, onClick, ...buttonProps }, + ref, + ) => { + const { isDisabled, isActive, handleAlignment, displayOptions } = + useTextAlign(align, disabled); + + return ( + + + + + {showText && {displayOptions.label}} + + + + {displayOptions.label} + + + + ); + }, +); + +TextAlignButton.displayName = "TextAlignButton"; + +export default TextAlignButton; diff --git a/packages/editor/src/components/tiptap-ui/undo-redo-button.tsx b/packages/editor/src/components/tiptap-ui/undo-redo-button.tsx new file mode 100644 index 000000000..34d26213d --- /dev/null +++ b/packages/editor/src/components/tiptap-ui/undo-redo-button.tsx @@ -0,0 +1,120 @@ +import { + Button, + type ButtonProps, +} from "@rectangular-labs/ui/components/ui/button"; +import type { ShortcutKeys } from "@rectangular-labs/ui/components/ui/shortcut"; +import type { Editor } from "@tiptap/react"; +import * as React from "react"; +import { useTiptapEditor } from "../../hooks/use-tiptap-editor"; +import { RedoIcon, UndoIcon } from "../icons"; + +type HistoryAction = "undo" | "redo"; + +const historyIcons = { + undo: UndoIcon, + redo: RedoIcon, +}; +const historyShortcutKeys: Record = { + undo: "ctrl-z", + redo: "ctrl-shift-z", +}; +const historyActionLabels = { + undo: "Undo", + redo: "Redo", +}; + +function isUndoRedoDisabled( + editor: Editor | null, + action: HistoryAction, + manuallyDisabled = false, +) { + if (!editor || manuallyDisabled) return true; + if (action === "undo") { + return !editor.can().undo(); + } + return !editor.can().redo(); +} + +/** + * Hook that provides all the necessary state and handlers for a history action. + * + * @param editor The TipTap editor instance + * @param action The history action to handle + * @returns Object containing state and handlers for the history action + */ +function useHistoryAction(action: HistoryAction, manuallyDisabled = false) { + const editor = useTiptapEditor(); + const isDisabled = isUndoRedoDisabled(editor, action, manuallyDisabled); + + const handleAction = React.useCallback(() => { + if (!editor || isDisabled) return; + const chain = editor.chain().focus(); + + if (action === "undo") { + chain.undo().run(); + } else { + chain.redo().run(); + } + }, [editor, action, isDisabled]); + + const Icon = historyIcons[action]; + const actionLabel = historyActionLabels[action]; + const shortcutKey = historyShortcutKeys[action]; + + return { + isDisabled, + handleAction, + Icon, + actionLabel, + shortcutKey, + }; +} + +interface UndoRedoButtonProps extends ButtonProps { + action: HistoryAction; + showText?: boolean; +} + +/** + * Button component for triggering undo/redo actions in a TipTap editor. + */ +export const UndoRedoButton = React.forwardRef< + HTMLButtonElement, + UndoRedoButtonProps +>(({ action, showText, onClick, ...props }, ref) => { + const { isDisabled, handleAction, Icon, actionLabel, shortcutKey } = + useHistoryAction(action, props.disabled); + + const onClickHandler = React.useCallback( + (e: React.MouseEvent) => { + onClick?.(e); + if (!e.defaultPrevented && !isDisabled) { + handleAction(); + } + }, + [onClick, handleAction, isDisabled], + ); + + return ( + + ); +}); + +UndoRedoButton.displayName = "UndoRedoButton"; diff --git a/packages/editor/src/hooks/use-menu-navigation.ts b/packages/editor/src/hooks/use-menu-navigation.ts new file mode 100644 index 000000000..9c893d73c --- /dev/null +++ b/packages/editor/src/hooks/use-menu-navigation.ts @@ -0,0 +1,161 @@ +"use client"; + +import type { Editor } from "@tiptap/react"; +import * as React from "react"; + +type Orientation = "horizontal" | "vertical" | "both"; + +interface MenuNavigationOptions { + editor?: Editor | null; + containerRef?: React.RefObject; + query?: string; + items: T[]; + onSelect?: (item: T) => void; + onClose?: () => void; + orientation?: Orientation; + autoSelectFirstItem?: boolean; +} + +export function useMenuNavigation({ + editor, + containerRef, + query, + items, + onSelect, + onClose, + orientation = "vertical", + autoSelectFirstItem = true, +}: MenuNavigationOptions) { + const [selectedIndex, setSelectedIndex] = React.useState( + autoSelectFirstItem ? 0 : -1, + ); + + React.useEffect(() => { + const handleKeyboardNavigation = (event: KeyboardEvent) => { + if (!items.length) return false; + + const moveNext = () => + setSelectedIndex((currentIndex) => { + if (currentIndex === -1) return 0; + return (currentIndex + 1) % items.length; + }); + + const movePrev = () => + setSelectedIndex((currentIndex) => { + if (currentIndex === -1) return items.length - 1; + return (currentIndex - 1 + items.length) % items.length; + }); + + switch (event.key) { + case "ArrowUp": { + if (orientation === "horizontal") return false; + event.preventDefault(); + movePrev(); + return true; + } + + case "ArrowDown": { + if (orientation === "horizontal") return false; + event.preventDefault(); + moveNext(); + return true; + } + + case "ArrowLeft": { + if (orientation === "vertical") return false; + event.preventDefault(); + movePrev(); + return true; + } + + case "ArrowRight": { + if (orientation === "vertical") return false; + event.preventDefault(); + moveNext(); + return true; + } + + case "Tab": { + event.preventDefault(); + if (event.shiftKey) { + movePrev(); + } else { + moveNext(); + } + return true; + } + + case "Home": { + event.preventDefault(); + setSelectedIndex(0); + return true; + } + + case "End": { + event.preventDefault(); + setSelectedIndex(items.length - 1); + return true; + } + + case "Enter": { + if (event.isComposing) return false; + event.preventDefault(); + if (selectedIndex !== -1 && items[selectedIndex]) { + onSelect?.(items[selectedIndex]); + } + return true; + } + + case "Escape": { + event.preventDefault(); + onClose?.(); + return true; + } + + default: + return false; + } + }; + + let targetElement: HTMLElement | null = null; + + if (editor) { + targetElement = editor.view.dom; + } else if (containerRef?.current) { + targetElement = containerRef.current; + } + + if (targetElement) { + targetElement.addEventListener("keydown", handleKeyboardNavigation, true); + + return () => { + targetElement?.removeEventListener( + "keydown", + handleKeyboardNavigation, + true, + ); + }; + } + + return undefined; + }, [ + editor, + containerRef, + items, + selectedIndex, + onSelect, + onClose, + orientation, + ]); + + React.useEffect(() => { + if (query) { + setSelectedIndex(autoSelectFirstItem ? 0 : -1); + } + }, [query, autoSelectFirstItem]); + + return { + selectedIndex: items.length ? selectedIndex : undefined, + setSelectedIndex, + }; +} diff --git a/packages/editor/src/hooks/use-tiptap-editor.ts b/packages/editor/src/hooks/use-tiptap-editor.ts new file mode 100644 index 000000000..a24193201 --- /dev/null +++ b/packages/editor/src/hooks/use-tiptap-editor.ts @@ -0,0 +1,12 @@ +"use client"; + +import { type Editor, useCurrentEditor } from "@tiptap/react"; +import * as React from "react"; + +export function useTiptapEditor(providedEditor?: Editor | null): Editor | null { + const { editor: coreEditor } = useCurrentEditor(); + return React.useMemo( + () => providedEditor || coreEditor, + [providedEditor, coreEditor], + ); +} diff --git a/packages/editor/src/index.ts b/packages/editor/src/index.ts new file mode 100644 index 000000000..634407724 --- /dev/null +++ b/packages/editor/src/index.ts @@ -0,0 +1 @@ +export { SimpleEditor } from "./components/tiptap-templates/simple/simple-editor"; diff --git a/packages/editor/src/style.css b/packages/editor/src/style.css new file mode 100644 index 000000000..943edc746 --- /dev/null +++ b/packages/editor/src/style.css @@ -0,0 +1,220 @@ +@import "tailwindcss"; +@source "./components/**/*.{ts,tsx}"; +@source "./hooks/**/*.{ts,tsx}"; + +/* ---------------------------------------------------------------- +------------------- TIPTAP GLOBAL VARIABLES ----------------------- +---------------------------------------------------------------- */ + +:root { + /****************** + Basics + ******************/ + + overflow-wrap: break-word; + text-size-adjust: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + + /****************** + Colors variables + ******************/ + + /* Gray alpha (light mode) */ + --tt-gray-light-a-50: rgba(56, 56, 56, 0.04); + --tt-gray-light-a-100: rgba(15, 22, 36, 0.05); + --tt-gray-light-a-200: rgba(37, 39, 45, 0.1); + --tt-gray-light-a-300: rgba(47, 50, 55, 0.2); + --tt-gray-light-a-400: rgba(40, 44, 51, 0.42); + --tt-gray-light-a-500: rgba(52, 55, 60, 0.64); + --tt-gray-light-a-600: rgba(36, 39, 46, 0.78); + --tt-gray-light-a-700: rgba(35, 37, 42, 0.87); + --tt-gray-light-a-800: rgba(30, 32, 36, 0.95); + --tt-gray-light-a-900: rgba(29, 30, 32, 0.98); + + /* Gray (light mode) */ + --tt-gray-light-50: rgba(250, 250, 250, 1); + --tt-gray-light-100: rgba(244, 244, 245, 1); + --tt-gray-light-200: rgba(234, 234, 235, 1); + --tt-gray-light-300: rgba(213, 214, 215, 1); + --tt-gray-light-400: rgba(166, 167, 171, 1); + --tt-gray-light-500: rgba(125, 127, 130, 1); + --tt-gray-light-600: rgba(83, 86, 90, 1); + --tt-gray-light-700: rgba(64, 65, 69, 1); + --tt-gray-light-800: rgba(44, 45, 48, 1); + --tt-gray-light-900: rgba(34, 35, 37, 1); + + /* Gray alpha (dark mode) */ + --tt-gray-dark-a-50: rgba(232, 232, 253, 0.05); + --tt-gray-dark-a-100: rgba(231, 231, 243, 0.07); + --tt-gray-dark-a-200: rgba(238, 238, 246, 0.11); + --tt-gray-dark-a-300: rgba(239, 239, 245, 0.22); + --tt-gray-dark-a-400: rgba(244, 244, 255, 0.37); + --tt-gray-dark-a-500: rgba(236, 238, 253, 0.5); + --tt-gray-dark-a-600: rgba(247, 247, 253, 0.64); + --tt-gray-dark-a-700: rgba(251, 251, 254, 0.75); + --tt-gray-dark-a-800: rgba(253, 253, 253, 0.88); + --tt-gray-dark-a-900: rgba(255, 255, 255, 0.96); + + /* Gray (dark mode) */ + --tt-gray-dark-50: rgba(25, 25, 26, 1); + --tt-gray-dark-100: rgba(32, 32, 34, 1); + --tt-gray-dark-200: rgba(45, 45, 47, 1); + --tt-gray-dark-300: rgba(70, 70, 73, 1); + --tt-gray-dark-400: rgba(99, 99, 105, 1); + --tt-gray-dark-500: rgba(124, 124, 131, 1); + --tt-gray-dark-600: rgba(163, 163, 168, 1); + --tt-gray-dark-700: rgba(192, 192, 195, 1); + --tt-gray-dark-800: rgba(224, 224, 225, 1); + --tt-gray-dark-900: rgba(245, 245, 245, 1); + + /* Brand colors */ + --tt-brand-color-50: rgba(239, 238, 255, 1); + --tt-brand-color-100: rgba(222, 219, 255, 1); + --tt-brand-color-200: rgba(195, 189, 255, 1); + --tt-brand-color-300: rgba(157, 138, 255, 1); + --tt-brand-color-400: rgba(122, 82, 255, 1); + --tt-brand-color-500: rgba(98, 41, 255, 1); + --tt-brand-color-600: rgba(84, 0, 229, 1); + --tt-brand-color-700: rgba(75, 0, 204, 1); + --tt-brand-color-800: rgba(56, 0, 153, 1); + --tt-brand-color-900: rgba(43, 25, 102, 1); + --tt-brand-color-950: hsla(257, 100%, 9%, 1); + + /* Green */ + --tt-color-green-inc-5: hsla(129, 100%, 97%, 1); + --tt-color-green-inc-4: hsla(129, 100%, 92%, 1); + --tt-color-green-inc-3: hsla(131, 100%, 86%, 1); + --tt-color-green-inc-2: hsla(133, 98%, 78%, 1); + --tt-color-green-inc-1: hsla(137, 99%, 70%, 1); + --tt-color-green-base: hsla(147, 99%, 50%, 1); + --tt-color-green-dec-1: hsla(147, 97%, 41%, 1); + --tt-color-green-dec-2: hsla(146, 98%, 32%, 1); + --tt-color-green-dec-3: hsla(146, 100%, 24%, 1); + --tt-color-green-dec-4: hsla(144, 100%, 16%, 1); + --tt-color-green-dec-5: hsla(140, 100%, 9%, 1); + + /* Yellow */ + --tt-color-yellow-inc-5: hsla(50, 100%, 97%, 1); + --tt-color-yellow-inc-4: hsla(50, 100%, 91%, 1); + --tt-color-yellow-inc-3: hsla(50, 100%, 84%, 1); + --tt-color-yellow-inc-2: hsla(50, 100%, 77%, 1); + --tt-color-yellow-inc-1: hsla(50, 100%, 68%, 1); + --tt-color-yellow-base: hsla(52, 100%, 50%, 1); + --tt-color-yellow-dec-1: hsla(52, 100%, 41%, 1); + --tt-color-yellow-dec-2: hsla(52, 100%, 32%, 1); + --tt-color-yellow-dec-3: hsla(52, 100%, 24%, 1); + --tt-color-yellow-dec-4: hsla(51, 100%, 16%, 1); + --tt-color-yellow-dec-5: hsla(50, 100%, 9%, 1); + + /* Red */ + --tt-color-red-inc-5: hsla(11, 100%, 96%, 1); + --tt-color-red-inc-4: hsla(11, 100%, 88%, 1); + --tt-color-red-inc-3: hsla(10, 100%, 80%, 1); + --tt-color-red-inc-2: hsla(9, 100%, 73%, 1); + --tt-color-red-inc-1: hsla(7, 100%, 64%, 1); + --tt-color-red-base: hsla(7, 100%, 54%, 1); + --tt-color-red-dec-1: hsla(7, 100%, 41%, 1); + --tt-color-red-dec-2: hsla(5, 100%, 32%, 1); + --tt-color-red-dec-3: hsla(4, 100%, 24%, 1); + --tt-color-red-dec-4: hsla(3, 100%, 16%, 1); + --tt-color-red-dec-5: hsla(1, 100%, 9%, 1); + + /* Basic colors */ + --white: rgba(255, 255, 255, 1); + --black: rgba(14, 14, 17, 1); + --transparent: rgba(255, 255, 255, 0); + + /****************** + Shadow variables + ******************/ + + /* Shadows Light */ + --tt-shadow-elevated-md: + 0px 16px 48px 0px rgba(17, 24, 39, 0.04), + 0px 12px 24px 0px rgba(17, 24, 39, 0.04), + 0px 6px 8px 0px rgba(17, 24, 39, 0.02), + 0px 2px 3px 0px rgba(17, 24, 39, 0.02); + + /************************************************** + Radius variables + **************************************************/ + + --tt-radius-xxs: 0.125rem; /* 2px */ + --tt-radius-xs: 0.25rem; /* 4px */ + --tt-radius-sm: 0.375rem; /* 6px */ + --tt-radius-md: 0.5rem; /* 8px */ + --tt-radius-lg: 0.75rem; /* 12px */ + --tt-radius-xl: 1rem; /* 16px */ + + /************************************************** + Transition variables + **************************************************/ + + --tt-transition-duration-short: 0.1s; + --tt-transition-duration-default: 0.2s; + --tt-transition-duration-long: 0.64s; + --tt-transition-easing-default: cubic-bezier(0.46, 0.03, 0.52, 0.96); + --tt-transition-easing-cubic: cubic-bezier(0.65, 0.05, 0.36, 1); + --tt-transition-easing-quart: cubic-bezier(0.77, 0, 0.18, 1); + --tt-transition-easing-circ: cubic-bezier(0.79, 0.14, 0.15, 0.86); + --tt-transition-easing-back: cubic-bezier(0.68, -0.55, 0.27, 1.55); + + /****************** + Contrast variables + ******************/ + + --tt-accent-contrast: 8%; + --tt-destructive-contrast: 8%; + --tt-foreground-contrast: 8%; + + &, + *, + ::before, + ::after { + box-sizing: border-box; + transition: none var(--tt-transition-duration-default) + var(--tt-transition-easing-default); + } +} + +/* Shadows Dark */ +@media (prefers-color-scheme: dark) { + :root { + --tt-shadow-elevated-md: + 0px 16px 48px 0px rgba(0, 0, 0, 0.5), + 0px 12px 24px 0px rgba(0, 0, 0, 0.24), + 0px 6px 8px 0px rgba(0, 0, 0, 0.22), 0px 2px 3px 0px rgba(0, 0, 0, 0.12); + } +} + +:root { + /************************************************** + Global colors + **************************************************/ + + /* Global colors - Light mode */ + --tt-bg-color: var(--white); + --tt-border-color: var(--tt-gray-light-a-200); + --tt-border-color-tint: var(--tt-gray-light-a-100); + --tt-sidebar-bg-color: var(--tt-gray-light-100); + --tt-scrollbar-color: var(--tt-gray-light-a-200); + --tt-cursor-color: var(--tt-brand-color-500); + --tt-selection-color: rgba(157, 138, 255, 0.2); + --tt-card-bg-color: var(--white); + --tt-card-border-color: var(--tt-gray-light-a-100); +} + +/* Global colors - Dark mode */ +.dark { + --tt-bg-color: var(--black); + --tt-border-color: var(--tt-gray-dark-a-200); + --tt-border-color-tint: var(--tt-gray-dark-a-100); + --tt-sidebar-bg-color: var(--tt-gray-dark-100); + --tt-scrollbar-color: var(--tt-gray-dark-a-200); + --tt-cursor-color: var(--tt-brand-color-400); + --tt-selection-color: rgba(122, 82, 255, 0.2); + --tt-card-bg-color: var(--tt-gray-dark-50); + --tt-card-border-color: var(--tt-gray-dark-a-50); +} diff --git a/packages/editor/src/tiptap-utils.ts b/packages/editor/src/tiptap-utils.ts new file mode 100644 index 000000000..305dc63a6 --- /dev/null +++ b/packages/editor/src/tiptap-utils.ts @@ -0,0 +1,74 @@ +import type { Editor } from "@tiptap/react"; + +export const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB + +/** + * Handles image upload with progress tracking and abort capability + */ +export const handleImageUpload = async ( + _file: File, + onProgress?: (event: { progress: number }) => void, + abortSignal?: AbortSignal, +): Promise => { + // Simulate upload progress + for (let progress = 0; progress <= 100; progress += 10) { + if (abortSignal?.aborted) { + throw new Error("Upload cancelled"); + } + await new Promise((resolve) => setTimeout(resolve, 500)); + onProgress?.({ progress }); + } + + return "/images/placeholder-image.png"; + + // Uncomment to use actual file conversion: + // return convertFileToBase64(file, abortSignal) +}; + +/** + * Converts a File to base64 string + */ +const _convertFileToBase64 = ( + file: File, + abortSignal?: AbortSignal, +): Promise => { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + + const abortHandler = () => { + reader.abort(); + reject(new Error("Upload cancelled")); + }; + + if (abortSignal) { + abortSignal.addEventListener("abort", abortHandler); + } + + reader.onloadend = () => { + if (abortSignal) { + abortSignal.removeEventListener("abort", abortHandler); + } + + if (typeof reader.result === "string") { + resolve(reader.result); + } else { + reject(new Error("Failed to convert File to base64")); + } + }; + + reader.onerror = reject; + reader.readAsDataURL(file); + }); +}; + +/** + * Checks if a node exists in the editor schema + * + * @param nodeName - The name of the node to check + * @param editor - The editor instance + */ +export const isNodeInSchema = (nodeName: string, editor: Editor | null) => + editor?.schema.spec.nodes.get(nodeName) !== undefined; + +export const isMarkInSchema = (markName: string, editor: Editor | null) => + editor?.schema.spec.marks.get(markName) !== undefined; diff --git a/packages/editor/tsconfig.json b/packages/editor/tsconfig.json new file mode 100644 index 000000000..59872c7cb --- /dev/null +++ b/packages/editor/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "@rectangular-labs/typescript/tsconfig.internal-package.json", + "compilerOptions": { + "lib": ["ES2022", "dom", "dom.iterable"], + "jsx": "preserve", + "rootDir": "src" + }, + "include": ["src"], + "exclude": ["node_modules"] +} diff --git a/packages/ui/package.json b/packages/ui/package.json index 9e500c661..2dd1cbee8 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -5,12 +5,13 @@ "sideEffects": false, "files": [ "tailwind.config.web.ts", - "globals.css" + "style.css" ], "exports": { "./*": "./src/*.tsx", - "./styles.css": "./src/styles.css", - "./utils/*": "./src/utils/*.ts" + "./style.css": "./src/style.css", + "./utils/*": "./src/utils/*.ts", + "./hooks/*": "./src/hooks/*.ts" }, "scripts": { "clean": "git clean -xdf .turbo node_modules", @@ -33,6 +34,7 @@ "react-dom": "^19.1.0" }, "dependencies": { + "@ark-ui/react": "^5.22.0", "@hookform/resolvers": "^5.2.1", "@radix-ui/react-accordion": "^1.2.12", "@radix-ui/react-alert-dialog": "^1.1.15", diff --git a/packages/ui/src/components/auth/schema/code.ts b/packages/ui/src/components/auth/schema/code.ts index 2d5530fa9..67f5ec106 100644 --- a/packages/ui/src/components/auth/schema/code.ts +++ b/packages/ui/src/components/auth/schema/code.ts @@ -1,7 +1,7 @@ import { type } from "arktype"; export const CodeSchema = type("string").narrow((code, ctx) => { - if (code.length !== 6 || Number.isNaN(parseInt(code))) { + if (code.length !== 6 || Number.isNaN(parseInt(code, 10))) { return ctx.reject({ expected: "a valid code", actual: "" }); } return true; diff --git a/packages/ui/src/components/icon.tsx b/packages/ui/src/components/icon.tsx index a61c6a8f9..4f0b569a4 100644 --- a/packages/ui/src/components/icon.tsx +++ b/packages/ui/src/components/icon.tsx @@ -7,8 +7,10 @@ import { FileIcon, Loader2, MoonIcon, + PaperclipIcon, SunIcon, } from "lucide-react"; + import type { SVGProps } from "react"; type IconProps = React.HTMLAttributes; @@ -18,12 +20,13 @@ export const Sun = SunIcon; export const EyeOn = EyeIcon; export const EyeOff = EyeOffIcon; export const Spinner = Loader2; +export const Paperclip = PaperclipIcon; +export const Dot = DotIcon; +export const File = FileIcon; export const ArrowUp = ArrowUpIcon; export const ArrowDown = ArrowDownIcon; -export const File = FileIcon; -export const Dot = DotIcon; -export const Logo = (props: IconProps) => ( +export const Logo = (props: SVGProps) => ( {children}; } -export function ThemeToggle({ className }: { className?: string }) { +export function ThemeToggle({ className, ...props }: ButtonProps) { const { setTheme, theme } = useTheme(); return ( @@ -20,6 +20,7 @@ export function ThemeToggle({ className }: { className?: string }) { onClick={() => (theme === "dark" ? setTheme("light") : setTheme("dark"))} size="icon" variant="outline" + {...props} > diff --git a/packages/ui/src/components/ui/button.tsx b/packages/ui/src/components/ui/button.tsx index 026f8facd..494ef04e0 100644 --- a/packages/ui/src/components/ui/button.tsx +++ b/packages/ui/src/components/ui/button.tsx @@ -1,8 +1,9 @@ -import { Slot } from "@radix-ui/react-slot"; import { cva, type VariantProps } from "class-variance-authority"; import { Loader2 } from "lucide-react"; import type * as React from "react"; import { cn } from "../../utils/cn"; +import { ShortcutDisplay, type ShortcutKeys } from "./shortcut"; +import { Tooltip, TooltipContent, TooltipTrigger } from "./tooltip"; const buttonVariants = cva( "inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-md font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0", @@ -35,22 +36,27 @@ const buttonVariants = cva( }, ); +interface ButtonProps + extends React.ComponentProps<"button">, + VariantProps { + tooltip?: { + content: string; + shortcutKeys?: ShortcutKeys[]; + }; + isLoading?: boolean; +} + function Button({ className, variant, size, - asChild = false, - isLoading = false, + tooltip, children, + isLoading, ...props -}: React.ComponentProps<"button"> & - VariantProps & { - asChild?: boolean; - isLoading?: boolean; - }) { - const Comp = asChild ? Slot : "button"; - - const finalChildren = isLoading ? ( +}: ButtonProps) { + const isDisabled = isLoading || props.disabled; + const body = isLoading ? (
{children} @@ -59,16 +65,38 @@ function Button({ children ); + if (tooltip) { + return ( + + + {body} + + + {tooltip.content} + {tooltip.shortcutKeys && ( + + )} + + + ); + } + return ( - - {finalChildren} - + {body} + ); } -export { Button, buttonVariants }; +Button.displayName = "Button"; + +export { Button, buttonVariants, type ButtonProps }; diff --git a/packages/ui/src/components/ui/separator.tsx b/packages/ui/src/components/ui/separator.tsx index 614b23fe9..7b3a8f9b5 100644 --- a/packages/ui/src/components/ui/separator.tsx +++ b/packages/ui/src/components/ui/separator.tsx @@ -1,28 +1,34 @@ -"use client"; - -import * as SeparatorPrimitive from "@radix-ui/react-separator"; -import type * as React from "react"; - +import type React from "react"; import { cn } from "../../utils/cn"; -function Separator({ +export interface SeparatorProps extends React.ComponentProps<"div"> { + orientation?: "horizontal" | "vertical"; + decorative?: boolean; +} + +export const Separator = ({ + decorative, + orientation = "vertical", className, - orientation = "horizontal", - decorative = true, - ...props -}: React.ComponentProps) { + ...divProps +}: SeparatorProps) => { + const ariaOrientation = orientation === "vertical" ? orientation : undefined; + const semanticProps = decorative + ? { role: "none" } + : { "aria-orientation": ariaOrientation, role: "separator" }; + return ( - ); -} +}; -export { Separator }; +Separator.displayName = "Separator"; diff --git a/packages/ui/src/components/ui/shortcut.tsx b/packages/ui/src/components/ui/shortcut.tsx new file mode 100644 index 000000000..468cbffec --- /dev/null +++ b/packages/ui/src/components/ui/shortcut.tsx @@ -0,0 +1,124 @@ +import React from "react"; +import { useMemo } from "react"; + +type ShortcutModifier = "ctrl" | "alt" | "shift"; +type ShortcutKey = + | "a" + | "b" + | "c" + | "d" + | "e" + | "f" + | "g" + | "h" + | "i" + | "j" + | "k" + | "l" + | "m" + | "n" + | "o" + | "p" + | "q" + | "r" + | "s" + | "t" + | "u" + | "v" + | "w" + | "x" + | "y" + | "z" + | "0" + | "1" + | "2" + | "3" + | "4" + | "5" + | "6" + | "7" + | "8" + | "9" + | "." + | "," + | "/" + | ";" + | "'" + | "[" + | "]"; +type ShortcutKeys = + | ShortcutKey + | `${ShortcutModifier}-${ShortcutKey}` + | `${ShortcutModifier}-${ShortcutModifier}-${ShortcutKey}`; + +const MAC_SYMBOLS: Record = { + ctrl: "⌘", + alt: "⌥", + shift: "⇧", +}; + +const formatShortcutKey = (key: string, isMac: boolean) => { + if (isMac) { + const lowerKey = key.toLowerCase(); + if (Object.keys(MAC_SYMBOLS).includes(lowerKey)) { + return MAC_SYMBOLS[lowerKey as ShortcutModifier]; + } + return key.toUpperCase(); + } + + return key.charAt(0).toUpperCase() + key.slice(1); +}; + +const parseShortcutKeys = (shortcutKeys: ShortcutKeys, isMac: boolean) => { + return shortcutKeys + .split("-") + .map((key) => key.trim()) + .map((key) => formatShortcutKey(key, isMac)); +}; + +function ShortcutCombo({ shortcutKeys }: { shortcutKeys: string[] }) { + return shortcutKeys.map((shortcut, index) => { + return ( + // biome-ignore lint/suspicious/noArrayIndexKey: We need to use the index as a key to avoid re-rendering the component + + {index > 0 && +} + {shortcut} + + ); + }); +} + +function ShortcutDisplay({ + shortcutCombos, +}: { + shortcutCombos: ShortcutKeys[]; +}) { + const isMac = useMemo( + () => + typeof navigator !== "undefined" && + navigator.platform.toLowerCase().includes("mac"), + [], + ); + + const parsedShortcuts = useMemo( + () => shortcutCombos.map((shortcut) => parseShortcutKeys(shortcut, isMac)), + [shortcutCombos, isMac], + ); + + if (shortcutCombos.length === 0) return null; + + return ( +
+ {parsedShortcuts.map((shortcutCombo, index) => { + return ( + + {index > 0 && then } + + + ); + })} +
+ ); +} + +export { ShortcutDisplay, type ShortcutKeys }; diff --git a/packages/ui/src/components/ui/sidebar.tsx b/packages/ui/src/components/ui/sidebar.tsx index aad29e512..1afa5f73c 100644 --- a/packages/ui/src/components/ui/sidebar.tsx +++ b/packages/ui/src/components/ui/sidebar.tsx @@ -5,7 +5,7 @@ import { cva, type VariantProps } from "class-variance-authority"; import { PanelLeftIcon } from "lucide-react"; import * as React from "react"; -import { useIsMobile } from "../../hooks/use-mobile"; +import { useIsMobile } from "../../hooks/use-is-mobile"; import { cn } from "../../utils/cn"; import { Button } from "./button"; import { Input } from "./input"; @@ -18,12 +18,7 @@ import { SheetTitle, } from "./sheet"; import { Skeleton } from "./skeleton"; -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from "./tooltip"; +import { Tooltip, TooltipContent, TooltipTrigger } from "./tooltip"; const SIDEBAR_COOKIE_NAME = "sidebar_state"; const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7; @@ -129,25 +124,23 @@ function SidebarProvider({ return ( - -
- {children} -
-
+
+ {children} +
); } @@ -536,12 +529,7 @@ function SidebarMenuButton({ return ( {button} - ); } diff --git a/packages/ui/src/components/ui/spacer.tsx b/packages/ui/src/components/ui/spacer.tsx new file mode 100644 index 000000000..5036de6bb --- /dev/null +++ b/packages/ui/src/components/ui/spacer.tsx @@ -0,0 +1,36 @@ +import type * as React from "react"; + +type SpacerOrientation = "horizontal" | "vertical"; + +interface SpacerProps extends React.ComponentPropsWithRef<"div"> { + orientation?: SpacerOrientation; + size?: string | number; +} + +export const Spacer = ({ + orientation = "horizontal", + size, + className = "", + style = {}, + ...props +}: SpacerProps) => { + const computedStyle = { + ...style, + ...(orientation === "horizontal" && !size && { flex: 1 }), + ...(size && { + width: orientation === "vertical" ? "1px" : size, + height: orientation === "horizontal" ? "1px" : size, + }), + }; + + return ( +
+ ); +}; + +Spacer.displayName = "Spacer"; diff --git a/packages/ui/src/components/ui/toggle.tsx b/packages/ui/src/components/ui/toggle.tsx index 3032b7a60..c97c4180a 100644 --- a/packages/ui/src/components/ui/toggle.tsx +++ b/packages/ui/src/components/ui/toggle.tsx @@ -1,13 +1,11 @@ "use client"; - -import * as TogglePrimitive from "@radix-ui/react-toggle"; +import { Toggle as TogglePrimitive } from "@ark-ui/react"; import { cva, type VariantProps } from "class-variance-authority"; import type * as React from "react"; - import { cn } from "../../utils/cn"; const toggleVariants = cva( - "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md font-medium text-sm outline-none transition-[color,box-shadow] hover:bg-muted hover:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0", + "inline-flex items-center justify-center gap-2 rounded-md font-medium text-sm transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", { variants: { variant: { @@ -28,13 +26,11 @@ const toggleVariants = cva( }, ); -function Toggle({ - className, - variant, - size, - ...props -}: React.ComponentProps & - VariantProps) { +interface ToggleProps + extends React.ComponentPropsWithRef, + VariantProps {} + +function Toggle({ size, variant, className, ...props }: ToggleProps) { return ( ) { + return ( + {children} + ); +} + +export { Toggle, type ToggleProps, toggleVariants, ToggleIndicator }; diff --git a/packages/ui/src/components/ui/tooltip.tsx b/packages/ui/src/components/ui/tooltip.tsx index 599fc1505..d236ce5b9 100644 --- a/packages/ui/src/components/ui/tooltip.tsx +++ b/packages/ui/src/components/ui/tooltip.tsx @@ -1,61 +1,55 @@ "use client"; -import * as TooltipPrimitive from "@radix-ui/react-tooltip"; +import { Tooltip as ArkTooltip } from "@ark-ui/react/tooltip"; import type * as React from "react"; import { cn } from "../../utils/cn"; -function TooltipProvider({ - delayDuration = 0, - ...props -}: React.ComponentProps) { +function Tooltip({ ...props }: React.ComponentProps) { return ( - ); } - -function Tooltip({ - ...props -}: React.ComponentProps) { - return ( - - - - ); -} - -function TooltipTrigger({ - ...props -}: React.ComponentProps) { - return ; -} - +const TooltipTrigger = ArkTooltip.Trigger; function TooltipContent({ className, - sideOffset = 0, children, ...props -}: React.ComponentProps) { +}: React.ComponentProps) { return ( - - + + + + {children} - - - + + ); } -export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger }; +export { Tooltip, TooltipContent, TooltipTrigger }; diff --git a/packages/ui/src/hooks/use-mobile.ts b/packages/ui/src/hooks/use-is-mobile.ts similarity index 100% rename from packages/ui/src/hooks/use-mobile.ts rename to packages/ui/src/hooks/use-is-mobile.ts diff --git a/packages/ui/src/hooks/use-window-size.ts b/packages/ui/src/hooks/use-window-size.ts new file mode 100644 index 000000000..35a6181af --- /dev/null +++ b/packages/ui/src/hooks/use-window-size.ts @@ -0,0 +1,41 @@ +import * as React from "react"; + +export function useWindowSize() { + const [windowSize, setWindowSize] = React.useState({ + width: 0, + height: 0, + offsetTop: 0, + }); + + React.useEffect(() => { + const handleResize = () => { + if (typeof window !== "undefined") { + const width = window.visualViewport?.width || 0; + const height = window.visualViewport?.height || 0; + const offsetTop = window.visualViewport?.offsetTop || 0; + + setWindowSize((state) => { + if ( + width === state.width && + height === state.height && + offsetTop === state.offsetTop + ) { + return state; + } + + return { width, height, offsetTop }; + }); + } + }; + + window.visualViewport?.addEventListener("resize", handleResize); + window.visualViewport?.addEventListener("scroll", handleResize); + + return () => { + window.visualViewport?.removeEventListener("resize", handleResize); + window.visualViewport?.removeEventListener("scroll", handleResize); + }; + }, []); + + return windowSize; +} diff --git a/packages/ui/src/styles.css b/packages/ui/src/style.css similarity index 96% rename from packages/ui/src/styles.css rename to packages/ui/src/style.css index 599109ad1..be3c34a39 100644 --- a/packages/ui/src/styles.css +++ b/packages/ui/src/style.css @@ -130,6 +130,7 @@ code { --color-sidebar-ring: var(--sidebar-ring); --animate-accordion-down: accordion-down 0.2s ease-out; --animate-accordion-up: accordion-up 0.2s ease-out; + --animate-typing-dot-bounce: typing-dot-bounce 1.25s ease-out infinite; @keyframes accordion-down { from { @@ -148,6 +149,16 @@ code { height: 0; } } + + @keyframes typing-dot-bounce { + 0%, + 40% { + transform: translateY(0); + } + 20% { + transform: translateY(-0.25rem); + } + } } @layer base { diff --git a/packages/ui/src/utils/type.ts b/packages/ui/src/utils/type.ts new file mode 100644 index 000000000..b76f25cf7 --- /dev/null +++ b/packages/ui/src/utils/type.ts @@ -0,0 +1,4 @@ +// mostly to play nice with exactOptionalPropertyTypes +export type ExplicitUndefined = { + [P in keyof T]-?: T[P] | undefined; +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ae620a823..dd793d399 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22,7 +22,7 @@ importers: version: link:tooling/typescript '@turbo/gen': specifier: ^2.5.6 - version: 2.5.6(@types/node@22.18.0)(typescript@5.9.2) + version: 2.5.6(@swc/core@1.13.5(@swc/helpers@0.5.17))(@swc/wasm@1.13.5)(@types/node@22.18.0)(typescript@5.9.2) turbo: specifier: ^2.5.6 version: 2.5.6 @@ -32,12 +32,21 @@ importers: apps/www: dependencies: + '@ai-sdk/react': + specifier: ^2.0.27 + version: 2.0.27(react@19.1.1)(zod@4.1.4) '@rectangular-labs/api': specifier: workspace:* version: link:../../packages/api '@rectangular-labs/auth': specifier: workspace:* version: link:../../packages/auth + '@rectangular-labs/editor': + specifier: workspace:* + version: link:../../packages/editor + '@rectangular-labs/result': + specifier: workspace:* + version: link:../../packages/result '@rectangular-labs/ui': specifier: workspace:* version: link:../../packages/ui @@ -64,7 +73,7 @@ importers: version: 1.130.17(@tanstack/react-query@5.85.5(react@19.1.1))(@tanstack/react-router@1.131.28(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@tanstack/router-core@1.131.28)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@tanstack/react-start': specifier: ^1.131.27 - version: 1.131.28(@netlify/blobs@9.1.2)(@tanstack/react-router@1.131.28(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@vitejs/plugin-react@5.0.2(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.3)))(aws4fetch@1.0.20)(better-sqlite3@11.9.1)(drizzle-orm@0.44.5(@cloudflare/workers-types@4.20250414.0)(@opentelemetry/api@1.9.0)(@prisma/client@6.6.0(prisma@6.6.0(typescript@5.9.2))(typescript@5.9.2))(@types/pg@8.15.5)(better-sqlite3@11.9.1)(gel@2.0.1)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.5)(prisma@6.6.0(typescript@5.9.2)))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.3))(webpack@5.99.5)(xml2js@0.6.2) + version: 1.131.28(@netlify/blobs@9.1.2)(@tanstack/react-router@1.131.28(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@vitejs/plugin-react@5.0.2(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.3)))(aws4fetch@1.0.20)(better-sqlite3@11.9.1)(drizzle-orm@0.44.5(@cloudflare/workers-types@4.20250414.0)(@opentelemetry/api@1.9.0)(@prisma/client@6.6.0(prisma@6.6.0(typescript@5.9.2))(typescript@5.9.2))(@types/pg@8.15.5)(better-sqlite3@11.9.1)(gel@2.0.1)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.5)(prisma@6.6.0(typescript@5.9.2)))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.3))(webpack@5.99.5(@swc/core@1.13.5(@swc/helpers@0.5.17)))(xml2js@0.6.2) arktype: specifier: ^2.1.20 version: 2.1.20 @@ -111,6 +120,12 @@ importers: vite-plugin-mkcert: specifier: ^1.17.8 version: 1.17.8(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.3)) + vite-plugin-top-level-await: + specifier: ^1.5.0 + version: 1.6.0(@swc/helpers@0.5.17)(rollup@4.49.0)(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.3)) + vite-plugin-wasm: + specifier: ^3.4.1 + version: 3.5.0(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.3)) vite-tsconfig-paths: specifier: ^5.1.4 version: 5.1.4(typescript@5.9.2)(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.3)) @@ -235,6 +250,85 @@ importers: specifier: ^5.9.2 version: 5.9.2 + packages/editor: + dependencies: + '@rectangular-labs/result': + specifier: workspace:* + version: link:../result + '@rectangular-labs/ui': + specifier: workspace:* + version: link:../ui + '@tiptap/extension-highlight': + specifier: ^2.11.7 + version: 2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1)) + '@tiptap/extension-image': + specifier: ^2.11.7 + version: 2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1)) + '@tiptap/extension-link': + specifier: ^2.11.7 + version: 2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))(@tiptap/pm@2.26.1) + '@tiptap/extension-subscript': + specifier: ^2.11.7 + version: 2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1)) + '@tiptap/extension-superscript': + specifier: ^2.11.7 + version: 2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1)) + '@tiptap/extension-task-item': + specifier: ^2.11.7 + version: 2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))(@tiptap/pm@2.26.1) + '@tiptap/extension-task-list': + specifier: ^2.11.7 + version: 2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1)) + '@tiptap/extension-text-align': + specifier: ^2.11.7 + version: 2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1)) + '@tiptap/extension-typography': + specifier: ^2.11.7 + version: 2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1)) + '@tiptap/extension-underline': + specifier: ^2.11.7 + version: 2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1)) + '@tiptap/pm': + specifier: ^2.11.7 + version: 2.26.1 + '@tiptap/react': + specifier: ^2.11.7 + version: 2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))(@tiptap/pm@2.26.1)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@tiptap/starter-kit': + specifier: ^2.11.7 + version: 2.26.1 + loro-crdt: + specifier: ^1.5.4 + version: 1.6.0 + loro-prosemirror: + specifier: ^0.2.1 + version: 0.2.3(loro-crdt@1.6.0)(prosemirror-model@1.25.3)(prosemirror-state@1.4.3)(prosemirror-view@1.40.1) + lucide-react: + specifier: ^0.542.0 + version: 0.542.0(react@19.1.1) + devDependencies: + '@rectangular-labs/typescript': + specifier: workspace:* + version: link:../../tooling/typescript + '@types/react': + specifier: ^19.1.8 + version: 19.1.12 + '@types/react-dom': + specifier: ^19.1.6 + version: 19.1.9(@types/react@19.1.12) + react: + specifier: ^19.1.0 + version: 19.1.1 + react-dom: + specifier: ^19.1.0 + version: 19.1.1(react@19.1.1) + tailwindcss: + specifier: ^4.1.12 + version: 4.1.12 + typescript: + specifier: ^5.9.2 + version: 5.9.2 + packages/result: devDependencies: '@rectangular-labs/typescript': @@ -242,13 +336,16 @@ importers: version: link:../../tooling/typescript tsup: specifier: ^8.5.0 - version: 8.5.0(jiti@2.5.1)(postcss@8.5.6)(tsx@4.20.3)(typescript@5.9.2) + version: 8.5.0(@swc/core@1.13.5(@swc/helpers@0.5.17))(jiti@2.5.1)(postcss@8.5.6)(tsx@4.20.3)(typescript@5.9.2) typescript: specifier: ^5.9.2 version: 5.9.2 packages/ui: dependencies: + '@ark-ui/react': + specifier: ^5.22.0 + version: 5.22.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@hookform/resolvers': specifier: ^5.2.1 version: 5.2.1(react-hook-form@7.62.0(react@19.1.1)) @@ -434,10 +531,42 @@ importers: packages: + '@ai-sdk/gateway@1.0.15': + resolution: {integrity: sha512-xySXoQ29+KbGuGfmDnABx+O6vc7Gj7qugmj1kGpn0rW0rQNn6UKUuvscKMzWyv1Uv05GyC1vqHq8ZhEOLfXscQ==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4 + + '@ai-sdk/provider-utils@3.0.7': + resolution: {integrity: sha512-o3BS5/t8KnBL3ubP8k3w77AByOypLm+pkIL/DCw0qKkhDbvhCy+L3hRTGPikpdb8WHcylAeKsjgwOxhj4cqTUA==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4 + + '@ai-sdk/provider@2.0.0': + resolution: {integrity: sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==} + engines: {node: '>=18'} + + '@ai-sdk/react@2.0.27': + resolution: {integrity: sha512-CGqE6/4NMJ8dzTYoBaqwcgucdhr6ihZzaKwbGjqWc7skmCPWkdPOHCP4tNyuaM7h+dZxG2bRbHif/8B5s41Yyg==} + engines: {node: '>=18'} + peerDependencies: + react: ^18 || ^19 || ^19.0.0-rc + zod: ^3.25.76 || ^4 + peerDependenciesMeta: + zod: + optional: true + '@ampproject/remapping@2.3.0': resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} + '@ark-ui/react@5.22.0': + resolution: {integrity: sha512-cH3xVhKRn0ZsP2Jg2RZAziI38obIfTMC3Q6ZWtWeYL5k9fq6K8sa1XjdJclBRSD0vYYvR1ynHG9ThicWKKANtQ==} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + '@ark/schema@0.46.0': resolution: {integrity: sha512-c2UQdKgP2eqqDArfBqQIJppxJHvNNXuQPeuSPlDML4rjw+f1cu0qAlzOG4b8ujgm9ctIDWwhpyw6gjG5ledIVQ==} @@ -1419,15 +1548,24 @@ packages: '@floating-ui/core@1.6.9': resolution: {integrity: sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==} + '@floating-ui/core@1.7.3': + resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==} + '@floating-ui/dom@1.6.13': resolution: {integrity: sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==} + '@floating-ui/dom@1.7.4': + resolution: {integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==} + '@floating-ui/react-dom@2.1.2': resolution: {integrity: sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==} peerDependencies: react: '>=16.8.0' react-dom: '>=16.8.0' + '@floating-ui/utils@0.2.10': + resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} + '@floating-ui/utils@0.2.9': resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==} @@ -1448,6 +1586,12 @@ packages: '@types/node': optional: true + '@internationalized/date@3.8.2': + resolution: {integrity: sha512-/wENk7CbvLbkUvX1tu0mwq49CVkkWpkXubGel6birjRPyo6uQ4nQpnq5xZu823zRCwwn82zgHrvgF1vZyvmVgA==} + + '@internationalized/number@3.6.4': + resolution: {integrity: sha512-P+/h+RDaiX8EGt3shB9AYM1+QgkvHmJ5rKi4/59k4sg9g58k9rqsRW0WxRO7jCoHyvVbFRRFKmVTdFYdehrxHg==} + '@ioredis/commands@1.3.0': resolution: {integrity: sha512-M/T6Zewn7sDaBQEqIZ8Rb+i9y8qfGmq+5SDFSf9sA2lUZTmdDLVdOiQaeDp+Q4wElZ9HG1GAX5KhDaidp6LQsQ==} @@ -1768,6 +1912,9 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@popperjs/core@2.11.8': + resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} + '@poppinss/colors@4.1.5': resolution: {integrity: sha512-FvdDqtcRCtz6hThExcFOgW0cWX+xwSMWcRuQe5ZEb2m7cVQOAVZOIMt+/v9RxGiD9/OY16qJBXK4CVKWAPalBw==} @@ -2468,6 +2615,9 @@ packages: '@radix-ui/rect@1.1.1': resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} + '@remirror/core-constants@3.0.0': + resolution: {integrity: sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==} + '@rolldown/pluginutils@1.0.0-beta.34': resolution: {integrity: sha512-LyAREkZHP5pMom7c24meKmJCdhf2hEyvam2q0unr3or9ydwDL+DJ8chTF6Av/RFPb3rH8UFBdMzO5MxTZW97oA==} @@ -2534,6 +2684,15 @@ packages: rollup: optional: true + '@rollup/plugin-virtual@3.0.2': + resolution: {integrity: sha512-10monEYsBp3scM4/ND4LNH5Rxvh3e/cVeL3jWTgZ2SrQ+BmUoQcopVQvnaMcOnykb1VkxUFuDAN+0FnpTFRy2A==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + '@rollup/pluginutils@5.2.0': resolution: {integrity: sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw==} engines: {node: '>=14.0.0'} @@ -2783,6 +2942,87 @@ packages: '@standard-schema/utils@0.3.0': resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} + '@swc/core-darwin-arm64@1.13.5': + resolution: {integrity: sha512-lKNv7SujeXvKn16gvQqUQI5DdyY8v7xcoO3k06/FJbHJS90zEwZdQiMNRiqpYw/orU543tPaWgz7cIYWhbopiQ==} + engines: {node: '>=10'} + cpu: [arm64] + os: [darwin] + + '@swc/core-darwin-x64@1.13.5': + resolution: {integrity: sha512-ILd38Fg/w23vHb0yVjlWvQBoE37ZJTdlLHa8LRCFDdX4WKfnVBiblsCU9ar4QTMNdeTBEX9iUF4IrbNWhaF1Ng==} + engines: {node: '>=10'} + cpu: [x64] + os: [darwin] + + '@swc/core-linux-arm-gnueabihf@1.13.5': + resolution: {integrity: sha512-Q6eS3Pt8GLkXxqz9TAw+AUk9HpVJt8Uzm54MvPsqp2yuGmY0/sNaPPNVqctCX9fu/Nu8eaWUen0si6iEiCsazQ==} + engines: {node: '>=10'} + cpu: [arm] + os: [linux] + + '@swc/core-linux-arm64-gnu@1.13.5': + resolution: {integrity: sha512-aNDfeN+9af+y+M2MYfxCzCy/VDq7Z5YIbMqRI739o8Ganz6ST+27kjQFd8Y/57JN/hcnUEa9xqdS3XY7WaVtSw==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + + '@swc/core-linux-arm64-musl@1.13.5': + resolution: {integrity: sha512-9+ZxFN5GJag4CnYnq6apKTnnezpfJhCumyz0504/JbHLo+Ue+ZtJnf3RhyA9W9TINtLE0bC4hKpWi8ZKoETyOQ==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + + '@swc/core-linux-x64-gnu@1.13.5': + resolution: {integrity: sha512-WD530qvHrki8Ywt/PloKUjaRKgstQqNGvmZl54g06kA+hqtSE2FTG9gngXr3UJxYu/cNAjJYiBifm7+w4nbHbA==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + + '@swc/core-linux-x64-musl@1.13.5': + resolution: {integrity: sha512-Luj8y4OFYx4DHNQTWjdIuKTq2f5k6uSXICqx+FSabnXptaOBAbJHNbHT/06JZh6NRUouaf0mYXN0mcsqvkhd7Q==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + + '@swc/core-win32-arm64-msvc@1.13.5': + resolution: {integrity: sha512-cZ6UpumhF9SDJvv4DA2fo9WIzlNFuKSkZpZmPG1c+4PFSEMy5DFOjBSllCvnqihCabzXzpn6ykCwBmHpy31vQw==} + engines: {node: '>=10'} + cpu: [arm64] + os: [win32] + + '@swc/core-win32-ia32-msvc@1.13.5': + resolution: {integrity: sha512-C5Yi/xIikrFUzZcyGj9L3RpKljFvKiDMtyDzPKzlsDrKIw2EYY+bF88gB6oGY5RGmv4DAX8dbnpRAqgFD0FMEw==} + engines: {node: '>=10'} + cpu: [ia32] + os: [win32] + + '@swc/core-win32-x64-msvc@1.13.5': + resolution: {integrity: sha512-YrKdMVxbYmlfybCSbRtrilc6UA8GF5aPmGKBdPvjrarvsmf4i7ZHGCEnLtfOMd3Lwbs2WUZq3WdMbozYeLU93Q==} + engines: {node: '>=10'} + cpu: [x64] + os: [win32] + + '@swc/core@1.13.5': + resolution: {integrity: sha512-WezcBo8a0Dg2rnR82zhwoR6aRNxeTGfK5QCD6TQ+kg3xx/zNT02s/0o+81h/3zhvFSB24NtqEr8FTw88O5W/JQ==} + engines: {node: '>=10'} + peerDependencies: + '@swc/helpers': '>=0.5.17' + peerDependenciesMeta: + '@swc/helpers': + optional: true + + '@swc/counter@0.1.3': + resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} + + '@swc/helpers@0.5.17': + resolution: {integrity: sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==} + + '@swc/types@0.1.24': + resolution: {integrity: sha512-tjTMh3V4vAORHtdTprLlfoMptu1WfTZG9Rsca6yOKyNYsRr+MUXutKmliB17orgSZk5DpnDxs8GUdd/qwYxOng==} + + '@swc/wasm@1.13.5': + resolution: {integrity: sha512-ZBZcxieydxNwgEU9eFAXGMaDb1Xoh+ZkZcUQ27LNJzc2lPSByoL6CSVqnYiaVo+n9JgqbYyHlMq+i7z0wRNTfA==} + '@t3-oss/env-core@0.13.8': resolution: {integrity: sha512-L1inmpzLQyYu4+Q1DyrXsGJYCXbtXjC4cICw1uAKv0ppYPQv656lhZPU91Qd1VS6SO/bou1/q5ufVzBGbNsUpw==} peerDependencies: @@ -3083,6 +3323,189 @@ packages: '@types/react-dom': optional: true + '@tiptap/core@2.26.1': + resolution: {integrity: sha512-fymyd/XZvYiHjBoLt1gxs024xP/LY26d43R1vluYq7AHBL/7DE3ywzy+1GEsGyAv5Je2L0KBhNIR/izbq3Kaqg==} + peerDependencies: + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-blockquote@2.26.1': + resolution: {integrity: sha512-viQ6AHRhjCYYipKK6ZepBzwZpkuMvO9yhRHeUZDvlSOAh8rvsUTSre0y74nu8QRYUt4a44lJJ6BpphJK7bEgYA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-bold@2.26.1': + resolution: {integrity: sha512-zCce9PRuTNhadFir71luLo99HERDpGJ0EEflGm7RN8I1SnNi9gD5ooK42BOIQtejGCJqg3hTPZiYDJC2hXvckQ==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-bubble-menu@2.26.1': + resolution: {integrity: sha512-oHevUcZbTMFOTpdCEo4YEDe044MB4P1ZrWyML8CGe5tnnKdlI9BN03AXpI1mEEa5CA3H1/eEckXx8EiCgYwQ3Q==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-bullet-list@2.26.1': + resolution: {integrity: sha512-HHakuV4ckYCDOnBbne088FvCEP4YICw+wgPBz/V2dfpiFYQ4WzT0LPK9s7OFMCN+ROraoug+1ryN1Z1KdIgujQ==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-code-block@2.26.1': + resolution: {integrity: sha512-/TDDOwONl0qEUc4+B6V9NnWtSjz95eg7/8uCb8Y8iRbGvI9vT4/znRKofFxstvKmW4URu/H74/g0ywV57h0B+A==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-code@2.26.1': + resolution: {integrity: sha512-GU9deB1A/Tr4FMPu71CvlcjGKwRhGYz60wQ8m4aM+ELZcVIcZRa1ebR8bExRIEWnvRztQuyRiCQzw2N0xQJ1QQ==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-document@2.26.1': + resolution: {integrity: sha512-2P2IZp1NRAE+21mRuFBiP3X2WKfZ6kUC23NJKpn8bcOamY3obYqCt0ltGPhE4eR8n8QAl2fI/3jIgjR07dC8ow==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-dropcursor@2.26.1': + resolution: {integrity: sha512-JkDQU2ZYFOuT5mNYb8OiWGwD1HcjbtmX8tLNugQbToECmz9WvVPqJmn7V/q8VGpP81iEECz/IsyRmuf2kSD4uA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-floating-menu@2.26.1': + resolution: {integrity: sha512-OJF+H6qhQogVTMedAGSWuoL1RPe3LZYXONuFCVyzHnvvMpK+BP1vm180E2zDNFnn/DVA+FOrzNGpZW7YjoFH1w==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-gapcursor@2.26.1': + resolution: {integrity: sha512-KOiMZc3PwJS3hR0nSq5d0TJi2jkNZkLZElcT6pCEnhRHzPH6dRMu9GM5Jj798ZRUy0T9UFcKJalFZaDxnmRnpg==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-hard-break@2.26.1': + resolution: {integrity: sha512-d6uStdNKi8kjPlHAyO59M6KGWATNwhLCD7dng0NXfwGndc22fthzIk/6j9F6ltQx30huy5qQram6j3JXwNACoA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-heading@2.26.1': + resolution: {integrity: sha512-KSzL8WZV3pjJG9ke4RaU70+B5UlYR2S6olNt5UCAawM+fi11mobVztiBoC19xtpSVqIXC1AmXOqUgnuSvmE4ZA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-highlight@2.26.1': + resolution: {integrity: sha512-9eW2UqDqeAKSDIiL6SqcPSDCQAdU5qQmRMsJlShOM7Fu1aU71b1ewhUP9YioUCanciR99tqNsk/n3LAe0w5XdA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-history@2.26.1': + resolution: {integrity: sha512-m6YR1gkkauIDo3PRl0gP+7Oc4n5OqDzcjVh6LvWREmZP8nmi94hfseYbqOXUb6RPHIc0JKF02eiRifT4MSd2nw==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-horizontal-rule@2.26.1': + resolution: {integrity: sha512-mT6baqOhs/NakgrAeDeed194E/ZJFGL692H0C7f1N7WDRaWxUu2oR0LrnRqSH5OyPjELkzu6nQnNy0+0tFGHHg==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-image@2.26.1': + resolution: {integrity: sha512-96+MaYBJebQlR/ik5W72GLUfXdEoxFs+6jsoERxbM5qEdhb7TEnodBFtWZOwgDO27kFd6rSNZuW9r5KJNtljEg==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-italic@2.26.1': + resolution: {integrity: sha512-pOs6oU4LyGO89IrYE4jbE8ZYsPwMMIiKkYfXcfeD9NtpGNBnjeVXXF5I9ndY2ANrCAgC8k58C3/powDRf0T2yA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-link@2.26.1': + resolution: {integrity: sha512-7yfum5Jymkue/uOSTQPt2SmkZIdZx7t3QhZLqBU7R9ettkdSCBgEGok6N+scJM1R1Zes+maSckLm0JZw5BKYNA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-list-item@2.26.1': + resolution: {integrity: sha512-quOXckC73Luc3x+Dcm88YAEBW+Crh3x5uvtQOQtn2GEG91AshrvbnhGRiYnfvEN7UhWIS+FYI5liHFcRKSUKrQ==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-ordered-list@2.26.1': + resolution: {integrity: sha512-UHKNRxq6TBnXMGFSq91knD6QaHsyyOwLOsXMzupmKM5Su0s+CRXEjfav3qKlbb9e4m7D7S/a0aPm8nC9KIXNhQ==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-paragraph@2.26.1': + resolution: {integrity: sha512-UezvM9VDRAVJlX1tykgHWSD1g3MKfVMWWZ+Tg+PE4+kizOwoYkRWznVPgCAxjmyHajxpCKRXgqTZkOxjJ9Kjzg==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-strike@2.26.1': + resolution: {integrity: sha512-CkoRH+pAi6MgdCh7K0cVZl4N2uR4pZdabXAnFSoLZRSg6imLvEUmWHfSi1dl3Z7JOvd3a4yZ4NxerQn5MWbJ7g==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-subscript@2.26.1': + resolution: {integrity: sha512-tnXu18nBbTE6PqmkcpoPun5VxElupYacNfl2WkLB/trN3rBJbyDkn0diS8pL0Ku1vPNi2kSfrHq78/PbX0O1iA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-superscript@2.26.1': + resolution: {integrity: sha512-YTUmppwJchqXxE4nf+wTMuZuUU9/9ibg8p73rif6WxldjuH0RGZQRY8ad5Ha1c5clG+60e0nrXthqqLgvWfjtw==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-task-item@2.26.1': + resolution: {integrity: sha512-b7JNeOsBqEd1p2oQ5N6Msz9fr2o73WR1WsYDC0WhECg07Goud2gQEkwWkQaLsvfcwuS746eMJK/nrT2pVEngYA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-task-list@2.26.1': + resolution: {integrity: sha512-xR4LMpMPZ6bpkZNmFvIojmNGtdGKNlKFbpvyIOgs4qhlWskbFQQVevglHjV1R8xJLic5c+byJQaAmQdQudqGng==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-text-align@2.26.1': + resolution: {integrity: sha512-x6mpNGELy2QtSPBoQqNgiXO9PjZoB+O2EAfXA9YRiBDSIRNOrw+7vOVpi+IgzswFmhMNgIYUVfQRud4FHUCNew==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-text-style@2.26.1': + resolution: {integrity: sha512-t9Nc/UkrbCfnSHEUi1gvUQ2ZPzvfdYFT5TExoV2DTiUCkhG6+mecT5bTVFGW3QkPmbToL+nFhGn4ZRMDD0SP3Q==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-text@2.26.1': + resolution: {integrity: sha512-p2n8WVMd/2vckdJlol24acaTDIZAhI7qle5cM75bn01sOEZoFlSw6SwINOULrUCzNJsYb43qrLEibZb4j2LeQw==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-typography@2.26.1': + resolution: {integrity: sha512-1zwKWfy7Tjutert1Vn/unN+98E0JFr5C2jx1xuesAEf4X405cQMb/zNMI44ON3xBG+aXZoTRlJuXNoYodeVSAg==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-underline@2.26.1': + resolution: {integrity: sha512-/fufv41WDMdf0a4xmFAxONoAz08TonJXX6NEoSJmuGKO59M/Y0Pz8DTK1g32Wk44kn7dyScDiPlvvndl+UOv0A==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/pm@2.26.1': + resolution: {integrity: sha512-8aF+mY/vSHbGFqyG663ds84b+vca5Lge3tHdTMTKazxCnhXR9dn2oQJMnZ78YZvdRbkPkMJJHti9h3K7u2UQvw==} + + '@tiptap/react@2.26.1': + resolution: {integrity: sha512-Zxlwzi1iML7aELa+PyysFD2ncVo2mEcjTkhoDok9iTbMGpm1oU8hgR1i6iHrcSNQLfaRiW6M7HNhZZQPKIC9yw==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@tiptap/starter-kit@2.26.1': + resolution: {integrity: sha512-oziMGCds8SVQ3s5dRpBxVdEKZAmO/O//BjZ69mhA3q4vJdR0rnfLb5fTxSeQvHiqB878HBNn76kNaJrHrV35GA==} + '@tootallnate/quickjs-emscripten@0.23.0': resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} @@ -3187,9 +3610,18 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/linkify-it@5.0.0': + resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} + + '@types/markdown-it@14.1.2': + resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==} + '@types/mdast@4.0.4': resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + '@types/mdurl@2.0.0': + resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} + '@types/minimatch@5.1.2': resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==} @@ -3234,6 +3666,9 @@ packages: '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + '@types/use-sync-external-store@0.0.6': + resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} + '@types/yauzl@2.10.3': resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} @@ -3392,6 +3827,225 @@ packages: '@xtuc/long@4.2.2': resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} + '@zag-js/accordion@1.22.1': + resolution: {integrity: sha512-P3jsauxnAGKBhuqs9gdivjEiSu7N7KnKRlgWlIpyti35askz8swHsqxsfkc2ASs9tcPKnPvuZDHIxXmJmZSLuQ==} + + '@zag-js/anatomy@1.22.1': + resolution: {integrity: sha512-I5OvOuJBt6hEqbpqVkWCOEoDfGMnKuLx+S0h7Un5SyAwnif3F1dSqDYujU28bCy8FtKs36vsq/izxufXyiXSEg==} + + '@zag-js/angle-slider@1.22.1': + resolution: {integrity: sha512-Nitjwwo2NVUEK+PabDnOfqizErnFIZZKThtcpQikAhE1J4MX3H128MANu1hJXNkvVYXyZmhTvzjt6XZc2j7YyQ==} + + '@zag-js/aria-hidden@1.22.1': + resolution: {integrity: sha512-vPfAE35BfYPS1UbYRcNw8/kMl7uayE7LyRncK/gPMnoQMjmEKW0nXmD5WlCHFLdGX9WFGYTIde8k4U8ay+oqcg==} + + '@zag-js/async-list@1.22.1': + resolution: {integrity: sha512-/evBfhDW3Rj3An5fHW8SYINM/pkxeOe/Uk7rRlBreHVn2PdAay4sj1gax4hlUUFEbqyvBgbHpR/atwfdxSuWYQ==} + + '@zag-js/auto-resize@1.22.1': + resolution: {integrity: sha512-O+tKmqwLko74DCmwdouxBZqEtIQB6Rt2pyXdlyBXLB7UnYXEIvEUzf8XK39I5AHXp6NlLqx77GtLn1qiBtKrkQ==} + + '@zag-js/avatar@1.22.1': + resolution: {integrity: sha512-SAz9XaFD8jg4LODkS51s6KrNcYF/PvAcRkCE9TDiuiCeFdgB6+JFKBNk0iM9og8Tk4Doe/3qIA/I12qKNW9pAw==} + + '@zag-js/carousel@1.22.1': + resolution: {integrity: sha512-bFbCRe5xarBtD3NnozHmCmrGJ+nLRhqLQFq+RG13fl1hlhUJaJ5AsS7e8L1r2ZLdbVVrsB0lUuW/ocfJ/G4MSw==} + + '@zag-js/checkbox@1.22.1': + resolution: {integrity: sha512-A/cZb89Aeb2k/KGl3ITS2fuLBXwq6Rnq9aFirfKs/UHrY16fopRbRjfqOxF6wm8lWoFk3gqmRGgybo8qsIfxog==} + + '@zag-js/clipboard@1.22.1': + resolution: {integrity: sha512-rKTPRKvLtcJ1c/CDvnWDRpqAteFS20UQe+mQpO83ACMCRZAfkXP3UOzBL53mh59+LIVlDxgZbMlwRiNiqqKhmA==} + + '@zag-js/collapsible@1.22.1': + resolution: {integrity: sha512-vKfDe/fzm3ndDfaueqW/XgGaWCHVD8MuLFtRRyv3jX3ubdNYn5R/j7ftQURdYyqRlPI3Si50FWSAtOqtvs4y9Q==} + + '@zag-js/collection@1.22.1': + resolution: {integrity: sha512-jjeSKALTH3iK2vTI6uAh2NCtS9n+e2r1cGERKCfNkbt86U6VSp9xiXqalUsEI4ovNIPcgg0+/nzixoVwFO1Vgg==} + + '@zag-js/color-picker@1.22.1': + resolution: {integrity: sha512-vUx8Ef0CZ/VPARIPh2ur76HH1AL3FVObNgtX64kPNUDUI+Z/L/q6CBfIeGcElVQ/Y6QowrqAXjVyPGArmmohmw==} + + '@zag-js/color-utils@1.22.1': + resolution: {integrity: sha512-Bee1KvYOV0yWQbODN+O2zPmdUaH+rymEmIHLfKNipPo5GVmxWqAe8oTQDyquzsUtoPE5MFgW5avg8tgSlCFcBA==} + + '@zag-js/combobox@1.22.1': + resolution: {integrity: sha512-N4tGTmezfHGaKB0+aDB5yMuVzBv2ShgsAx1uizom6ElcvlYD2rsQTr3xLc4wyOR7fx0z6fFDo1+63/Dt3y0t4A==} + + '@zag-js/core@1.22.1': + resolution: {integrity: sha512-4BNrwO9Tadq2Z0d2xSSQs4O/o3OarEHzXM2FQqx46vrwSE57qUghnZex429ZQ51fuk8AL5Lowt26a9JxE9sVPg==} + + '@zag-js/date-picker@1.22.1': + resolution: {integrity: sha512-ja482LloO7AGfFYXTfGV+qV484QWUM1cnF3hWtROd4Vdx/NONwn0w7TEJH+XbO3HaoUC5XpeacWLFQugGCsRjg==} + peerDependencies: + '@internationalized/date': '>=3.0.0' + + '@zag-js/date-utils@1.22.1': + resolution: {integrity: sha512-OWIWxihfFFyQDEaA35a/Fdfp3+GyGUgTUbutMD3BrbnPjKNLm0RyvAgZiq0zPTY7CzpYRbZ2J98GDU+CTERCjA==} + peerDependencies: + '@internationalized/date': '>=3.0.0' + + '@zag-js/dialog@1.22.1': + resolution: {integrity: sha512-b5KwMPYKc9RenZwxrAAHu6aHPz7tqPy4Mxa/YR5zo1pXBV4amA7u2xnqyncRaK65Z7y5QKmpmDuBp+0PnXxNIA==} + + '@zag-js/dismissable@1.22.1': + resolution: {integrity: sha512-0DzbykJu9QoXYw4Zcjte69Mtk6ThNRCXWxxCKBf930V8Bw3Ha7vfY5bgdb4RFT5K+BQP3E8vLT+PzIaDINn2Xw==} + + '@zag-js/dom-query@1.22.1': + resolution: {integrity: sha512-mtvGj2z3rkl40mkjd+QwoOHvxqpiOkY4mtVjzNzgzcbVtUN63Mz7giW8OZB+KLy37hwFX0B8JfiQncU8IOHNpw==} + + '@zag-js/editable@1.22.1': + resolution: {integrity: sha512-NY7VeKYuNLQzi+yZYmWliif0Qd/2PTKtDeqtnVypv8XSHqTbVeS2N9dqTru1g4RP+eGQWx0za12hjmCVU4DuMQ==} + + '@zag-js/file-upload@1.22.1': + resolution: {integrity: sha512-4iKpqxVLafLbQejcPoZcygtNURsezIlWRigHvVPd2pLsXPa8erbdcEZ8X4QvGp77xcW2QTkuSxB+BSCrEEAotA==} + + '@zag-js/file-utils@1.22.1': + resolution: {integrity: sha512-cZAJ5MAZCe7IfHfN+3xSNb9e6mA812U8BPJr/jNPN+qLQh/PkQDwKaGM33o2Me50r18iGTAswEkETnaFZt3wkw==} + + '@zag-js/floating-panel@1.22.1': + resolution: {integrity: sha512-YGjLoYt2xSk4pkTgsR0z/7U7V5OdaicSOZa0HDtskH4MkKPxQxrgf2G4e8dNsw8hnQwfVuoc0RGPGW0BArVr6A==} + + '@zag-js/focus-trap@1.22.1': + resolution: {integrity: sha512-6W9cG0LEVICt0srVfWSpamKzsnRxXMdl3gV+GQ5HvkCCk1Sw6Io4tc3QvSSvaWcfyhM07feerOsa2ah7qiT/ig==} + + '@zag-js/focus-visible@1.22.1': + resolution: {integrity: sha512-TuBEux3UTivo9VXPPe79q9JfTwaP/uIshL1KPifg51ofGYesWjMGeE5S5MAuaSzUmH9+3CpnwP7h7f65s3D0kw==} + + '@zag-js/highlight-word@1.22.1': + resolution: {integrity: sha512-mcPg4/ED3MNDzj5b3t4EEIKkvdyvVUJ9pqbyRUoj76KI+ZWXXJIw5PNAkG5vUVVUXKKjfzPVninIqWv1Bh9Bvg==} + + '@zag-js/hover-card@1.22.1': + resolution: {integrity: sha512-sGcWASPrt0f8oOpBdyDyka0Mkya4TdlBEOvB9qOvnkcIX2bc6YFUtWQN1L1M/K6nv8D0wSZK0p18JBaqGlHmBQ==} + + '@zag-js/i18n-utils@1.22.1': + resolution: {integrity: sha512-45KUYB9tu1br6NmgtaNW9NviozYCYUxJ8aZTI/Y6vKotXK/Pn3bIlaiOaq4Zel7TalGYT8gVnwgPe2E6H5sqTg==} + + '@zag-js/interact-outside@1.22.1': + resolution: {integrity: sha512-+iZ3xHC9+jVo2FCC4B9c9ntcXv19shVOqQGDr2cD30Hwmwtm9kCOdVydMqv3Lp3UhR8a105MXEVUAKg53WbCoA==} + + '@zag-js/json-tree-utils@1.22.1': + resolution: {integrity: sha512-z/15CTtXJHGUvecAAlPnUAaAK83Wxh5WlW9qEpgXlXdB5k7gnWVzH4qN9vDwlSShyZgqaFVqn+muxqaCTYv8Zg==} + + '@zag-js/listbox@1.22.1': + resolution: {integrity: sha512-M017Oq0s9PRR5ZwlJkmLhQHucEta/DZ5eHl/t+9yQqHnYRwWKo2ZXLyXquC1wihbHk81E0a1veDw8vBYpfRovA==} + + '@zag-js/live-region@1.22.1': + resolution: {integrity: sha512-xjrlCbcgIw+iXxSXnjXAv+WX9r/bMwp4HOIxWOD99360XvatQ2ZGhLH9lfixiXeHLvm6hjWsP92MjYefSLDFSA==} + + '@zag-js/menu@1.22.1': + resolution: {integrity: sha512-a5pgQgcpVTVyY6JM8k1WGqelHVKSPwV2CwOv2oGjHWXIr2fpRCAKqZRtytE5PvUP/CZArk8bCjatmgOWe1RdPQ==} + + '@zag-js/number-input@1.22.1': + resolution: {integrity: sha512-E4DROYvSo5TFJMkSmnq+f75wSTL/N7SK6MR8ssNlA2oQp69iVWXhIlFLe4knekX02QJzK1MF97aVU332kAYTeQ==} + + '@zag-js/pagination@1.22.1': + resolution: {integrity: sha512-Jeix+sXcfMPm5jer2W4PHSUCgu9a11aC/AOBk6dkxbX8XL23fYXJu5YyOVVq0iQIDWzX4Uij1N/vBha64ARmcA==} + + '@zag-js/password-input@1.22.1': + resolution: {integrity: sha512-EcCH0V2tbJbexy62nVDUXCMg/XVEcd0PGcBgUfziyaLlDnJz2HWkfe0MzpEiidJwfJfhvvf2DapX9mAyqzZhhw==} + + '@zag-js/pin-input@1.22.1': + resolution: {integrity: sha512-tyI5mVi+zmsDEVuZZTOA7fVyxxGwmD8A2snF6nRkFK11o5xnnZaXt44Z7XrPeljTMSLKt+rdF0y/9Q05Auc4tg==} + + '@zag-js/popover@1.22.1': + resolution: {integrity: sha512-27VVkhaEOtiHJYj2j++AzYlAzpMcW0ED05TV9wIT1q0EYzASWxweSBajbnCiQf9TIYzCImDiNVDaCMl5D+TamQ==} + + '@zag-js/popper@1.22.1': + resolution: {integrity: sha512-vBI5WpvE/3ugsimjZaNisOwcECiYfzc+3LIJwaU8od62kInZ1XF6m096BvV7JGwP0FjkMPJrgjcv7weDtY2iDQ==} + + '@zag-js/presence@1.22.1': + resolution: {integrity: sha512-9+pkKnjcHbNxk/80HzLdDjpiKGV/I208wAe0Njmej6q6Z79ED6cb7tXiOgAS7w/ZLWxwQW7B9oMJ3guVflBHwQ==} + + '@zag-js/progress@1.22.1': + resolution: {integrity: sha512-2U1IJLb1mhBLEgac8x8qaEv3qgr+pHdw6pn9mCCJVBcyFaSqliWps6X+vi+qKokFLrpjCjdAKuuf48ItNfFFcw==} + + '@zag-js/qr-code@1.22.1': + resolution: {integrity: sha512-HIRlNsPNcp5buiTZx7DrX/gCtouGAH4VJc8Q6HBUkaBbiiijVEuYN0aNAjZIdm2pDtrh4KaYjMPuIH8IrV554Q==} + + '@zag-js/radio-group@1.22.1': + resolution: {integrity: sha512-eqvY1y/Ui4nQOU8XE9tGShOCbI/YdSHFeH/tDJe2Yy+1kqO4bENxFJ3R1P097KusJgeb2SYzhID27whUslOq7g==} + + '@zag-js/rating-group@1.22.1': + resolution: {integrity: sha512-QxBK+hpfkQ4yFHUr1YOSwEQ3LuTrdS32J9zV8UyHu8HbgwzfR7L8ZAa1PUUmG65tupzua2pbn1NioOkMvDmBOQ==} + + '@zag-js/react@1.22.1': + resolution: {integrity: sha512-TcIKkNo9EFel+d92nb7104voKJNDiMkqq9nn7Ozq/TE8A62JPf5zk8y8zqoxTbGDTTk+tDjW7Sm1IKb4r6rX4w==} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + '@zag-js/rect-utils@1.22.1': + resolution: {integrity: sha512-jtI03SR9kF0AcBffoFI/TKXn5KyhjNCtsGlqbWw0dKbhWTNy1v432FDC5opmmnH8W5LjjWebIzo4QtO5+632QQ==} + + '@zag-js/remove-scroll@1.22.1': + resolution: {integrity: sha512-2TrS8ljp8SADX5xRB/+KGBCBYbYTeH0k5IEalG2rt8ReNyNAW1JfCrm53KCVoCg9YmxKF3MrxPgPT83MNFsJhQ==} + + '@zag-js/scroll-area@1.22.1': + resolution: {integrity: sha512-BuWKGR3n1yMktYqfTx+U9iwpXkJJhDXW4yin7u/lLMAE0DXR4byyo8aollCkuzZdZbK7NmUG2zVQHUMZ1QaR6w==} + + '@zag-js/scroll-snap@1.22.1': + resolution: {integrity: sha512-kctqJiteALaavoHEpYBDSPgUErIdwAoY5jcrU4Mq5L8FjtI4tSNr8BWcXzSBK2UVqaKN+vDo+PDcj7XIXTUQJA==} + + '@zag-js/select@1.22.1': + resolution: {integrity: sha512-sWq0RqlJvmj0heJDpfS3OfM1ynSSCW+fYY5v3T/QyH4qneqB8OJjgh8EEBaHlOkbqv/oBsk855U8/o6jegfUxw==} + + '@zag-js/signature-pad@1.22.1': + resolution: {integrity: sha512-iD8tBCHSmRI6kdtHO8dNRZrfjGTxfWgweLlNXKu5JV2JkzPBhDCxpthHI9k8LJ0cgUM5/EW4HdEpjO9h47FsaA==} + + '@zag-js/slider@1.22.1': + resolution: {integrity: sha512-aricrX99r21RAS9TyPNTJL8gE8mNRSQMy7TIXTa9aoeRjN0Cf6+PSksKfmPdP9l249/nplGqvC25Ck7XUVJn6A==} + + '@zag-js/splitter@1.22.1': + resolution: {integrity: sha512-ZMuFlVvqO2WYD7AECEB51iiFpN7A30Q28NfkIVR98xugwUX1OJq1IizKRSbLgC/LmseHPp3OvotxjZX6FqkK4Q==} + + '@zag-js/steps@1.22.1': + resolution: {integrity: sha512-eJCHbHG9aGAbzb/IQCqpmk6fmwSmIfocAxNKVTljroD6OHkBtqgaZQVS3q4xyjz61nB/d/0ZlsvpCVjm1EhwBw==} + + '@zag-js/store@1.22.1': + resolution: {integrity: sha512-KrMWi/Fa4cqOjx2zDSMIu6vztFYik+V3K6VPWRVONM4FkboLpTqAEayzwgTTNqMK9iYYZIYjhiPhAVLW9iLuBg==} + + '@zag-js/switch@1.22.1': + resolution: {integrity: sha512-ipmBHEqtcrPYr5WS5Juj5dt4GFIqr81NYVNe8RHMW8jIHgHhRCRj3TokGXVlZ7HdseCKTTNNrcvRFBr1sJBbOw==} + + '@zag-js/tabs@1.22.1': + resolution: {integrity: sha512-B0WHW36uuR+pu/24X0yI4eyvSwo7WmqOc5C3ohZHOf03zkmMJdtMtVQSotKr7qhGMt5updCgs68MR7jAmmc1Lw==} + + '@zag-js/tags-input@1.22.1': + resolution: {integrity: sha512-/56pCeSIW+g+ish3Gjed7iNcPSbQEsBCBsCn6FU/JfjwyhLM0sAtn1vkE/eR92hvDX3klV12XzEMBGe4Egr3GQ==} + + '@zag-js/time-picker@1.22.1': + resolution: {integrity: sha512-7fqCtyDbuaelffLZ8q9infns+HQKqFMjL4k2V5zALAWdYu2NzvlMYHgj2Ue9AI4VI5QaE1nnwV6hxwS4Zpglvg==} + peerDependencies: + '@internationalized/date': '>=3.0.0' + + '@zag-js/timer@1.22.1': + resolution: {integrity: sha512-VmXnXjecuF4tXFdBRuMHxO8mQX3/vxagE4vx0M0gKwbGoGrXnhYGvULiPL3RlJj8OR8pIfYuP2lbCrt8XM625A==} + + '@zag-js/toast@1.22.1': + resolution: {integrity: sha512-cxcfbMftA//ggOAlxG3q04WZVL/mMVklvtQ2rSyj3oRmnwocJPYXtJzKIRazWBjji3u3BOA+ZeOI1AcGrfp/TQ==} + + '@zag-js/toggle-group@1.22.1': + resolution: {integrity: sha512-StxnGsPwzB60pGHTD7sNOqIMXjEPMl3lYQk0i2F5MIQWlTRkYdp4ivh73xBRYVtqK15gqacuWXw87EDzKcNwcA==} + + '@zag-js/toggle@1.22.1': + resolution: {integrity: sha512-KK9VK8ZkA/ep7KxQFaeVE/zHVm90fkp9q6q4inyQkUdURUg0vovTFI3c5q/c1zm9/g51vbNf5qCXWU4m9sQK8A==} + + '@zag-js/tooltip@1.22.1': + resolution: {integrity: sha512-0ub0p22CzYnaXv0prAnWNjqUBkdw4nO4yGk5qntaodajpLNQ4gSdq7Hj4afHzJqwbKAkwb3KzJFqcqIm9Y/dfw==} + + '@zag-js/tour@1.22.1': + resolution: {integrity: sha512-VhHC65NgBaCjlVsw1M4Me0P6PCtmD9oi9gRzN2fEUESdpM/QT5Yw6PAAPP1AEo5okv+V2rRBgSKOu9ZyYHa+IQ==} + + '@zag-js/tree-view@1.22.1': + resolution: {integrity: sha512-AQmOn1mB+nLJEaq0xdSVnTI8Vt3nB3OweqdB12jkbdIOcWI9eY0RfhiNHC0k0mgAw+dMjyn84op/gOd9VVdtmA==} + + '@zag-js/types@1.22.1': + resolution: {integrity: sha512-lvpDSMR96e7H7TdwOiVpMzj6css5Ydix1nBi7BlmjME6v5OPR0KZwVDGD6h5UtTeVjPq8dPaqM8TJWw+QwbQSw==} + + '@zag-js/utils@1.22.1': + resolution: {integrity: sha512-VXY4gjHaTENHW+wjnKKENZ2jcaW0vnG2a5lYEMuZR4dpNCKH217yFr/bCNrI44y2s1W3LWhWmpEjfZluP6udYg==} + abbrev@3.0.1: resolution: {integrity: sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==} engines: {node: ^18.17.0 || >=20.5.0} @@ -3427,6 +4081,12 @@ packages: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} + ai@5.0.27: + resolution: {integrity: sha512-V7I9Rvrap5+3ozAjOrETA5Mv9Z1LmQobyY13U88IkFRahFp0xrEwjvYTwjQa4q5lPgLxwKgbIZRLnZSbUQwnUg==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4 + ajv-formats@2.1.1: resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} peerDependencies: @@ -3923,6 +4583,9 @@ packages: create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + crelt@1.0.6: + resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} + cron-parser@4.9.0: resolution: {integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==} engines: {node: '>=12.0.0'} @@ -4457,6 +5120,10 @@ packages: resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} engines: {node: '>=0.8.0'} + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + escape-string-regexp@5.0.0: resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} engines: {node: '>=12'} @@ -4519,6 +5186,10 @@ packages: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} + eventsource-parser@3.0.5: + resolution: {integrity: sha512-bSRG85ZrMdmWtm7qkF9He9TNRzc/Bm99gEJMaQoHJ9E6Kv9QBbsldh2oMj7iXmYNEAVvNgvv5vPorG6W+XtBhQ==} + engines: {node: '>=20.0.0'} + execa@5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} @@ -5155,6 +5826,9 @@ packages: resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==} engines: {node: '>=16'} + isomorphic.js@0.2.5: + resolution: {integrity: sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==} + jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} @@ -5210,6 +5884,9 @@ packages: json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + json-schema@0.4.0: + resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} + json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} @@ -5256,6 +5933,11 @@ packages: resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} engines: {node: '>= 0.6.3'} + lib0@0.2.114: + resolution: {integrity: sha512-gcxmNFzA4hv8UYi8j43uPlQ7CGcyMJ2KQb5kZASw6SnAKAf10hK12i2fjrS3Cl/ugZa5Ui6WwIu1/6MIXiHttQ==} + engines: {node: '>=16'} + hasBin: true + libphonenumber-js@1.12.14: resolution: {integrity: sha512-HBAMAV7f3yGYy7ZZN5FxQ1tXJTwC77G5/96Yn/SH/HPyKX2EMLGFuCIYUmdLU7CxxJlQcvJymP/PGLzyapurhQ==} @@ -5330,6 +6012,12 @@ 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==} + + linkifyjs@4.3.2: + resolution: {integrity: sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==} + listhen@1.9.0: resolution: {integrity: sha512-I8oW2+QL5KJo8zXNWX046M134WchxsXC7SawLPvRQpogCbkyQIaFxPE89A2HiwR7vAK2Dm2ERBAmyjTYGYEpBg==} hasBin: true @@ -5398,6 +6086,17 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + loro-crdt@1.6.0: + resolution: {integrity: sha512-Jdtjo3GITCncKxFU9LbK1ykGwZ4zOmlhPtJFA1vzRYuy3tYpuu/gU6rXx0nZ3pSuDNv0ZklS+7mm1cSEjouOqQ==} + + loro-prosemirror@0.2.3: + resolution: {integrity: sha512-H2i0aTdAJHl5FpwIM9wM+OXt7WdriYiJMFTTLMck/2+tm5MSGvtFt9Hd8X7mh7JVvuhQc7acfWl+AZnjHdWvxg==} + peerDependencies: + loro-crdt: ^1.4.0 + prosemirror-model: ^1.18.1 + prosemirror-state: ^1.4.1 + prosemirror-view: ^1.28.0 + loupe@3.1.3: resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==} @@ -5442,6 +6141,10 @@ packages: make-error@1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + markdown-it@14.1.0: + resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==} + hasBin: true + markdown-table@3.0.4: resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} @@ -5499,6 +6202,9 @@ packages: mdast-util-to-string@4.0.0: resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + mdurl@2.0.0: + resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} + merge-options@3.0.4: resolution: {integrity: sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==} engines: {node: '>=10'} @@ -5907,6 +6613,9 @@ packages: resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} engines: {node: '>=10'} + orderedmap@2.1.1: + resolution: {integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==} + os-tmpdir@1.0.2: resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} engines: {node: '>=0.10.0'} @@ -6063,6 +6772,9 @@ packages: perfect-debounce@1.0.0: resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} + perfect-freehand@1.2.2: + resolution: {integrity: sha512-eh31l019WICQ03pkF3FSzHxB8n07ItqIQ++G5UV8JX0zVOXzgTGCqnRR0jJ2h9U8/2uW4W4mtGJELt9kEV0CFQ==} + pg-cloudflare@1.2.7: resolution: {integrity: sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==} @@ -6235,16 +6947,84 @@ packages: property-information@7.0.0: resolution: {integrity: sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg==} + prosemirror-changeset@2.3.1: + resolution: {integrity: sha512-j0kORIBm8ayJNl3zQvD1TTPHJX3g042et6y/KQhZhnPrruO8exkTgG8X+NRpj7kIyMMEx74Xb3DyMIBtO0IKkQ==} + + prosemirror-collab@1.3.1: + resolution: {integrity: sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==} + + prosemirror-commands@1.7.1: + resolution: {integrity: sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==} + + prosemirror-dropcursor@1.8.2: + resolution: {integrity: sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==} + + prosemirror-gapcursor@1.3.2: + resolution: {integrity: sha512-wtjswVBd2vaQRrnYZaBCbyDqr232Ed4p2QPtRIUK5FuqHYKGWkEwl08oQM4Tw7DOR0FsasARV5uJFvMZWxdNxQ==} + + prosemirror-history@1.4.1: + resolution: {integrity: sha512-2JZD8z2JviJrboD9cPuX/Sv/1ChFng+xh2tChQ2X4bB2HeK+rra/bmJ3xGntCcjhOqIzSDG6Id7e8RJ9QPXLEQ==} + + prosemirror-inputrules@1.5.0: + resolution: {integrity: sha512-K0xJRCmt+uSw7xesnHmcn72yBGTbY45vm8gXI4LZXbx2Z0jwh5aF9xrGQgrVPu0WbyFVFF3E/o9VhJYz6SQWnA==} + + prosemirror-keymap@1.2.3: + resolution: {integrity: sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==} + + prosemirror-markdown@1.13.2: + resolution: {integrity: sha512-FPD9rHPdA9fqzNmIIDhhnYQ6WgNoSWX9StUZ8LEKapaXU9i6XgykaHKhp6XMyXlOWetmaFgGDS/nu/w9/vUc5g==} + + prosemirror-menu@1.2.5: + resolution: {integrity: sha512-qwXzynnpBIeg1D7BAtjOusR+81xCp53j7iWu/IargiRZqRjGIlQuu1f3jFi+ehrHhWMLoyOQTSRx/IWZJqOYtQ==} + + prosemirror-model@1.25.3: + resolution: {integrity: sha512-dY2HdaNXlARknJbrManZ1WyUtos+AP97AmvqdOQtWtrrC5g4mohVX5DTi9rXNFSk09eczLq9GuNTtq3EfMeMGA==} + + prosemirror-schema-basic@1.2.4: + resolution: {integrity: sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==} + + prosemirror-schema-list@1.5.1: + resolution: {integrity: sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==} + + prosemirror-state@1.4.3: + resolution: {integrity: sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q==} + + prosemirror-tables@1.8.1: + resolution: {integrity: sha512-DAgDoUYHCcc6tOGpLVPSU1k84kCUWTWnfWX3UDy2Delv4ryH0KqTD6RBI6k4yi9j9I8gl3j8MkPpRD/vWPZbug==} + + prosemirror-trailing-node@3.0.0: + resolution: {integrity: sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==} + peerDependencies: + prosemirror-model: ^1.22.1 + prosemirror-state: ^1.4.2 + prosemirror-view: ^1.33.8 + + prosemirror-transform@1.10.4: + resolution: {integrity: sha512-pwDy22nAnGqNR1feOQKHxoFkkUtepoFAd3r2hbEDsnf4wp57kKA36hXsB3njA9FtONBEwSDnDeCiJe+ItD+ykw==} + + prosemirror-view@1.40.1: + resolution: {integrity: sha512-pbwUjt3G7TlsQQHDiYSupWBhJswpLVB09xXm1YiJPdkjkh9Pe7Y51XdLh5VWIZmROLY8UpUpG03lkdhm9lzIBA==} + proxy-agent@6.5.0: resolution: {integrity: sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==} engines: {node: '>= 14'} + proxy-compare@3.0.1: + resolution: {integrity: sha512-V9plBAt3qjMlS1+nC8771KNf6oJ12gExvaxnNzN/9yVRLdTv/lc+oJlnSzrdYDAvBfTStPCoiaCOTmTs0adv7Q==} + proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + proxy-memoize@3.0.1: + resolution: {integrity: sha512-VDdG/VYtOgdGkWJx7y0o7p+zArSf2383Isci8C+BP3YXgMYDoPd3cCBjw0JdWb6YBb9sFiOPbAADDVTPJnh+9g==} + pump@3.0.2: resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==} + punycode.js@2.3.1: + resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} + engines: {node: '>=6'} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -6540,6 +7320,9 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + rope-sequence@1.3.4: + resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==} + rou3@0.5.1: resolution: {integrity: sha512-OXMmJ3zRk2xeXFGfA3K+EOPHC5u7RDFG7lIOx0X1pdnhUkI8MdVrbV+sNsD80ElpUZ+MRHdyxPnFthq9VHs8uQ==} @@ -6748,6 +7531,7 @@ packages: source-map@0.8.0-beta.0: resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} engines: {node: '>= 8'} + deprecated: The work that was done in this beta branch won't be included in future versions space-separated-tokens@2.0.2: resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} @@ -6878,6 +7662,11 @@ packages: swap-case@1.1.2: resolution: {integrity: sha512-BAmWG6/bx8syfc6qXPprof3Mn5vQgf5dwdUNJhsNqU9WdPt5P+ES/wQ5bxfijy8zwZgZZHslC3iAsxsuQMCzJQ==} + swr@2.3.6: + resolution: {integrity: sha512-wfHRmHWk/isGNMwlLGlZX5Gzz/uTgo0o2IRuTMcf4CPuPFJZlq0rDaKUx+ozB5nBOReNV1kiOyzMfj+MBMikLw==} + peerDependencies: + react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} @@ -6952,6 +7741,10 @@ packages: thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + throttleit@2.1.0: + resolution: {integrity: sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==} + engines: {node: '>=18'} + through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} @@ -6996,6 +7789,9 @@ packages: resolution: {integrity: sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==} engines: {node: '>=14.0.0'} + tippy.js@6.3.7: + resolution: {integrity: sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==} + title-case@2.1.1: resolution: {integrity: sha512-EkJoZ2O3zdCz3zJsYCsxyq2OC5hrxR9mfdd5I+w8h/tmFfeOxJ+vvkxsKxdmN0WtS9zLdHEgfgVOiMVgv+Po4Q==} @@ -7169,6 +7965,9 @@ packages: engines: {node: '>=14.17'} hasBin: true + uc.micro@2.1.0: + resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} + ufo@1.6.1: resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} @@ -7378,6 +8177,10 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + uuid@10.0.0: + resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} + hasBin: true + uuid@11.1.0: resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} hasBin: true @@ -7418,6 +8221,16 @@ packages: peerDependencies: vite: '>=3' + vite-plugin-top-level-await@1.6.0: + resolution: {integrity: sha512-bNhUreLamTIkoulCR9aDXbTbhLk6n1YE8NJUTTxl5RYskNRtzOR0ASzSjBVRtNdjIfngDXo11qOsybGLNsrdww==} + peerDependencies: + vite: '>=2.8' + + vite-plugin-wasm@3.5.0: + resolution: {integrity: sha512-X5VWgCnqiQEGb+omhlBVsvTfxikKtoOgAzQ95+BZ8gQ+VfMHIjSHr0wyvXFQCa0eKQ0fKyaL0kWcEnYqBac4lQ==} + peerDependencies: + vite: ^2 || ^3 || ^4 || ^5 || ^6 || ^7 + vite-tsconfig-paths@5.1.4: resolution: {integrity: sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==} peerDependencies: @@ -7502,6 +8315,9 @@ packages: jsdom: optional: true + w3c-keyname@2.2.8: + resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} + w3c-xmlserializer@5.0.0: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} @@ -7697,11 +8513,104 @@ packages: snapshots: + '@ai-sdk/gateway@1.0.15(zod@4.1.4)': + dependencies: + '@ai-sdk/provider': 2.0.0 + '@ai-sdk/provider-utils': 3.0.7(zod@4.1.4) + zod: 4.1.4 + + '@ai-sdk/provider-utils@3.0.7(zod@4.1.4)': + dependencies: + '@ai-sdk/provider': 2.0.0 + '@standard-schema/spec': 1.0.0 + eventsource-parser: 3.0.5 + zod: 4.1.4 + + '@ai-sdk/provider@2.0.0': + dependencies: + json-schema: 0.4.0 + + '@ai-sdk/react@2.0.27(react@19.1.1)(zod@4.1.4)': + dependencies: + '@ai-sdk/provider-utils': 3.0.7(zod@4.1.4) + ai: 5.0.27(zod@4.1.4) + react: 19.1.1 + swr: 2.3.6(react@19.1.1) + throttleit: 2.1.0 + optionalDependencies: + zod: 4.1.4 + '@ampproject/remapping@2.3.0': dependencies: '@jridgewell/gen-mapping': 0.3.8 '@jridgewell/trace-mapping': 0.3.25 + '@ark-ui/react@5.22.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@internationalized/date': 3.8.2 + '@zag-js/accordion': 1.22.1 + '@zag-js/anatomy': 1.22.1 + '@zag-js/angle-slider': 1.22.1 + '@zag-js/async-list': 1.22.1 + '@zag-js/auto-resize': 1.22.1 + '@zag-js/avatar': 1.22.1 + '@zag-js/carousel': 1.22.1 + '@zag-js/checkbox': 1.22.1 + '@zag-js/clipboard': 1.22.1 + '@zag-js/collapsible': 1.22.1 + '@zag-js/collection': 1.22.1 + '@zag-js/color-picker': 1.22.1 + '@zag-js/color-utils': 1.22.1 + '@zag-js/combobox': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/date-picker': 1.22.1(@internationalized/date@3.8.2) + '@zag-js/date-utils': 1.22.1(@internationalized/date@3.8.2) + '@zag-js/dialog': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/editable': 1.22.1 + '@zag-js/file-upload': 1.22.1 + '@zag-js/file-utils': 1.22.1 + '@zag-js/floating-panel': 1.22.1 + '@zag-js/focus-trap': 1.22.1 + '@zag-js/highlight-word': 1.22.1 + '@zag-js/hover-card': 1.22.1 + '@zag-js/i18n-utils': 1.22.1 + '@zag-js/json-tree-utils': 1.22.1 + '@zag-js/listbox': 1.22.1 + '@zag-js/menu': 1.22.1 + '@zag-js/number-input': 1.22.1 + '@zag-js/pagination': 1.22.1 + '@zag-js/password-input': 1.22.1 + '@zag-js/pin-input': 1.22.1 + '@zag-js/popover': 1.22.1 + '@zag-js/presence': 1.22.1 + '@zag-js/progress': 1.22.1 + '@zag-js/qr-code': 1.22.1 + '@zag-js/radio-group': 1.22.1 + '@zag-js/rating-group': 1.22.1 + '@zag-js/react': 1.22.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@zag-js/scroll-area': 1.22.1 + '@zag-js/select': 1.22.1 + '@zag-js/signature-pad': 1.22.1 + '@zag-js/slider': 1.22.1 + '@zag-js/splitter': 1.22.1 + '@zag-js/steps': 1.22.1 + '@zag-js/switch': 1.22.1 + '@zag-js/tabs': 1.22.1 + '@zag-js/tags-input': 1.22.1 + '@zag-js/time-picker': 1.22.1(@internationalized/date@3.8.2) + '@zag-js/timer': 1.22.1 + '@zag-js/toast': 1.22.1 + '@zag-js/toggle': 1.22.1 + '@zag-js/toggle-group': 1.22.1 + '@zag-js/tooltip': 1.22.1 + '@zag-js/tour': 1.22.1 + '@zag-js/tree-view': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + '@ark/schema@0.46.0': dependencies: '@ark/util': 0.46.0 @@ -8568,17 +9477,28 @@ snapshots: dependencies: '@floating-ui/utils': 0.2.9 + '@floating-ui/core@1.7.3': + dependencies: + '@floating-ui/utils': 0.2.10 + '@floating-ui/dom@1.6.13': dependencies: '@floating-ui/core': 1.6.9 '@floating-ui/utils': 0.2.9 + '@floating-ui/dom@1.7.4': + dependencies: + '@floating-ui/core': 1.7.3 + '@floating-ui/utils': 0.2.10 + '@floating-ui/react-dom@2.1.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: '@floating-ui/dom': 1.6.13 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) + '@floating-ui/utils@0.2.10': {} + '@floating-ui/utils@0.2.9': {} '@hexagon/base64@1.1.28': {} @@ -8595,6 +9515,14 @@ snapshots: optionalDependencies: '@types/node': 22.18.0 + '@internationalized/date@3.8.2': + dependencies: + '@swc/helpers': 0.5.17 + + '@internationalized/number@3.6.4': + dependencies: + '@swc/helpers': 0.5.17 + '@ioredis/commands@1.3.0': {} '@isaacs/cliui@8.0.2': @@ -8812,8 +9740,7 @@ snapshots: '@oozcitak/util@8.3.8': {} - '@opentelemetry/api@1.9.0': - optional: true + '@opentelemetry/api@1.9.0': {} '@orpc/arktype@1.8.5(@ark/schema@0.46.0)(@opentelemetry/api@1.9.0)(@orpc/contract@1.8.5(@opentelemetry/api@1.9.0))(arktype@2.1.20)(crossws@0.3.5)(ws@8.18.1)': dependencies: @@ -9056,6 +9983,8 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@popperjs/core@2.11.8': {} + '@poppinss/colors@4.1.5': dependencies: kleur: 4.1.5 @@ -9796,6 +10725,8 @@ snapshots: '@radix-ui/rect@1.1.1': {} + '@remirror/core-constants@3.0.0': {} + '@rolldown/pluginutils@1.0.0-beta.34': {} '@rollup/plugin-alias@5.1.1(rollup@4.49.0)': @@ -9853,6 +10784,10 @@ snapshots: optionalDependencies: rollup: 4.49.0 + '@rollup/plugin-virtual@3.0.2(rollup@4.49.0)': + optionalDependencies: + rollup: 4.49.0 + '@rollup/pluginutils@5.2.0(rollup@4.49.0)': dependencies: '@types/estree': 1.0.6 @@ -10033,6 +10968,65 @@ snapshots: '@standard-schema/utils@0.3.0': {} + '@swc/core-darwin-arm64@1.13.5': + optional: true + + '@swc/core-darwin-x64@1.13.5': + optional: true + + '@swc/core-linux-arm-gnueabihf@1.13.5': + optional: true + + '@swc/core-linux-arm64-gnu@1.13.5': + optional: true + + '@swc/core-linux-arm64-musl@1.13.5': + optional: true + + '@swc/core-linux-x64-gnu@1.13.5': + optional: true + + '@swc/core-linux-x64-musl@1.13.5': + optional: true + + '@swc/core-win32-arm64-msvc@1.13.5': + optional: true + + '@swc/core-win32-ia32-msvc@1.13.5': + optional: true + + '@swc/core-win32-x64-msvc@1.13.5': + optional: true + + '@swc/core@1.13.5(@swc/helpers@0.5.17)': + dependencies: + '@swc/counter': 0.1.3 + '@swc/types': 0.1.24 + optionalDependencies: + '@swc/core-darwin-arm64': 1.13.5 + '@swc/core-darwin-x64': 1.13.5 + '@swc/core-linux-arm-gnueabihf': 1.13.5 + '@swc/core-linux-arm64-gnu': 1.13.5 + '@swc/core-linux-arm64-musl': 1.13.5 + '@swc/core-linux-x64-gnu': 1.13.5 + '@swc/core-linux-x64-musl': 1.13.5 + '@swc/core-win32-arm64-msvc': 1.13.5 + '@swc/core-win32-ia32-msvc': 1.13.5 + '@swc/core-win32-x64-msvc': 1.13.5 + '@swc/helpers': 0.5.17 + + '@swc/counter@0.1.3': {} + + '@swc/helpers@0.5.17': + dependencies: + tslib: 2.8.1 + + '@swc/types@0.1.24': + dependencies: + '@swc/counter': 0.1.3 + + '@swc/wasm@1.13.5': {} + '@t3-oss/env-core@0.13.8(arktype@2.1.20)(typescript@5.9.2)(zod@4.1.4)': optionalDependencies: arktype: 2.1.20 @@ -10182,9 +11176,9 @@ snapshots: tiny-invariant: 1.3.3 tiny-warning: 1.0.3 - '@tanstack/react-start-plugin@1.131.28(@netlify/blobs@9.1.2)(@tanstack/react-router@1.131.28(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@vitejs/plugin-react@5.0.2(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.3)))(aws4fetch@1.0.20)(better-sqlite3@11.9.1)(drizzle-orm@0.44.5(@cloudflare/workers-types@4.20250414.0)(@opentelemetry/api@1.9.0)(@prisma/client@6.6.0(prisma@6.6.0(typescript@5.9.2))(typescript@5.9.2))(@types/pg@8.15.5)(better-sqlite3@11.9.1)(gel@2.0.1)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.5)(prisma@6.6.0(typescript@5.9.2)))(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.3))(webpack@5.99.5)(xml2js@0.6.2)': + '@tanstack/react-start-plugin@1.131.28(@netlify/blobs@9.1.2)(@tanstack/react-router@1.131.28(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@vitejs/plugin-react@5.0.2(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.3)))(aws4fetch@1.0.20)(better-sqlite3@11.9.1)(drizzle-orm@0.44.5(@cloudflare/workers-types@4.20250414.0)(@opentelemetry/api@1.9.0)(@prisma/client@6.6.0(prisma@6.6.0(typescript@5.9.2))(typescript@5.9.2))(@types/pg@8.15.5)(better-sqlite3@11.9.1)(gel@2.0.1)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.5)(prisma@6.6.0(typescript@5.9.2)))(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.3))(webpack@5.99.5(@swc/core@1.13.5(@swc/helpers@0.5.17)))(xml2js@0.6.2)': dependencies: - '@tanstack/start-plugin-core': 1.131.28(@netlify/blobs@9.1.2)(@tanstack/react-router@1.131.28(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(aws4fetch@1.0.20)(better-sqlite3@11.9.1)(drizzle-orm@0.44.5(@cloudflare/workers-types@4.20250414.0)(@opentelemetry/api@1.9.0)(@prisma/client@6.6.0(prisma@6.6.0(typescript@5.9.2))(typescript@5.9.2))(@types/pg@8.15.5)(better-sqlite3@11.9.1)(gel@2.0.1)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.5)(prisma@6.6.0(typescript@5.9.2)))(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.3))(webpack@5.99.5)(xml2js@0.6.2) + '@tanstack/start-plugin-core': 1.131.28(@netlify/blobs@9.1.2)(@tanstack/react-router@1.131.28(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(aws4fetch@1.0.20)(better-sqlite3@11.9.1)(drizzle-orm@0.44.5(@cloudflare/workers-types@4.20250414.0)(@opentelemetry/api@1.9.0)(@prisma/client@6.6.0(prisma@6.6.0(typescript@5.9.2))(typescript@5.9.2))(@types/pg@8.15.5)(better-sqlite3@11.9.1)(gel@2.0.1)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.5)(prisma@6.6.0(typescript@5.9.2)))(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.3))(webpack@5.99.5(@swc/core@1.13.5(@swc/helpers@0.5.17)))(xml2js@0.6.2) '@vitejs/plugin-react': 5.0.2(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.3)) pathe: 2.0.3 vite: 7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.3) @@ -10234,10 +11228,10 @@ snapshots: react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@tanstack/react-start@1.131.28(@netlify/blobs@9.1.2)(@tanstack/react-router@1.131.28(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@vitejs/plugin-react@5.0.2(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.3)))(aws4fetch@1.0.20)(better-sqlite3@11.9.1)(drizzle-orm@0.44.5(@cloudflare/workers-types@4.20250414.0)(@opentelemetry/api@1.9.0)(@prisma/client@6.6.0(prisma@6.6.0(typescript@5.9.2))(typescript@5.9.2))(@types/pg@8.15.5)(better-sqlite3@11.9.1)(gel@2.0.1)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.5)(prisma@6.6.0(typescript@5.9.2)))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.3))(webpack@5.99.5)(xml2js@0.6.2)': + '@tanstack/react-start@1.131.28(@netlify/blobs@9.1.2)(@tanstack/react-router@1.131.28(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@vitejs/plugin-react@5.0.2(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.3)))(aws4fetch@1.0.20)(better-sqlite3@11.9.1)(drizzle-orm@0.44.5(@cloudflare/workers-types@4.20250414.0)(@opentelemetry/api@1.9.0)(@prisma/client@6.6.0(prisma@6.6.0(typescript@5.9.2))(typescript@5.9.2))(@types/pg@8.15.5)(better-sqlite3@11.9.1)(gel@2.0.1)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.5)(prisma@6.6.0(typescript@5.9.2)))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.3))(webpack@5.99.5(@swc/core@1.13.5(@swc/helpers@0.5.17)))(xml2js@0.6.2)': dependencies: '@tanstack/react-start-client': 1.131.28(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@tanstack/react-start-plugin': 1.131.28(@netlify/blobs@9.1.2)(@tanstack/react-router@1.131.28(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@vitejs/plugin-react@5.0.2(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.3)))(aws4fetch@1.0.20)(better-sqlite3@11.9.1)(drizzle-orm@0.44.5(@cloudflare/workers-types@4.20250414.0)(@opentelemetry/api@1.9.0)(@prisma/client@6.6.0(prisma@6.6.0(typescript@5.9.2))(typescript@5.9.2))(@types/pg@8.15.5)(better-sqlite3@11.9.1)(gel@2.0.1)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.5)(prisma@6.6.0(typescript@5.9.2)))(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.3))(webpack@5.99.5)(xml2js@0.6.2) + '@tanstack/react-start-plugin': 1.131.28(@netlify/blobs@9.1.2)(@tanstack/react-router@1.131.28(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@vitejs/plugin-react@5.0.2(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.3)))(aws4fetch@1.0.20)(better-sqlite3@11.9.1)(drizzle-orm@0.44.5(@cloudflare/workers-types@4.20250414.0)(@opentelemetry/api@1.9.0)(@prisma/client@6.6.0(prisma@6.6.0(typescript@5.9.2))(typescript@5.9.2))(@types/pg@8.15.5)(better-sqlite3@11.9.1)(gel@2.0.1)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.5)(prisma@6.6.0(typescript@5.9.2)))(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.3))(webpack@5.99.5(@swc/core@1.13.5(@swc/helpers@0.5.17)))(xml2js@0.6.2) '@tanstack/react-start-server': 1.131.28(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@tanstack/start-server-functions-client': 1.131.28(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.3)) '@tanstack/start-server-functions-server': 1.131.2(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.3)) @@ -10318,7 +11312,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/router-plugin@1.131.28(@tanstack/react-router@1.131.28(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.3))(webpack@5.99.5)': + '@tanstack/router-plugin@1.131.28(@tanstack/react-router@1.131.28(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.3))(webpack@5.99.5(@swc/core@1.13.5(@swc/helpers@0.5.17)))': dependencies: '@babel/core': 7.28.3 '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.3) @@ -10337,7 +11331,7 @@ snapshots: optionalDependencies: '@tanstack/react-router': 1.131.28(react-dom@19.1.1(react@19.1.1))(react@19.1.1) vite: 7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.3) - webpack: 5.99.5 + webpack: 5.99.5(@swc/core@1.13.5(@swc/helpers@0.5.17)) transitivePeerDependencies: - supports-color @@ -10376,14 +11370,14 @@ snapshots: tiny-invariant: 1.3.3 tiny-warning: 1.0.3 - '@tanstack/start-plugin-core@1.131.28(@netlify/blobs@9.1.2)(@tanstack/react-router@1.131.28(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(aws4fetch@1.0.20)(better-sqlite3@11.9.1)(drizzle-orm@0.44.5(@cloudflare/workers-types@4.20250414.0)(@opentelemetry/api@1.9.0)(@prisma/client@6.6.0(prisma@6.6.0(typescript@5.9.2))(typescript@5.9.2))(@types/pg@8.15.5)(better-sqlite3@11.9.1)(gel@2.0.1)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.5)(prisma@6.6.0(typescript@5.9.2)))(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.3))(webpack@5.99.5)(xml2js@0.6.2)': + '@tanstack/start-plugin-core@1.131.28(@netlify/blobs@9.1.2)(@tanstack/react-router@1.131.28(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(aws4fetch@1.0.20)(better-sqlite3@11.9.1)(drizzle-orm@0.44.5(@cloudflare/workers-types@4.20250414.0)(@opentelemetry/api@1.9.0)(@prisma/client@6.6.0(prisma@6.6.0(typescript@5.9.2))(typescript@5.9.2))(@types/pg@8.15.5)(better-sqlite3@11.9.1)(gel@2.0.1)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.5)(prisma@6.6.0(typescript@5.9.2)))(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.3))(webpack@5.99.5(@swc/core@1.13.5(@swc/helpers@0.5.17)))(xml2js@0.6.2)': dependencies: '@babel/code-frame': 7.26.2 '@babel/core': 7.27.4 '@babel/types': 7.27.6 '@tanstack/router-core': 1.131.28 '@tanstack/router-generator': 1.131.28 - '@tanstack/router-plugin': 1.131.28(@tanstack/react-router@1.131.28(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.3))(webpack@5.99.5) + '@tanstack/router-plugin': 1.131.28(@tanstack/react-router@1.131.28(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.3))(webpack@5.99.5(@swc/core@1.13.5(@swc/helpers@0.5.17))) '@tanstack/router-utils': 1.131.2 '@tanstack/server-functions-plugin': 1.131.2(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.3)) '@tanstack/start-server-core': 1.131.28 @@ -10444,55 +11438,252 @@ snapshots: tiny-warning: 1.0.3 unctx: 2.4.1 - '@tanstack/start-server-functions-client@1.131.28(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.3))': + '@tanstack/start-server-functions-client@1.131.28(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.3))': + dependencies: + '@tanstack/server-functions-plugin': 1.131.2(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.3)) + '@tanstack/start-server-functions-fetcher': 1.131.28 + transitivePeerDependencies: + - supports-color + - vite + + '@tanstack/start-server-functions-fetcher@1.131.28': + dependencies: + '@tanstack/router-core': 1.131.28 + '@tanstack/start-client-core': 1.131.28 + + '@tanstack/start-server-functions-server@1.131.2(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.3))': + dependencies: + '@tanstack/server-functions-plugin': 1.131.2(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.3)) + tiny-invariant: 1.3.3 + transitivePeerDependencies: + - supports-color + - vite + + '@tanstack/start-storage-context@1.131.28': + dependencies: + '@tanstack/router-core': 1.131.28 + + '@tanstack/store@0.7.0': {} + + '@tanstack/virtual-file-routes@1.131.2': {} + + '@testing-library/dom@10.4.1': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/runtime': 7.26.10 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + picocolors: 1.1.1 + pretty-format: 27.5.1 + + '@testing-library/react@16.3.0(@testing-library/dom@10.4.1)(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@babel/runtime': 7.26.10 + '@testing-library/dom': 10.4.1 + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.12 + '@types/react-dom': 19.1.9(@types/react@19.1.12) + + '@tiptap/core@2.26.1(@tiptap/pm@2.26.1)': + dependencies: + '@tiptap/pm': 2.26.1 + + '@tiptap/extension-blockquote@2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))': + dependencies: + '@tiptap/core': 2.26.1(@tiptap/pm@2.26.1) + + '@tiptap/extension-bold@2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))': + dependencies: + '@tiptap/core': 2.26.1(@tiptap/pm@2.26.1) + + '@tiptap/extension-bubble-menu@2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))(@tiptap/pm@2.26.1)': + dependencies: + '@tiptap/core': 2.26.1(@tiptap/pm@2.26.1) + '@tiptap/pm': 2.26.1 + tippy.js: 6.3.7 + + '@tiptap/extension-bullet-list@2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))': + dependencies: + '@tiptap/core': 2.26.1(@tiptap/pm@2.26.1) + + '@tiptap/extension-code-block@2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))(@tiptap/pm@2.26.1)': + dependencies: + '@tiptap/core': 2.26.1(@tiptap/pm@2.26.1) + '@tiptap/pm': 2.26.1 + + '@tiptap/extension-code@2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))': + dependencies: + '@tiptap/core': 2.26.1(@tiptap/pm@2.26.1) + + '@tiptap/extension-document@2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))': + dependencies: + '@tiptap/core': 2.26.1(@tiptap/pm@2.26.1) + + '@tiptap/extension-dropcursor@2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))(@tiptap/pm@2.26.1)': + dependencies: + '@tiptap/core': 2.26.1(@tiptap/pm@2.26.1) + '@tiptap/pm': 2.26.1 + + '@tiptap/extension-floating-menu@2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))(@tiptap/pm@2.26.1)': + dependencies: + '@tiptap/core': 2.26.1(@tiptap/pm@2.26.1) + '@tiptap/pm': 2.26.1 + tippy.js: 6.3.7 + + '@tiptap/extension-gapcursor@2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))(@tiptap/pm@2.26.1)': + dependencies: + '@tiptap/core': 2.26.1(@tiptap/pm@2.26.1) + '@tiptap/pm': 2.26.1 + + '@tiptap/extension-hard-break@2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))': + dependencies: + '@tiptap/core': 2.26.1(@tiptap/pm@2.26.1) + + '@tiptap/extension-heading@2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))': + dependencies: + '@tiptap/core': 2.26.1(@tiptap/pm@2.26.1) + + '@tiptap/extension-highlight@2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))': + dependencies: + '@tiptap/core': 2.26.1(@tiptap/pm@2.26.1) + + '@tiptap/extension-history@2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))(@tiptap/pm@2.26.1)': + dependencies: + '@tiptap/core': 2.26.1(@tiptap/pm@2.26.1) + '@tiptap/pm': 2.26.1 + + '@tiptap/extension-horizontal-rule@2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))(@tiptap/pm@2.26.1)': + dependencies: + '@tiptap/core': 2.26.1(@tiptap/pm@2.26.1) + '@tiptap/pm': 2.26.1 + + '@tiptap/extension-image@2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))': + dependencies: + '@tiptap/core': 2.26.1(@tiptap/pm@2.26.1) + + '@tiptap/extension-italic@2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))': + dependencies: + '@tiptap/core': 2.26.1(@tiptap/pm@2.26.1) + + '@tiptap/extension-link@2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))(@tiptap/pm@2.26.1)': + dependencies: + '@tiptap/core': 2.26.1(@tiptap/pm@2.26.1) + '@tiptap/pm': 2.26.1 + linkifyjs: 4.3.2 + + '@tiptap/extension-list-item@2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))': + dependencies: + '@tiptap/core': 2.26.1(@tiptap/pm@2.26.1) + + '@tiptap/extension-ordered-list@2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))': + dependencies: + '@tiptap/core': 2.26.1(@tiptap/pm@2.26.1) + + '@tiptap/extension-paragraph@2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))': + dependencies: + '@tiptap/core': 2.26.1(@tiptap/pm@2.26.1) + + '@tiptap/extension-strike@2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))': + dependencies: + '@tiptap/core': 2.26.1(@tiptap/pm@2.26.1) + + '@tiptap/extension-subscript@2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))': + dependencies: + '@tiptap/core': 2.26.1(@tiptap/pm@2.26.1) + + '@tiptap/extension-superscript@2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))': + dependencies: + '@tiptap/core': 2.26.1(@tiptap/pm@2.26.1) + + '@tiptap/extension-task-item@2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))(@tiptap/pm@2.26.1)': dependencies: - '@tanstack/server-functions-plugin': 1.131.2(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.3)) - '@tanstack/start-server-functions-fetcher': 1.131.28 - transitivePeerDependencies: - - supports-color - - vite + '@tiptap/core': 2.26.1(@tiptap/pm@2.26.1) + '@tiptap/pm': 2.26.1 - '@tanstack/start-server-functions-fetcher@1.131.28': + '@tiptap/extension-task-list@2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))': dependencies: - '@tanstack/router-core': 1.131.28 - '@tanstack/start-client-core': 1.131.28 + '@tiptap/core': 2.26.1(@tiptap/pm@2.26.1) - '@tanstack/start-server-functions-server@1.131.2(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.3))': + '@tiptap/extension-text-align@2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))': dependencies: - '@tanstack/server-functions-plugin': 1.131.2(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.3)) - tiny-invariant: 1.3.3 - transitivePeerDependencies: - - supports-color - - vite + '@tiptap/core': 2.26.1(@tiptap/pm@2.26.1) - '@tanstack/start-storage-context@1.131.28': + '@tiptap/extension-text-style@2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))': dependencies: - '@tanstack/router-core': 1.131.28 + '@tiptap/core': 2.26.1(@tiptap/pm@2.26.1) - '@tanstack/store@0.7.0': {} + '@tiptap/extension-text@2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))': + dependencies: + '@tiptap/core': 2.26.1(@tiptap/pm@2.26.1) - '@tanstack/virtual-file-routes@1.131.2': {} + '@tiptap/extension-typography@2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))': + dependencies: + '@tiptap/core': 2.26.1(@tiptap/pm@2.26.1) - '@testing-library/dom@10.4.1': + '@tiptap/extension-underline@2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))': dependencies: - '@babel/code-frame': 7.27.1 - '@babel/runtime': 7.26.10 - '@types/aria-query': 5.0.4 - aria-query: 5.3.0 - dom-accessibility-api: 0.5.16 - lz-string: 1.5.0 - picocolors: 1.1.1 - pretty-format: 27.5.1 + '@tiptap/core': 2.26.1(@tiptap/pm@2.26.1) - '@testing-library/react@16.3.0(@testing-library/dom@10.4.1)(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@tiptap/pm@2.26.1': dependencies: - '@babel/runtime': 7.26.10 - '@testing-library/dom': 10.4.1 + prosemirror-changeset: 2.3.1 + prosemirror-collab: 1.3.1 + prosemirror-commands: 1.7.1 + prosemirror-dropcursor: 1.8.2 + prosemirror-gapcursor: 1.3.2 + prosemirror-history: 1.4.1 + prosemirror-inputrules: 1.5.0 + prosemirror-keymap: 1.2.3 + prosemirror-markdown: 1.13.2 + prosemirror-menu: 1.2.5 + prosemirror-model: 1.25.3 + prosemirror-schema-basic: 1.2.4 + prosemirror-schema-list: 1.5.1 + prosemirror-state: 1.4.3 + prosemirror-tables: 1.8.1 + prosemirror-trailing-node: 3.0.0(prosemirror-model@1.25.3)(prosemirror-state@1.4.3)(prosemirror-view@1.40.1) + prosemirror-transform: 1.10.4 + prosemirror-view: 1.40.1 + + '@tiptap/react@2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))(@tiptap/pm@2.26.1)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@tiptap/core': 2.26.1(@tiptap/pm@2.26.1) + '@tiptap/extension-bubble-menu': 2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))(@tiptap/pm@2.26.1) + '@tiptap/extension-floating-menu': 2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))(@tiptap/pm@2.26.1) + '@tiptap/pm': 2.26.1 + '@types/use-sync-external-store': 0.0.6 + fast-deep-equal: 3.1.3 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - optionalDependencies: - '@types/react': 19.1.12 - '@types/react-dom': 19.1.9(@types/react@19.1.12) + use-sync-external-store: 1.5.0(react@19.1.1) + + '@tiptap/starter-kit@2.26.1': + dependencies: + '@tiptap/core': 2.26.1(@tiptap/pm@2.26.1) + '@tiptap/extension-blockquote': 2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1)) + '@tiptap/extension-bold': 2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1)) + '@tiptap/extension-bullet-list': 2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1)) + '@tiptap/extension-code': 2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1)) + '@tiptap/extension-code-block': 2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))(@tiptap/pm@2.26.1) + '@tiptap/extension-document': 2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1)) + '@tiptap/extension-dropcursor': 2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))(@tiptap/pm@2.26.1) + '@tiptap/extension-gapcursor': 2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))(@tiptap/pm@2.26.1) + '@tiptap/extension-hard-break': 2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1)) + '@tiptap/extension-heading': 2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1)) + '@tiptap/extension-history': 2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))(@tiptap/pm@2.26.1) + '@tiptap/extension-horizontal-rule': 2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))(@tiptap/pm@2.26.1) + '@tiptap/extension-italic': 2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1)) + '@tiptap/extension-list-item': 2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1)) + '@tiptap/extension-ordered-list': 2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1)) + '@tiptap/extension-paragraph': 2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1)) + '@tiptap/extension-strike': 2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1)) + '@tiptap/extension-text': 2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1)) + '@tiptap/extension-text-style': 2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1)) + '@tiptap/pm': 2.26.1 '@tootallnate/quickjs-emscripten@0.23.0': {} @@ -10504,7 +11695,7 @@ snapshots: '@tsconfig/node16@1.0.4': {} - '@turbo/gen@2.5.6(@types/node@22.18.0)(typescript@5.9.2)': + '@turbo/gen@2.5.6(@swc/core@1.13.5(@swc/helpers@0.5.17))(@swc/wasm@1.13.5)(@types/node@22.18.0)(typescript@5.9.2)': dependencies: '@turbo/workspaces': 2.5.6 commander: 10.0.1 @@ -10514,7 +11705,7 @@ snapshots: node-plop: 0.26.3 picocolors: 1.0.1 proxy-agent: 6.5.0 - ts-node: 10.9.2(@types/node@22.18.0)(typescript@5.9.2) + ts-node: 10.9.2(@swc/core@1.13.5(@swc/helpers@0.5.17))(@swc/wasm@1.13.5)(@types/node@22.18.0)(typescript@5.9.2) update-check: 1.5.4 validate-npm-package-name: 5.0.1 transitivePeerDependencies: @@ -10634,10 +11825,19 @@ snapshots: '@types/json-schema@7.0.15': optional: true + '@types/linkify-it@5.0.0': {} + + '@types/markdown-it@14.1.2': + dependencies: + '@types/linkify-it': 5.0.0 + '@types/mdurl': 2.0.0 + '@types/mdast@4.0.4': dependencies: '@types/unist': 3.0.3 + '@types/mdurl@2.0.0': {} + '@types/minimatch@5.1.2': {} '@types/ms@2.1.0': {} @@ -10678,6 +11878,8 @@ snapshots: '@types/unist@3.0.3': {} + '@types/use-sync-external-store@0.0.6': {} + '@types/yauzl@2.10.3': dependencies: '@types/node': 22.18.0 @@ -10951,6 +12153,519 @@ snapshots: '@xtuc/long@4.2.2': optional: true + '@zag-js/accordion@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/anatomy@1.22.1': {} + + '@zag-js/angle-slider@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/rect-utils': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/aria-hidden@1.22.1': {} + + '@zag-js/async-list@1.22.1': + dependencies: + '@zag-js/core': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/auto-resize@1.22.1': + dependencies: + '@zag-js/dom-query': 1.22.1 + + '@zag-js/avatar@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/carousel@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/scroll-snap': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/checkbox@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/focus-visible': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/clipboard@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/collapsible@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/collection@1.22.1': + dependencies: + '@zag-js/utils': 1.22.1 + + '@zag-js/color-picker@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/color-utils': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dismissable': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/popper': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/color-utils@1.22.1': + dependencies: + '@zag-js/utils': 1.22.1 + + '@zag-js/combobox@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/aria-hidden': 1.22.1 + '@zag-js/collection': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dismissable': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/popper': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/core@1.22.1': + dependencies: + '@zag-js/dom-query': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/date-picker@1.22.1(@internationalized/date@3.8.2)': + dependencies: + '@internationalized/date': 3.8.2 + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/date-utils': 1.22.1(@internationalized/date@3.8.2) + '@zag-js/dismissable': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/live-region': 1.22.1 + '@zag-js/popper': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/date-utils@1.22.1(@internationalized/date@3.8.2)': + dependencies: + '@internationalized/date': 3.8.2 + + '@zag-js/dialog@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/aria-hidden': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dismissable': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/focus-trap': 1.22.1 + '@zag-js/remove-scroll': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/dismissable@1.22.1': + dependencies: + '@zag-js/dom-query': 1.22.1 + '@zag-js/interact-outside': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/dom-query@1.22.1': + dependencies: + '@zag-js/types': 1.22.1 + + '@zag-js/editable@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/interact-outside': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/file-upload@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/file-utils': 1.22.1 + '@zag-js/i18n-utils': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/file-utils@1.22.1': + dependencies: + '@zag-js/i18n-utils': 1.22.1 + + '@zag-js/floating-panel@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dismissable': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/popper': 1.22.1 + '@zag-js/rect-utils': 1.22.1 + '@zag-js/store': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/focus-trap@1.22.1': + dependencies: + '@zag-js/dom-query': 1.22.1 + + '@zag-js/focus-visible@1.22.1': + dependencies: + '@zag-js/dom-query': 1.22.1 + + '@zag-js/highlight-word@1.22.1': {} + + '@zag-js/hover-card@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dismissable': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/popper': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/i18n-utils@1.22.1': + dependencies: + '@zag-js/dom-query': 1.22.1 + + '@zag-js/interact-outside@1.22.1': + dependencies: + '@zag-js/dom-query': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/json-tree-utils@1.22.1': {} + + '@zag-js/listbox@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/collection': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/focus-visible': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/live-region@1.22.1': {} + + '@zag-js/menu@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dismissable': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/popper': 1.22.1 + '@zag-js/rect-utils': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/number-input@1.22.1': + dependencies: + '@internationalized/number': 3.6.4 + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/pagination@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/password-input@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/pin-input@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/popover@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/aria-hidden': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dismissable': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/focus-trap': 1.22.1 + '@zag-js/popper': 1.22.1 + '@zag-js/remove-scroll': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/popper@1.22.1': + dependencies: + '@floating-ui/dom': 1.7.4 + '@zag-js/dom-query': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/presence@1.22.1': + dependencies: + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + + '@zag-js/progress@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/qr-code@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + proxy-memoize: 3.0.1 + uqr: 0.1.2 + + '@zag-js/radio-group@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/focus-visible': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/rating-group@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/react@1.22.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@zag-js/core': 1.22.1 + '@zag-js/store': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + + '@zag-js/rect-utils@1.22.1': {} + + '@zag-js/remove-scroll@1.22.1': + dependencies: + '@zag-js/dom-query': 1.22.1 + + '@zag-js/scroll-area@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/scroll-snap@1.22.1': + dependencies: + '@zag-js/dom-query': 1.22.1 + + '@zag-js/select@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/collection': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dismissable': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/popper': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/signature-pad@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + perfect-freehand: 1.2.2 + + '@zag-js/slider@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/splitter@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/steps@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/store@1.22.1': + dependencies: + proxy-compare: 3.0.1 + + '@zag-js/switch@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/focus-visible': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/tabs@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/tags-input@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/auto-resize': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/interact-outside': 1.22.1 + '@zag-js/live-region': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/time-picker@1.22.1(@internationalized/date@3.8.2)': + dependencies: + '@internationalized/date': 3.8.2 + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dismissable': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/popper': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/timer@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/toast@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dismissable': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/toggle-group@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/toggle@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/tooltip@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/focus-visible': 1.22.1 + '@zag-js/popper': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/tour@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dismissable': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/focus-trap': 1.22.1 + '@zag-js/interact-outside': 1.22.1 + '@zag-js/popper': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/tree-view@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/collection': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/types@1.22.1': + dependencies: + csstype: 3.1.3 + + '@zag-js/utils@1.22.1': {} + abbrev@3.0.1: {} abort-controller@3.0.0: @@ -10976,6 +12691,14 @@ snapshots: clean-stack: 2.2.0 indent-string: 4.0.0 + ai@5.0.27(zod@4.1.4): + dependencies: + '@ai-sdk/gateway': 1.0.15(zod@4.1.4) + '@ai-sdk/provider': 2.0.0 + '@ai-sdk/provider-utils': 3.0.7(zod@4.1.4) + '@opentelemetry/api': 1.9.0 + zod: 4.1.4 + ajv-formats@2.1.1(ajv@8.17.1): optionalDependencies: ajv: 8.17.1 @@ -11516,6 +13239,8 @@ snapshots: create-require@1.1.1: {} + crelt@1.0.6: {} + cron-parser@4.9.0: dependencies: luxon: 3.7.1 @@ -12017,6 +13742,8 @@ snapshots: escape-string-regexp@1.0.5: {} + escape-string-regexp@4.0.0: {} + escape-string-regexp@5.0.0: {} escodegen@2.1.0: @@ -12065,6 +13792,8 @@ snapshots: events@3.3.0: {} + eventsource-parser@3.0.5: {} + execa@5.1.1: dependencies: cross-spawn: 7.0.6 @@ -12116,8 +13845,7 @@ snapshots: transitivePeerDependencies: - supports-color - fast-deep-equal@3.1.3: - optional: true + fast-deep-equal@3.1.3: {} fast-equals@5.2.2: {} @@ -12762,6 +14490,8 @@ snapshots: isexe@3.1.1: {} + isomorphic.js@0.2.5: {} + jackspeak@3.4.3: dependencies: '@isaacs/cliui': 8.0.2 @@ -12831,6 +14561,8 @@ snapshots: json-schema-traverse@1.0.0: optional: true + json-schema@0.4.0: {} + json5@2.2.3: {} jsonfile@4.0.0: @@ -12867,6 +14599,10 @@ snapshots: dependencies: readable-stream: 2.3.8 + lib0@0.2.114: + dependencies: + isomorphic.js: 0.2.5 + libphonenumber-js@1.12.14: {} lightningcss-darwin-arm64@1.30.1: @@ -12918,6 +14654,12 @@ snapshots: lines-and-columns@1.2.4: {} + linkify-it@5.0.0: + dependencies: + uc.micro: 2.1.0 + + linkifyjs@4.3.2: {} + listhen@1.9.0: dependencies: '@parcel/watcher': 2.5.1 @@ -12998,6 +14740,16 @@ snapshots: dependencies: js-tokens: 4.0.0 + loro-crdt@1.6.0: {} + + loro-prosemirror@0.2.3(loro-crdt@1.6.0)(prosemirror-model@1.25.3)(prosemirror-state@1.4.3)(prosemirror-view@1.40.1): + dependencies: + lib0: 0.2.114 + loro-crdt: 1.6.0 + prosemirror-model: 1.25.3 + prosemirror-state: 1.4.3 + prosemirror-view: 1.40.1 + loupe@3.1.3: {} loupe@3.2.1: {} @@ -13036,6 +14788,15 @@ snapshots: make-error@1.3.6: {} + markdown-it@14.1.0: + 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 + markdown-table@3.0.4: {} marked@16.2.1: {} @@ -13195,6 +14956,8 @@ snapshots: dependencies: '@types/mdast': 4.0.4 + mdurl@2.0.0: {} + merge-options@3.0.4: dependencies: is-plain-obj: 2.1.0 @@ -13785,6 +15548,8 @@ snapshots: strip-ansi: 6.0.1 wcwidth: 1.0.1 + orderedmap@2.1.1: {} + os-tmpdir@1.0.2: {} outdent@0.5.0: {} @@ -13934,6 +15699,8 @@ snapshots: perfect-debounce@1.0.0: {} + perfect-freehand@1.2.2: {} + pg-cloudflare@1.2.7: optional: true @@ -14108,6 +15875,109 @@ snapshots: property-information@7.0.0: {} + prosemirror-changeset@2.3.1: + dependencies: + prosemirror-transform: 1.10.4 + + prosemirror-collab@1.3.1: + dependencies: + prosemirror-state: 1.4.3 + + prosemirror-commands@1.7.1: + dependencies: + prosemirror-model: 1.25.3 + prosemirror-state: 1.4.3 + prosemirror-transform: 1.10.4 + + prosemirror-dropcursor@1.8.2: + dependencies: + prosemirror-state: 1.4.3 + prosemirror-transform: 1.10.4 + prosemirror-view: 1.40.1 + + prosemirror-gapcursor@1.3.2: + dependencies: + prosemirror-keymap: 1.2.3 + prosemirror-model: 1.25.3 + prosemirror-state: 1.4.3 + prosemirror-view: 1.40.1 + + prosemirror-history@1.4.1: + dependencies: + prosemirror-state: 1.4.3 + prosemirror-transform: 1.10.4 + prosemirror-view: 1.40.1 + rope-sequence: 1.3.4 + + prosemirror-inputrules@1.5.0: + dependencies: + prosemirror-state: 1.4.3 + prosemirror-transform: 1.10.4 + + prosemirror-keymap@1.2.3: + dependencies: + prosemirror-state: 1.4.3 + w3c-keyname: 2.2.8 + + prosemirror-markdown@1.13.2: + dependencies: + '@types/markdown-it': 14.1.2 + markdown-it: 14.1.0 + prosemirror-model: 1.25.3 + + prosemirror-menu@1.2.5: + dependencies: + crelt: 1.0.6 + prosemirror-commands: 1.7.1 + prosemirror-history: 1.4.1 + prosemirror-state: 1.4.3 + + prosemirror-model@1.25.3: + dependencies: + orderedmap: 2.1.1 + + prosemirror-schema-basic@1.2.4: + dependencies: + prosemirror-model: 1.25.3 + + prosemirror-schema-list@1.5.1: + dependencies: + prosemirror-model: 1.25.3 + prosemirror-state: 1.4.3 + prosemirror-transform: 1.10.4 + + prosemirror-state@1.4.3: + dependencies: + prosemirror-model: 1.25.3 + prosemirror-transform: 1.10.4 + prosemirror-view: 1.40.1 + + prosemirror-tables@1.8.1: + dependencies: + prosemirror-keymap: 1.2.3 + prosemirror-model: 1.25.3 + prosemirror-state: 1.4.3 + prosemirror-transform: 1.10.4 + prosemirror-view: 1.40.1 + + prosemirror-trailing-node@3.0.0(prosemirror-model@1.25.3)(prosemirror-state@1.4.3)(prosemirror-view@1.40.1): + dependencies: + '@remirror/core-constants': 3.0.0 + escape-string-regexp: 4.0.0 + prosemirror-model: 1.25.3 + prosemirror-state: 1.4.3 + prosemirror-view: 1.40.1 + + prosemirror-transform@1.10.4: + dependencies: + prosemirror-model: 1.25.3 + + prosemirror-view@1.40.1: + dependencies: + prosemirror-model: 1.25.3 + prosemirror-state: 1.4.3 + prosemirror-transform: 1.10.4 + proxy-agent@6.5.0: dependencies: agent-base: 7.1.3 @@ -14121,13 +15991,21 @@ snapshots: transitivePeerDependencies: - supports-color + proxy-compare@3.0.1: {} + proxy-from-env@1.1.0: {} + proxy-memoize@3.0.1: + dependencies: + proxy-compare: 3.0.1 + pump@3.0.2: dependencies: end-of-stream: 1.4.4 once: 1.4.0 + punycode.js@2.3.1: {} + punycode@2.3.1: {} pvtsutils@1.3.6: @@ -14510,6 +16388,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.49.0 fsevents: 2.3.3 + rope-sequence@1.3.4: {} + rou3@0.5.1: {} rou3@0.7.3: {} @@ -14863,6 +16743,12 @@ snapshots: lower-case: 1.1.4 upper-case: 1.1.3 + swr@2.3.6(react@19.1.1): + dependencies: + dequal: 2.0.3 + react: 19.1.1 + use-sync-external-store: 1.5.0(react@19.1.1) + symbol-tree@3.2.4: {} system-architecture@0.1.0: {} @@ -14911,14 +16797,16 @@ snapshots: term-size@2.2.1: {} - terser-webpack-plugin@5.3.14(webpack@5.99.5): + terser-webpack-plugin@5.3.14(@swc/core@1.13.5(@swc/helpers@0.5.17))(webpack@5.99.5(@swc/core@1.13.5(@swc/helpers@0.5.17))): dependencies: '@jridgewell/trace-mapping': 0.3.30 jest-worker: 27.5.1 schema-utils: 4.3.0 serialize-javascript: 6.0.2 terser: 5.39.0 - webpack: 5.99.5 + webpack: 5.99.5(@swc/core@1.13.5(@swc/helpers@0.5.17)) + optionalDependencies: + '@swc/core': 1.13.5(@swc/helpers@0.5.17) optional: true terser@5.39.0: @@ -14942,6 +16830,8 @@ snapshots: dependencies: any-promise: 1.3.0 + throttleit@2.1.0: {} + through@2.3.8: {} tiny-invariant@1.3.3: {} @@ -14977,6 +16867,10 @@ snapshots: tinyspy@4.0.3: {} + tippy.js@6.3.7: + dependencies: + '@popperjs/core': 2.11.8 + title-case@2.1.1: dependencies: no-case: 2.3.2 @@ -15034,7 +16928,7 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-node@10.9.2(@types/node@22.18.0)(typescript@5.9.2): + ts-node@10.9.2(@swc/core@1.13.5(@swc/helpers@0.5.17))(@swc/wasm@1.13.5)(@types/node@22.18.0)(typescript@5.9.2): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 @@ -15051,6 +16945,9 @@ snapshots: typescript: 5.9.2 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 + optionalDependencies: + '@swc/core': 1.13.5(@swc/helpers@0.5.17) + '@swc/wasm': 1.13.5 tsconfck@3.1.6(typescript@5.9.2): optionalDependencies: @@ -15060,7 +16957,7 @@ snapshots: tslib@2.8.1: {} - tsup@8.5.0(jiti@2.5.1)(postcss@8.5.6)(tsx@4.20.3)(typescript@5.9.2): + tsup@8.5.0(@swc/core@1.13.5(@swc/helpers@0.5.17))(jiti@2.5.1)(postcss@8.5.6)(tsx@4.20.3)(typescript@5.9.2): dependencies: bundle-require: 5.1.0(esbuild@0.25.3) cac: 6.7.14 @@ -15080,6 +16977,7 @@ snapshots: tinyglobby: 0.2.13 tree-kill: 1.2.2 optionalDependencies: + '@swc/core': 1.13.5(@swc/helpers@0.5.17) postcss: 8.5.6 typescript: 5.9.2 transitivePeerDependencies: @@ -15133,6 +17031,8 @@ snapshots: typescript@5.9.2: {} + uc.micro@2.1.0: {} + ufo@1.6.1: {} uglify-js@3.19.3: @@ -15331,6 +17231,8 @@ snapshots: util-deprecate@1.0.2: {} + uuid@10.0.0: {} + uuid@11.1.0: {} v8-compile-cache-lib@3.0.1: {} @@ -15408,6 +17310,21 @@ snapshots: transitivePeerDependencies: - supports-color + vite-plugin-top-level-await@1.6.0(@swc/helpers@0.5.17)(rollup@4.49.0)(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.3)): + dependencies: + '@rollup/plugin-virtual': 3.0.2(rollup@4.49.0) + '@swc/core': 1.13.5(@swc/helpers@0.5.17) + '@swc/wasm': 1.13.5 + uuid: 10.0.0 + vite: 7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.3) + transitivePeerDependencies: + - '@swc/helpers' + - rollup + + vite-plugin-wasm@3.5.0(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.3)): + dependencies: + vite: 7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.3) + vite-tsconfig-paths@5.1.4(typescript@5.9.2)(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.20.3)): dependencies: debug: 4.4.1 @@ -15482,6 +17399,8 @@ snapshots: - tsx - yaml + w3c-keyname@2.2.8: {} + w3c-xmlserializer@5.0.0: dependencies: xml-name-validator: 5.0.0 @@ -15511,7 +17430,7 @@ snapshots: webpack-virtual-modules@0.6.2: {} - webpack@5.99.5: + webpack@5.99.5(@swc/core@1.13.5(@swc/helpers@0.5.17)): dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.8 @@ -15533,7 +17452,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 4.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.14(webpack@5.99.5) + terser-webpack-plugin: 5.3.14(@swc/core@1.13.5(@swc/helpers@0.5.17))(webpack@5.99.5(@swc/core@1.13.5(@swc/helpers@0.5.17))) watchpack: 2.4.2 webpack-sources: 3.2.3 transitivePeerDependencies: