From 38a8dc93d1637f502e5e179bca93716f126e534d Mon Sep 17 00:00:00 2001 From: liaibo Date: Sun, 23 Nov 2025 10:45:45 +0800 Subject: [PATCH] chore: remove luckysheet integration --- components.json | 22 + dev-3001.err | 78 + dev-3001.out | 60 + frontend-3001.err | 1 + frontend-3001.out | 11 + frontend.err | 12 + frontend.out | 6 + package.json | 49 +- pnpm-lock.yaml | 4649 +++++++++++++++++ src/app/(app)/documents/[id]/loading.tsx | 11 + src/app/(app)/documents/[id]/page.tsx | 61 + src/app/(app)/layout.tsx | 55 + src/app/(auth)/login/page.tsx | 91 + src/app/api/.gitkeep | 0 src/app/api/documents/create-child/route.ts | 99 + src/app/api/documents/create/route.ts | 123 + src/app/api/documents/delete/route.ts | 36 + src/app/api/documents/duplicate/route.ts | 74 + src/app/api/documents/embed/route.ts | 76 + src/app/api/documents/empty-trash/route.ts | 53 + src/app/api/documents/move/route.ts | 37 + src/app/api/documents/options/route.ts | 61 + src/app/api/documents/purge/route.ts | 33 + src/app/api/documents/restore/route.ts | 38 + src/app/api/documents/save/route.ts | 32 + src/app/api/documents/stats/route.ts | 40 + src/app/api/documents/title/route.ts | 32 + src/app/api/media/assets/route.ts | 92 + src/app/api/media/ocr/route.ts | 43 + src/app/api/media/upload/route.ts | 82 + src/app/api/references/backlinks/route.ts | 50 + src/app/api/references/record/route.ts | 68 + src/app/api/search/documents/route.ts | 303 ++ src/app/api/search/recent/route.ts | 54 + src/app/api/sidebar/route.ts | 47 + src/app/api/workspaces/switch/route.ts | 54 + src/app/globals.css | 408 +- src/app/layout.tsx | 35 +- src/app/page.tsx | 102 +- src/components/bottom-toolbar.tsx | 36 + src/components/breadcrumb.tsx | 63 + src/components/common/.gitkeep | 0 src/components/document-task-panel.tsx | 87 + src/components/editor/.gitkeep | 0 src/components/editor/blocknote-editor.tsx | 585 +++ .../editor/blocks/AdvancedTodoBlock.tsx | 56 + src/components/editor/blocks/MediaBlock.tsx | 498 ++ .../editor/blocks/PageReferenceBlock.tsx | 114 + .../editor/blocks/ProgressBlock.tsx | 50 + src/components/editor/document-content.tsx | 259 + .../editor/document-history-drawer.tsx | 51 + src/components/editor/document-shell.tsx | 37 + src/components/editor/document-toc.tsx | 48 + .../editor/menus/CustomSideMenu.tsx | 335 ++ .../editor/menus/CustomSlashMenu.tsx | 343 ++ .../editor/page-backlinks-panel.tsx | 77 + .../editor/page-options-sidebar.tsx | 228 + src/components/editor/schema.ts | 36 + src/components/media/image-picker-context.tsx | 78 + src/components/media/image-picker-dialog.tsx | 392 ++ src/components/mindmap/.gitkeep | 0 src/components/mobile-sidebar-trigger.tsx | 19 + src/components/providers/query-provider.tsx | 31 + .../providers/supabase-provider.tsx | 18 + src/components/reference/page-hover-card.tsx | 39 + src/components/search/search-palette.tsx | 654 +++ src/components/sidebar/.gitkeep | 0 src/components/sidebar/private-tree.tsx | 257 + src/components/sidebar/sidebar.tsx | 1077 ++++ src/components/sidebar/types.ts | 19 + src/components/ui/avatar.tsx | 53 + src/components/ui/badge.tsx | 46 + src/components/ui/button.tsx | 60 + src/components/ui/card.tsx | 92 + src/components/ui/command.tsx | 184 + src/components/ui/dialog.tsx | 152 + src/components/ui/drawer.tsx | 135 + src/components/ui/dropdown-menu.tsx | 257 + src/components/ui/hover-card.tsx | 30 + src/components/ui/input.tsx | 21 + src/components/ui/label.tsx | 24 + src/components/ui/popover.tsx | 48 + src/components/ui/scroll-area.tsx | 58 + src/components/ui/separator.tsx | 28 + src/components/ui/sheet.tsx | 139 + src/components/ui/skeleton.tsx | 13 + src/components/ui/tabs.tsx | 66 + src/components/ui/textarea.tsx | 18 + src/components/ui/toggle.tsx | 47 + src/hooks/.gitkeep | 0 src/hooks/use-backend-health.ts | 40 + src/hooks/use-backlinks.ts | 61 + src/hooks/use-debounced-callback.ts | 41 + src/hooks/use-document-search.ts | 34 + src/hooks/use-reference-composer.ts | 77 + src/hooks/use-sidebar-data.ts | 28 + src/lib/document-content.ts | 30 + src/lib/documents.ts | 63 + src/lib/search/record-recent.ts | 14 + src/lib/search/request.test.ts | 45 + src/lib/search/request.ts | 38 + src/lib/search/shortcuts.test.ts | 17 + src/lib/search/shortcuts.ts | 17 + src/lib/search/snippet.test.ts | 14 + src/lib/search/snippet.ts | 24 + src/lib/server-cookies.ts | 46 + src/lib/sidebar-tree.ts | 141 + src/lib/supabase/client.ts | 13 + src/lib/supabase/server.ts | 16 + src/lib/utils.ts | 6 + src/lib/workspaces.ts | 142 + src/store/.gitkeep | 0 src/store/editor-bridge.ts | 25 + src/store/page-layout.ts | 18 + src/store/search-palette.ts | 109 + src/store/sidebar.ts | 59 + src/types/document.ts | 9 + src/types/media.ts | 27 + src/types/page-options.ts | 15 + src/types/references.ts | 13 + src/types/search.ts | 47 + src/types/supabase.ts | 532 ++ start-4000.err | 0 start-4000.out | 11 + tsconfig.json | 1 + vitest.config.ts | 9 + 126 files changed, 15824 insertions(+), 94 deletions(-) create mode 100644 components.json create mode 100644 dev-3001.err create mode 100644 dev-3001.out create mode 100644 frontend-3001.err create mode 100644 frontend-3001.out create mode 100644 frontend.err create mode 100644 frontend.out create mode 100644 src/app/(app)/documents/[id]/loading.tsx create mode 100644 src/app/(app)/documents/[id]/page.tsx create mode 100644 src/app/(app)/layout.tsx create mode 100644 src/app/(auth)/login/page.tsx create mode 100644 src/app/api/.gitkeep create mode 100644 src/app/api/documents/create-child/route.ts create mode 100644 src/app/api/documents/create/route.ts create mode 100644 src/app/api/documents/delete/route.ts create mode 100644 src/app/api/documents/duplicate/route.ts create mode 100644 src/app/api/documents/embed/route.ts create mode 100644 src/app/api/documents/empty-trash/route.ts create mode 100644 src/app/api/documents/move/route.ts create mode 100644 src/app/api/documents/options/route.ts create mode 100644 src/app/api/documents/purge/route.ts create mode 100644 src/app/api/documents/restore/route.ts create mode 100644 src/app/api/documents/save/route.ts create mode 100644 src/app/api/documents/stats/route.ts create mode 100644 src/app/api/documents/title/route.ts create mode 100644 src/app/api/media/assets/route.ts create mode 100644 src/app/api/media/ocr/route.ts create mode 100644 src/app/api/media/upload/route.ts create mode 100644 src/app/api/references/backlinks/route.ts create mode 100644 src/app/api/references/record/route.ts create mode 100644 src/app/api/search/documents/route.ts create mode 100644 src/app/api/search/recent/route.ts create mode 100644 src/app/api/sidebar/route.ts create mode 100644 src/app/api/workspaces/switch/route.ts create mode 100644 src/components/bottom-toolbar.tsx create mode 100644 src/components/breadcrumb.tsx create mode 100644 src/components/common/.gitkeep create mode 100644 src/components/document-task-panel.tsx create mode 100644 src/components/editor/.gitkeep create mode 100644 src/components/editor/blocknote-editor.tsx create mode 100644 src/components/editor/blocks/AdvancedTodoBlock.tsx create mode 100644 src/components/editor/blocks/MediaBlock.tsx create mode 100644 src/components/editor/blocks/PageReferenceBlock.tsx create mode 100644 src/components/editor/blocks/ProgressBlock.tsx create mode 100644 src/components/editor/document-content.tsx create mode 100644 src/components/editor/document-history-drawer.tsx create mode 100644 src/components/editor/document-shell.tsx create mode 100644 src/components/editor/document-toc.tsx create mode 100644 src/components/editor/menus/CustomSideMenu.tsx create mode 100644 src/components/editor/menus/CustomSlashMenu.tsx create mode 100644 src/components/editor/page-backlinks-panel.tsx create mode 100644 src/components/editor/page-options-sidebar.tsx create mode 100644 src/components/editor/schema.ts create mode 100644 src/components/media/image-picker-context.tsx create mode 100644 src/components/media/image-picker-dialog.tsx create mode 100644 src/components/mindmap/.gitkeep create mode 100644 src/components/mobile-sidebar-trigger.tsx create mode 100644 src/components/providers/query-provider.tsx create mode 100644 src/components/providers/supabase-provider.tsx create mode 100644 src/components/reference/page-hover-card.tsx create mode 100644 src/components/search/search-palette.tsx create mode 100644 src/components/sidebar/.gitkeep create mode 100644 src/components/sidebar/private-tree.tsx create mode 100644 src/components/sidebar/sidebar.tsx create mode 100644 src/components/sidebar/types.ts create mode 100644 src/components/ui/avatar.tsx create mode 100644 src/components/ui/badge.tsx create mode 100644 src/components/ui/button.tsx create mode 100644 src/components/ui/card.tsx create mode 100644 src/components/ui/command.tsx create mode 100644 src/components/ui/dialog.tsx create mode 100644 src/components/ui/drawer.tsx create mode 100644 src/components/ui/dropdown-menu.tsx create mode 100644 src/components/ui/hover-card.tsx create mode 100644 src/components/ui/input.tsx create mode 100644 src/components/ui/label.tsx create mode 100644 src/components/ui/popover.tsx create mode 100644 src/components/ui/scroll-area.tsx create mode 100644 src/components/ui/separator.tsx create mode 100644 src/components/ui/sheet.tsx create mode 100644 src/components/ui/skeleton.tsx create mode 100644 src/components/ui/tabs.tsx create mode 100644 src/components/ui/textarea.tsx create mode 100644 src/components/ui/toggle.tsx create mode 100644 src/hooks/.gitkeep create mode 100644 src/hooks/use-backend-health.ts create mode 100644 src/hooks/use-backlinks.ts create mode 100644 src/hooks/use-debounced-callback.ts create mode 100644 src/hooks/use-document-search.ts create mode 100644 src/hooks/use-reference-composer.ts create mode 100644 src/hooks/use-sidebar-data.ts create mode 100644 src/lib/document-content.ts create mode 100644 src/lib/documents.ts create mode 100644 src/lib/search/record-recent.ts create mode 100644 src/lib/search/request.test.ts create mode 100644 src/lib/search/request.ts create mode 100644 src/lib/search/shortcuts.test.ts create mode 100644 src/lib/search/shortcuts.ts create mode 100644 src/lib/search/snippet.test.ts create mode 100644 src/lib/search/snippet.ts create mode 100644 src/lib/server-cookies.ts create mode 100644 src/lib/sidebar-tree.ts create mode 100644 src/lib/supabase/client.ts create mode 100644 src/lib/supabase/server.ts create mode 100644 src/lib/utils.ts create mode 100644 src/lib/workspaces.ts create mode 100644 src/store/.gitkeep create mode 100644 src/store/editor-bridge.ts create mode 100644 src/store/page-layout.ts create mode 100644 src/store/search-palette.ts create mode 100644 src/store/sidebar.ts create mode 100644 src/types/document.ts create mode 100644 src/types/media.ts create mode 100644 src/types/page-options.ts create mode 100644 src/types/references.ts create mode 100644 src/types/search.ts create mode 100644 src/types/supabase.ts create mode 100644 start-4000.err create mode 100644 start-4000.out create mode 100644 vitest.config.ts diff --git a/components.json b/components.json new file mode 100644 index 00000000..edcaef26 --- /dev/null +++ b/components.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/app/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "iconLibrary": "lucide", + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "registries": {} +} diff --git a/dev-3001.err b/dev-3001.err new file mode 100644 index 00000000..a3d18b4e --- /dev/null +++ b/dev-3001.err @@ -0,0 +1,78 @@ +F:\SOFT\MNOTE\wolai-frontend\.next\dev\server\chunks\ssr\node_modules__pnpm_ecee5139._.js: Invalid source map. Only conformant source maps can be used to find the original code. Cause: Error: sourceMapURL could not be parsed +F:\SOFT\MNOTE\wolai-frontend\.next\dev\server\chunks\ssr\[root-of-the-server]__d0423852._.js: Invalid source map. Only conformant source maps can be used to find the original code. Cause: Error: sourceMapURL could not be parsed +Error: Route "/documents/[id]" used `params.id`. `params` is a Promise and must be unwrapped with `await` or `React.use()` before accessing its properties. Learn more: https://nextjs.org/docs/messages/sync-dynamic-apis + at DocumentPage (F:\SOFT\MNOTE\wolai-frontend\.next\dev\server\chunks\ssr\[root-of-the-server]__d0423852._.js:120:150) +F:\SOFT\MNOTE\wolai-frontend\.next\dev\server\chunks\ssr\node_modules__pnpm_ecee5139._.js: Invalid source map. Only conformant source maps can be used to find the original code. Cause: Error: sourceMapURL could not be parsed +F:\SOFT\MNOTE\wolai-frontend\.next\dev\server\chunks\ssr\[root-of-the-server]__d0423852._.js: Invalid source map. Only conformant source maps can be used to find the original code. Cause: Error: sourceMapURL could not be parsed +Error: Route "/documents/[id]" used `params.id`. `params` is a Promise and must be unwrapped with `await` or `React.use()` before accessing its properties. Learn more: https://nextjs.org/docs/messages/sync-dynamic-apis + at DocumentPage (F:\SOFT\MNOTE\wolai-frontend\.next\dev\server\chunks\ssr\[root-of-the-server]__d0423852._.js:120:150) +F:\SOFT\MNOTE\wolai-frontend\.next\dev\server\chunks\ssr\node_modules__pnpm_ecee5139._.js: Invalid source map. Only conformant source maps can be used to find the original code. Cause: Error: sourceMapURL could not be parsed +F:\SOFT\MNOTE\wolai-frontend\.next\dev\server\chunks\ssr\[root-of-the-server]__d0423852._.js: Invalid source map. Only conformant source maps can be used to find the original code. Cause: Error: sourceMapURL could not be parsed +Error: Route "/documents/[id]" used `params.id`. `params` is a Promise and must be unwrapped with `await` or `React.use()` before accessing its properties. Learn more: https://nextjs.org/docs/messages/sync-dynamic-apis + at DocumentPage (F:\SOFT\MNOTE\wolai-frontend\.next\dev\server\chunks\ssr\[root-of-the-server]__d0423852._.js:120:150) +Using the user object as returned from supabase.auth.getSession() or from some supabase.auth.onAuthStateChange() events could be insecure! This value comes directly from the storage medium (usually cookies on the server) and may not be authentic. Use supabase.auth.getUser() instead which authenticates the data by contacting the Supabase Auth server. +F:\SOFT\MNOTE\wolai-frontend\.next\dev\server\chunks\ssr\node_modules__pnpm_ecee5139._.js: Invalid source map. Only conformant source maps can be used to find the original code. Cause: Error: sourceMapURL could not be parsed +F:\SOFT\MNOTE\wolai-frontend\.next\dev\server\chunks\ssr\[root-of-the-server]__d0423852._.js: Invalid source map. Only conformant source maps can be used to find the original code. Cause: Error: sourceMapURL could not be parsed +Error: Route "/documents/[id]" used `params.id`. `params` is a Promise and must be unwrapped with `await` or `React.use()` before accessing its properties. Learn more: https://nextjs.org/docs/messages/sync-dynamic-apis + at DocumentPage (F:\SOFT\MNOTE\wolai-frontend\.next\dev\server\chunks\ssr\[root-of-the-server]__d0423852._.js:120:150) +Using the user object as returned from supabase.auth.getSession() or from some supabase.auth.onAuthStateChange() events could be insecure! This value comes directly from the storage medium (usually cookies on the server) and may not be authentic. Use supabase.auth.getUser() instead which authenticates the data by contacting the Supabase Auth server. +F:\SOFT\MNOTE\wolai-frontend\.next\dev\server\chunks\ssr\node_modules__pnpm_ecee5139._.js: Invalid source map. Only conformant source maps can be used to find the original code. Cause: Error: sourceMapURL could not be parsed +F:\SOFT\MNOTE\wolai-frontend\.next\dev\server\chunks\ssr\[root-of-the-server]__d0423852._.js: Invalid source map. Only conformant source maps can be used to find the original code. Cause: Error: sourceMapURL could not be parsed +Error: Route "/documents/[id]" used `params.id`. `params` is a Promise and must be unwrapped with `await` or `React.use()` before accessing its properties. Learn more: https://nextjs.org/docs/messages/sync-dynamic-apis + at DocumentPage (F:\SOFT\MNOTE\wolai-frontend\.next\dev\server\chunks\ssr\[root-of-the-server]__d0423852._.js:120:150) +Using the user object as returned from supabase.auth.getSession() or from some supabase.auth.onAuthStateChange() events could be insecure! This value comes directly from the storage medium (usually cookies on the server) and may not be authentic. Use supabase.auth.getUser() instead which authenticates the data by contacting the Supabase Auth server. +F:\SOFT\MNOTE\wolai-frontend\.next\dev\server\chunks\ssr\node_modules__pnpm_ecee5139._.js: Invalid source map. Only conformant source maps can be used to find the original code. Cause: Error: sourceMapURL could not be parsed +F:\SOFT\MNOTE\wolai-frontend\.next\dev\server\chunks\ssr\[root-of-the-server]__d0423852._.js: Invalid source map. Only conformant source maps can be used to find the original code. Cause: Error: sourceMapURL could not be parsed +Error: Route "/documents/[id]" used `params.id`. `params` is a Promise and must be unwrapped with `await` or `React.use()` before accessing its properties. Learn more: https://nextjs.org/docs/messages/sync-dynamic-apis + at DocumentPage (F:\SOFT\MNOTE\wolai-frontend\.next\dev\server\chunks\ssr\[root-of-the-server]__d0423852._.js:120:150) +Using the user object as returned from supabase.auth.getSession() or from some supabase.auth.onAuthStateChange() events could be insecure! This value comes directly from the storage medium (usually cookies on the server) and may not be authentic. Use supabase.auth.getUser() instead which authenticates the data by contacting the Supabase Auth server. +F:\SOFT\MNOTE\wolai-frontend\.next\dev\server\chunks\ssr\node_modules__pnpm_ecee5139._.js: Invalid source map. Only conformant source maps can be used to find the original code. Cause: Error: sourceMapURL could not be parsed +F:\SOFT\MNOTE\wolai-frontend\.next\dev\server\chunks\ssr\[root-of-the-server]__d0423852._.js: Invalid source map. Only conformant source maps can be used to find the original code. Cause: Error: sourceMapURL could not be parsed +Error: Route "/documents/[id]" used `params.id`. `params` is a Promise and must be unwrapped with `await` or `React.use()` before accessing its properties. Learn more: https://nextjs.org/docs/messages/sync-dynamic-apis + at DocumentPage (F:\SOFT\MNOTE\wolai-frontend\.next\dev\server\chunks\ssr\[root-of-the-server]__d0423852._.js:120:150) +Using the user object as returned from supabase.auth.getSession() or from some supabase.auth.onAuthStateChange() events could be insecure! This value comes directly from the storage medium (usually cookies on the server) and may not be authentic. Use supabase.auth.getUser() instead which authenticates the data by contacting the Supabase Auth server. +Using the user object as returned from supabase.auth.getSession() or from some supabase.auth.onAuthStateChange() events could be insecure! This value comes directly from the storage medium (usually cookies on the server) and may not be authentic. Use supabase.auth.getUser() instead which authenticates the data by contacting the Supabase Auth server. +Using the user object as returned from supabase.auth.getSession() or from some supabase.auth.onAuthStateChange() events could be insecure! This value comes directly from the storage medium (usually cookies on the server) and may not be authentic. Use supabase.auth.getUser() instead which authenticates the data by contacting the Supabase Auth server. +Using the user object as returned from supabase.auth.getSession() or from some supabase.auth.onAuthStateChange() events could be insecure! This value comes directly from the storage medium (usually cookies on the server) and may not be authentic. Use supabase.auth.getUser() instead which authenticates the data by contacting the Supabase Auth server. +F:\SOFT\MNOTE\wolai-frontend\.next\dev\server\chunks\ssr\node_modules__pnpm_ecee5139._.js: Invalid source map. Only conformant source maps can be used to find the original code. Cause: Error: sourceMapURL could not be parsed +F:\SOFT\MNOTE\wolai-frontend\.next\dev\server\chunks\ssr\[root-of-the-server]__d0423852._.js: Invalid source map. Only conformant source maps can be used to find the original code. Cause: Error: sourceMapURL could not be parsed +Error: Route "/documents/[id]" used `params.id`. `params` is a Promise and must be unwrapped with `await` or `React.use()` before accessing its properties. Learn more: https://nextjs.org/docs/messages/sync-dynamic-apis + at DocumentPage (F:\SOFT\MNOTE\wolai-frontend\.next\dev\server\chunks\ssr\[root-of-the-server]__d0423852._.js:120:150) +Using the user object as returned from supabase.auth.getSession() or from some supabase.auth.onAuthStateChange() events could be insecure! This value comes directly from the storage medium (usually cookies on the server) and may not be authentic. Use supabase.auth.getUser() instead which authenticates the data by contacting the Supabase Auth server. +Using the user object as returned from supabase.auth.getSession() or from some supabase.auth.onAuthStateChange() events could be insecure! This value comes directly from the storage medium (usually cookies on the server) and may not be authentic. Use supabase.auth.getUser() instead which authenticates the data by contacting the Supabase Auth server. +Using the user object as returned from supabase.auth.getSession() or from some supabase.auth.onAuthStateChange() events could be insecure! This value comes directly from the storage medium (usually cookies on the server) and may not be authentic. Use supabase.auth.getUser() instead which authenticates the data by contacting the Supabase Auth server. +Using the user object as returned from supabase.auth.getSession() or from some supabase.auth.onAuthStateChange() events could be insecure! This value comes directly from the storage medium (usually cookies on the server) and may not be authentic. Use supabase.auth.getUser() instead which authenticates the data by contacting the Supabase Auth server. +Using the user object as returned from supabase.auth.getSession() or from some supabase.auth.onAuthStateChange() events could be insecure! This value comes directly from the storage medium (usually cookies on the server) and may not be authentic. Use supabase.auth.getUser() instead which authenticates the data by contacting the Supabase Auth server. +F:\SOFT\MNOTE\wolai-frontend\.next\dev\server\chunks\ssr\node_modules__pnpm_ecee5139._.js: Invalid source map. Only conformant source maps can be used to find the original code. Cause: Error: sourceMapURL could not be parsed +F:\SOFT\MNOTE\wolai-frontend\.next\dev\server\chunks\ssr\[root-of-the-server]__d0423852._.js: Invalid source map. Only conformant source maps can be used to find the original code. Cause: Error: sourceMapURL could not be parsed +Error: Route "/documents/[id]" used `params.id`. `params` is a Promise and must be unwrapped with `await` or `React.use()` before accessing its properties. Learn more: https://nextjs.org/docs/messages/sync-dynamic-apis + at DocumentPage (F:\SOFT\MNOTE\wolai-frontend\.next\dev\server\chunks\ssr\[root-of-the-server]__d0423852._.js:120:150) +Using the user object as returned from supabase.auth.getSession() or from some supabase.auth.onAuthStateChange() events could be insecure! This value comes directly from the storage medium (usually cookies on the server) and may not be authentic. Use supabase.auth.getUser() instead which authenticates the data by contacting the Supabase Auth server. +Using the user object as returned from supabase.auth.getSession() or from some supabase.auth.onAuthStateChange() events could be insecure! This value comes directly from the storage medium (usually cookies on the server) and may not be authentic. Use supabase.auth.getUser() instead which authenticates the data by contacting the Supabase Auth server. +Using the user object as returned from supabase.auth.getSession() or from some supabase.auth.onAuthStateChange() events could be insecure! This value comes directly from the storage medium (usually cookies on the server) and may not be authentic. Use supabase.auth.getUser() instead which authenticates the data by contacting the Supabase Auth server. +Using the user object as returned from supabase.auth.getSession() or from some supabase.auth.onAuthStateChange() events could be insecure! This value comes directly from the storage medium (usually cookies on the server) and may not be authentic. Use supabase.auth.getUser() instead which authenticates the data by contacting the Supabase Auth server. +F:\SOFT\MNOTE\wolai-frontend\.next\dev\server\chunks\ssr\node_modules__pnpm_ecee5139._.js: Invalid source map. Only conformant source maps can be used to find the original code. Cause: Error: sourceMapURL could not be parsed +F:\SOFT\MNOTE\wolai-frontend\.next\dev\server\chunks\ssr\[root-of-the-server]__d0423852._.js: Invalid source map. Only conformant source maps can be used to find the original code. Cause: Error: sourceMapURL could not be parsed +Error: Route "/documents/[id]" used `params.id`. `params` is a Promise and must be unwrapped with `await` or `React.use()` before accessing its properties. Learn more: https://nextjs.org/docs/messages/sync-dynamic-apis + at DocumentPage (F:\SOFT\MNOTE\wolai-frontend\.next\dev\server\chunks\ssr\[root-of-the-server]__d0423852._.js:120:150) +Using the user object as returned from supabase.auth.getSession() or from some supabase.auth.onAuthStateChange() events could be insecure! This value comes directly from the storage medium (usually cookies on the server) and may not be authentic. Use supabase.auth.getUser() instead which authenticates the data by contacting the Supabase Auth server. +Using the user object as returned from supabase.auth.getSession() or from some supabase.auth.onAuthStateChange() events could be insecure! This value comes directly from the storage medium (usually cookies on the server) and may not be authentic. Use supabase.auth.getUser() instead which authenticates the data by contacting the Supabase Auth server. +F:\SOFT\MNOTE\wolai-frontend\.next\dev\server\chunks\ssr\node_modules__pnpm_ecee5139._.js: Invalid source map. Only conformant source maps can be used to find the original code. Cause: Error: sourceMapURL could not be parsed +F:\SOFT\MNOTE\wolai-frontend\.next\dev\server\chunks\ssr\[root-of-the-server]__d0423852._.js: Invalid source map. Only conformant source maps can be used to find the original code. Cause: Error: sourceMapURL could not be parsed +Error: Route "/documents/[id]" used `params.id`. `params` is a Promise and must be unwrapped with `await` or `React.use()` before accessing its properties. Learn more: https://nextjs.org/docs/messages/sync-dynamic-apis + at DocumentPage (F:\SOFT\MNOTE\wolai-frontend\.next\dev\server\chunks\ssr\[root-of-the-server]__d0423852._.js:120:150) +Using the user object as returned from supabase.auth.getSession() or from some supabase.auth.onAuthStateChange() events could be insecure! This value comes directly from the storage medium (usually cookies on the server) and may not be authentic. Use supabase.auth.getUser() instead which authenticates the data by contacting the Supabase Auth server. +Using the user object as returned from supabase.auth.getSession() or from some supabase.auth.onAuthStateChange() events could be insecure! This value comes directly from the storage medium (usually cookies on the server) and may not be authentic. Use supabase.auth.getUser() instead which authenticates the data by contacting the Supabase Auth server. +Using the user object as returned from supabase.auth.getSession() or from some supabase.auth.onAuthStateChange() events could be insecure! This value comes directly from the storage medium (usually cookies on the server) and may not be authentic. Use supabase.auth.getUser() instead which authenticates the data by contacting the Supabase Auth server. +Using the user object as returned from supabase.auth.getSession() or from some supabase.auth.onAuthStateChange() events could be insecure! This value comes directly from the storage medium (usually cookies on the server) and may not be authentic. Use supabase.auth.getUser() instead which authenticates the data by contacting the Supabase Auth server. +Using the user object as returned from supabase.auth.getSession() or from some supabase.auth.onAuthStateChange() events could be insecure! This value comes directly from the storage medium (usually cookies on the server) and may not be authentic. Use supabase.auth.getUser() instead which authenticates the data by contacting the Supabase Auth server. +Using the user object as returned from supabase.auth.getSession() or from some supabase.auth.onAuthStateChange() events could be insecure! This value comes directly from the storage medium (usually cookies on the server) and may not be authentic. Use supabase.auth.getUser() instead which authenticates the data by contacting the Supabase Auth server. +F:\SOFT\MNOTE\wolai-frontend\.next\dev\server\chunks\ssr\2536e_@blocknote_react_dist_blocknote-react_65a5c21f.js: Invalid source map. Only conformant source maps can be used to find the original code. Cause: Error: sourceMapURL could not be parsed +F:\SOFT\MNOTE\wolai-frontend\node_modules\.pnpm\next@16.0.3_@babel+core@7.2_a6e7fe7b2107bfd5c9e45031e322c5c3\node_modules\next\dist\compiled\next-server\app-page-turbo.runtime.dev.js: Invalid source map. Only conformant source maps can be used to find the original code. Cause: Error: sourceMapURL could not be parsed +F:\SOFT\MNOTE\wolai-frontend\.next\dev\server\chunks\ssr\[root-of-the-server]__c5e4d87b._.js: Invalid source map. Only conformant source maps can be used to find the original code. Cause: Error: sourceMapURL could not be parsed + ⨯ ReferenceError: window is not defined + at (F:\SOFT\MNOTE\wolai-frontend\.next\dev\server\chunks\ssr\2536e_@blocknote_react_dist_blocknote-react_65a5c21f.js:421:9) + at Se (F:\SOFT\MNOTE\wolai-frontend\.next\dev\server\chunks\ssr\2536e_@blocknote_react_dist_blocknote-react_65a5c21f.js:419:354) + at BlockNoteEditor (F:\SOFT\MNOTE\wolai-frontend\.next\dev\server\chunks\ssr\[root-of-the-server]__c5e4d87b._.js:87:315) { + digest: '2629247923' +} diff --git a/dev-3001.out b/dev-3001.out new file mode 100644 index 00000000..ec7079e8 --- /dev/null +++ b/dev-3001.out @@ -0,0 +1,60 @@ + +> wolai-frontend@0.1.0 dev F:\SOFT\MNOTE\wolai-frontend +> next dev "--port" "3001" + + ▲ Next.js 16.0.3 (Turbopack) + - Local: http://localhost:3001 + - Network: http://192.168.121.1:3001 + - Environments: .env.local + + ✓ Starting... + ✓ Ready in 729ms + GET /login 200 in 2.3s (compile: 2.1s, render: 265ms) + GET / 200 in 613ms (compile: 365ms, render: 248ms) + GET /documents/2b01d6b7-dc39-49b9-97bb-0be59956e6fb 200 in 3.4s (compile: 2.9s, render: 473ms) + GET /documents/2b01d6b7-dc39-49b9-97bb-0be59956e6fb 200 in 442ms (compile: 26ms, render: 415ms) + GET /documents/1f256e20-0ef4-4090-9bec-954fcd799a4c 200 in 245ms (compile: 25ms, render: 220ms) + POST /api/documents/create 200 in 424ms (compile: 311ms, render: 113ms) + GET /documents/880f0a7a-e894-465b-a64d-bea1afa3da48 200 in 62ms (compile: 27ms, render: 34ms) + GET /documents/1f256e20-0ef4-4090-9bec-954fcd799a4c 200 in 68ms (compile: 22ms, render: 47ms) + GET /documents/e8b15472-3665-425e-b945-0de6bc9a15fb 200 in 56ms (compile: 22ms, render: 35ms) + GET /documents/880f0a7a-e894-465b-a64d-bea1afa3da48 200 in 56ms (compile: 22ms, render: 34ms) + GET / 307 in 297ms (compile: 2ms, render: 295ms) + GET /documents/2b01d6b7-dc39-49b9-97bb-0be59956e6fb 200 in 222ms (compile: 23ms, render: 199ms) + GET / 307 in 102ms (compile: 3ms, render: 99ms) + GET /login 200 in 53ms (compile: 3ms, render: 49ms) + GET / 200 in 20ms (compile: 3ms, render: 17ms) + GET /login 200 in 35ms (compile: 4ms, render: 31ms) + GET /login 200 in 18ms (compile: 2ms, render: 15ms) + GET / 200 in 20ms (compile: 3ms, render: 17ms) + GET /login 200 in 34ms (compile: 3ms, render: 31ms) + GET /login 200 in 18ms (compile: 2ms, render: 16ms) + GET / 307 in 108ms (compile: 3ms, render: 105ms) + GET /login 200 in 57ms (compile: 5ms, render: 52ms) + GET / 307 in 193ms (compile: 3ms, render: 190ms) + GET /documents/2b01d6b7-dc39-49b9-97bb-0be59956e6fb 200 in 125ms (compile: 23ms, render: 102ms) + GET / 200 in 27ms (compile: 4ms, render: 23ms) + GET /login 200 in 34ms (compile: 3ms, render: 31ms) + GET /login 200 in 19ms (compile: 3ms, render: 16ms) + GET / 200 in 19ms (compile: 3ms, render: 16ms) + GET /login 200 in 39ms (compile: 3ms, render: 36ms) + GET /login 200 in 19ms (compile: 3ms, render: 16ms) + GET / 200 in 20ms (compile: 3ms, render: 18ms) + GET /login 200 in 40ms (compile: 9ms, render: 31ms) + GET /login 200 in 19ms (compile: 2ms, render: 17ms) + GET / 200 in 27ms (compile: 5ms, render: 22ms) + GET /login 200 in 41ms (compile: 3ms, render: 37ms) + GET /login 200 in 21ms (compile: 2ms, render: 18ms) + GET / 200 in 23ms (compile: 3ms, render: 20ms) + GET /login 200 in 40ms (compile: 3ms, render: 37ms) + GET /login 200 in 22ms (compile: 3ms, render: 19ms) + GET /documents/2b01d6b7-dc39-49b9-97bb-0be59956e6fb 307 in 181ms (compile: 29ms, render: 152ms) + GET /login 200 in 60ms (compile: 3ms, render: 57ms) + ✓ Compiled in 90ms + GET /login 200 in 235ms (compile: 93ms, render: 142ms) + GET /login 200 in 42ms (compile: 3ms, render: 39ms) + GET / 200 in 175ms (compile: 93ms, render: 82ms) + GET /documents/2b01d6b7-dc39-49b9-97bb-0be59956e6fb 200 in 313ms (compile: 125ms, render: 188ms) + GET /documents/2b01d6b7-dc39-49b9-97bb-0be59956e6fb 200 in 67ms (compile: 4ms, render: 64ms) + GET /documents/2b01d6b7-dc39-49b9-97bb-0be59956e6fb 200 in 235ms (compile: 118ms, render: 117ms) + GET /documents/2b01d6b7-dc39-49b9-97bb-0be59956e6fb 200 in 611ms (compile: 24ms, render: 587ms) diff --git a/frontend-3001.err b/frontend-3001.err new file mode 100644 index 00000000..3438401f --- /dev/null +++ b/frontend-3001.err @@ -0,0 +1 @@ +^C \ No newline at end of file diff --git a/frontend-3001.out b/frontend-3001.out new file mode 100644 index 00000000..10e564f3 --- /dev/null +++ b/frontend-3001.out @@ -0,0 +1,11 @@ + +> wolai-frontend@0.1.0 dev F:\SOFT\MNOTE\wolai-frontend +> next dev "--port" "3001" + + ▲ Next.js 16.0.3 (Turbopack) + - Local: http://localhost:3001 + - Network: http://192.168.121.1:3001 + - Environments: .env.local + + ✓ Starting... + ✓ Ready in 876ms diff --git a/frontend.err b/frontend.err new file mode 100644 index 00000000..20dd8848 --- /dev/null +++ b/frontend.err @@ -0,0 +1,12 @@ + ⨯ Failed to start server +F:\SOFT\MNOTE\wolai-frontend\node_modules\.pnpm\next@16.0.3_@babel+core@7.2_a6e7fe7b2107bfd5c9e45031e322c5c3\node_modules\next\dist\server\lib\start-server.js: Invalid source map. Only conformant source maps can be used to find the original code. Cause: Error: sourceMapURL could not be parsed +F:\SOFT\MNOTE\wolai-frontend\node_modules\.pnpm\next@16.0.3_@babel+core@7.2_a6e7fe7b2107bfd5c9e45031e322c5c3\node_modules\next\dist\trace\trace.js: Invalid source map. Only conformant source maps can be used to find the original code. Cause: Error: sourceMapURL could not be parsed +Error: listen EADDRINUSE: address already in use :::3000 + at (Error: listen EADDRINUSE: address already in use :::3000) + at new Promise () { + code: 'EADDRINUSE', + errno: -4091, + syscall: 'listen', + address: '::', + port: 3000 +} diff --git a/frontend.out b/frontend.out new file mode 100644 index 00000000..0117337c --- /dev/null +++ b/frontend.out @@ -0,0 +1,6 @@ + +> wolai-frontend@0.1.0 dev F:\SOFT\MNOTE\wolai-frontend +> next dev "--port" "3000" + +[?25h + ELIFECYCLE  Command failed with exit code 1. diff --git a/package.json b/package.json index b4a80332..00deb845 100644 --- a/package.json +++ b/package.json @@ -6,12 +6,52 @@ "dev": "next dev", "build": "next build", "start": "next start", - "lint": "eslint" + "lint": "eslint", + "test": "vitest" }, "dependencies": { + "@blocknote/core": "^0.42.0", + "@blocknote/mantine": "0.42.0", + "@blocknote/react": "^0.42.0", + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", + "@hello-pangea/dnd": "^18.0.1", + "@hocuspocus/provider": "^2.15.3", + "@radix-ui/react-avatar": "^1.1.11", + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-hover-card": "^1.1.15", + "@radix-ui/react-label": "^2.1.8", + "@radix-ui/react-popover": "^1.1.15", + "@radix-ui/react-scroll-area": "^1.2.10", + "@radix-ui/react-separator": "^1.1.8", + "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-tabs": "^1.1.13", + "@radix-ui/react-toggle": "^1.1.10", + "@supabase/auth-helpers-nextjs": "^0.10.0", + "@supabase/auth-helpers-react": "^0.5.0", + "@supabase/supabase-js": "^2.81.1", + "@tanstack/react-query": "^5.90.10", + "@tanstack/react-query-devtools": "^5.90.2", + "@tanstack/react-virtual": "^3.11.0", + "@xyflow/react": "^12.9.3", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "cmdk": "^1.1.1", + "lucide-react": "^0.554.0", "next": "16.0.3", "react": "19.2.0", - "react-dom": "19.2.0" + "react-dom": "19.2.0", + "react-dropzone": "^14.3.8", + "react-icons": "5.5.0", + "sonner": "^2.0.7", + "tailwind-merge": "^3.4.0", + "uuid": "^13.0.0", + "vaul": "^1.1.2", + "y-protocols": "^1.0.6", + "yjs": "^13.6.27", + "zustand": "^5.0.8" }, "devDependencies": { "@tailwindcss/postcss": "^4", @@ -20,7 +60,10 @@ "@types/react-dom": "^19", "eslint": "^9", "eslint-config-next": "16.0.3", + "jsdom": "^27.2.0", "tailwindcss": "^4", - "typescript": "^5" + "tw-animate-css": "^1.4.0", + "typescript": "^5", + "vitest": "^4.0.13" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2fcaeb93..63f970ed 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,96 @@ importers: .: dependencies: + '@blocknote/core': + specifier: ^0.42.0 + version: 0.42.0(@hocuspocus/provider@2.15.3(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27))(@tiptap/extensions@3.10.7(@tiptap/core@3.10.7(@tiptap/pm@3.10.7))(@tiptap/pm@3.10.7))(@types/hast@3.0.4) + '@blocknote/mantine': + specifier: 0.42.0 + version: 0.42.0(@floating-ui/dom@1.7.4)(@hocuspocus/provider@2.15.3(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27))(@mantine/core@8.3.8(@mantine/hooks@8.3.8(react@19.2.0))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@mantine/hooks@8.3.8(react@19.2.0))(@mantine/utils@6.0.22(react@19.2.0))(@tiptap/extensions@3.10.7(@tiptap/core@3.10.7(@tiptap/pm@3.10.7))(@tiptap/pm@3.10.7))(@types/hast@3.0.4)(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@blocknote/react': + specifier: ^0.42.0 + version: 0.42.0(@floating-ui/dom@1.7.4)(@hocuspocus/provider@2.15.3(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27))(@tiptap/extensions@3.10.7(@tiptap/core@3.10.7(@tiptap/pm@3.10.7))(@tiptap/pm@3.10.7))(@types/hast@3.0.4)(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@dnd-kit/core': + specifier: ^6.3.1 + version: 6.3.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@dnd-kit/sortable': + specifier: ^10.0.0 + version: 10.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react@19.2.0) + '@dnd-kit/utilities': + specifier: ^3.2.2 + version: 3.2.2(react@19.2.0) + '@hello-pangea/dnd': + specifier: ^18.0.1 + version: 18.0.1(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@hocuspocus/provider': + specifier: ^2.15.3 + version: 2.15.3(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27) + '@radix-ui/react-avatar': + specifier: ^1.1.11 + version: 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-dialog': + specifier: ^1.1.15 + version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-dropdown-menu': + specifier: ^2.1.16 + version: 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-hover-card': + specifier: ^1.1.15 + version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-label': + specifier: ^2.1.8 + version: 2.1.8(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-popover': + specifier: ^1.1.15 + version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-scroll-area': + specifier: ^1.2.10 + version: 1.2.10(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-separator': + specifier: ^1.1.8 + version: 1.1.8(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': + specifier: ^1.2.4 + version: 1.2.4(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-tabs': + specifier: ^1.1.13 + version: 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-toggle': + specifier: ^1.1.10 + version: 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@supabase/auth-helpers-nextjs': + specifier: ^0.10.0 + version: 0.10.0(@supabase/supabase-js@2.81.1) + '@supabase/auth-helpers-react': + specifier: ^0.5.0 + version: 0.5.0(@supabase/supabase-js@2.81.1) + '@supabase/supabase-js': + specifier: ^2.81.1 + version: 2.81.1 + '@tanstack/react-query': + specifier: ^5.90.10 + version: 5.90.10(react@19.2.0) + '@tanstack/react-query-devtools': + specifier: ^5.90.2 + version: 5.90.2(@tanstack/react-query@5.90.10(react@19.2.0))(react@19.2.0) + '@tanstack/react-virtual': + specifier: ^3.11.0 + version: 3.13.12(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@xyflow/react': + specifier: ^12.9.3 + version: 12.9.3(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + class-variance-authority: + specifier: ^0.7.1 + version: 0.7.1 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + cmdk: + specifier: ^1.1.1 + version: 1.1.1(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + lucide-react: + specifier: ^0.554.0 + version: 0.554.0(react@19.2.0) next: specifier: 16.0.3 version: 16.0.3(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) @@ -17,6 +107,33 @@ importers: react-dom: specifier: 19.2.0 version: 19.2.0(react@19.2.0) + react-dropzone: + specifier: ^14.3.8 + version: 14.3.8(react@19.2.0) + react-icons: + specifier: 5.5.0 + version: 5.5.0(react@19.2.0) + sonner: + specifier: ^2.0.7 + version: 2.0.7(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + tailwind-merge: + specifier: ^3.4.0 + version: 3.4.0 + uuid: + specifier: ^13.0.0 + version: 13.0.0 + vaul: + specifier: ^1.1.2 + version: 1.1.2(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + y-protocols: + specifier: ^1.0.6 + version: 1.0.6(yjs@13.6.27) + yjs: + specifier: ^13.6.27 + version: 13.6.27 + zustand: + specifier: ^5.0.8 + version: 5.0.8(@types/react@19.2.5)(react@19.2.0)(use-sync-external-store@1.6.0(react@19.2.0)) devDependencies: '@tailwindcss/postcss': specifier: ^4 @@ -36,19 +153,40 @@ importers: eslint-config-next: specifier: 16.0.3 version: 16.0.3(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + jsdom: + specifier: ^27.2.0 + version: 27.2.0 tailwindcss: specifier: ^4 version: 4.1.17 + tw-animate-css: + specifier: ^1.4.0 + version: 1.4.0 typescript: specifier: ^5 version: 5.9.3 + vitest: + specifier: ^4.0.13 + version: 4.0.13(@types/debug@4.1.12)(@types/node@20.19.25)(jiti@2.6.1)(jsdom@27.2.0)(lightningcss@1.30.2) packages: + '@acemir/cssom@0.9.23': + resolution: {integrity: sha512-2kJ1HxBKzPLbmhZpxBiTZggjtgCwKg1ma5RHShxvd6zgqhDEdEkzpiwe7jLkI2p2BrZvFCXIihdoMkl1H39VnA==} + '@alloc/quick-lru@5.2.0': resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} + '@asamuzakjp/css-color@4.1.0': + resolution: {integrity: sha512-9xiBAtLn4aNsa4mDnpovJvBn72tNEIACyvlqaNJ+ADemR+yeMJWnBudOi2qGDviJa7SwcDOU/TRh5dnET7qk0w==} + + '@asamuzakjp/dom-selector@6.7.4': + resolution: {integrity: sha512-buQDjkm+wDPXd6c13534URWZqbz0RP5PAhXZ+LIoa5LgwInT9HVJvGIJivg75vi8I13CxDGdTnz+aY5YUJlIAA==} + + '@asamuzakjp/nwsapi@2.3.9': + resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} + '@babel/code-frame@7.27.1': resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} @@ -104,6 +242,10 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/runtime@7.28.4': + resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} + engines: {node: '>=6.9.0'} + '@babel/template@7.27.2': resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} engines: {node: '>=6.9.0'} @@ -116,6 +258,83 @@ packages: resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} engines: {node: '>=6.9.0'} + '@blocknote/core@0.42.0': + resolution: {integrity: sha512-V478pyJFTJxdKmVo2ICqKNVzuRNgBOK3Zg3Ut1AKozrOGJ2mB4h1esDncK01P/DSCLDro6TmSobOrVAF55w1uA==} + peerDependencies: + '@hocuspocus/provider': ^2.15.2 + peerDependenciesMeta: + '@hocuspocus/provider': + optional: true + + '@blocknote/mantine@0.42.0': + resolution: {integrity: sha512-N4sbfFm6mJaS9oRBOibyUUDdWDns1350m3CAUJXJzmTCp0ugOlVm+TPzMj2ENnALwkYEDtLWIDXrfen7XQ8r6Q==} + peerDependencies: + '@mantine/core': ^8.3.4 + '@mantine/hooks': ^8.3.4 + '@mantine/utils': ^6.0.22 + react: ^18.0 || ^19.0 || >= 19.0.0-rc + react-dom: ^18.0 || ^19.0 || >= 19.0.0-rc + + '@blocknote/react@0.42.0': + resolution: {integrity: sha512-6o2lUdzQAffe20mkb8q+KnNZLQ5MjAMztnMV14I0eraSpdFfNt11msASbL6uYXTwLL8mLSGaMUojcR8mLv3u1w==} + peerDependencies: + react: ^18.0 || ^19.0 || >= 19.0.0-rc + react-dom: ^18.0 || ^19.0 || >= 19.0.0-rc + + '@csstools/color-helpers@5.1.0': + resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==} + engines: {node: '>=18'} + + '@csstools/css-calc@2.1.4': + resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-color-parser@3.1.0': + resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-parser-algorithms@3.0.5': + resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-syntax-patches-for-csstree@1.0.17': + resolution: {integrity: sha512-LCC++2h8pLUSPY+EsZmrrJ1EOUu+5iClpEiDhhdw3zRJpPbABML/N5lmRuBHjxtKm9VnRcsUzioyD0sekFMF0A==} + engines: {node: '>=18'} + + '@csstools/css-tokenizer@3.0.4': + resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} + engines: {node: '>=18'} + + '@dnd-kit/accessibility@3.1.1': + resolution: {integrity: sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==} + peerDependencies: + react: '>=16.8.0' + + '@dnd-kit/core@6.3.1': + resolution: {integrity: sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@dnd-kit/sortable@10.0.0': + resolution: {integrity: sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==} + peerDependencies: + '@dnd-kit/core': ^6.3.0 + react: '>=16.8.0' + + '@dnd-kit/utilities@3.2.2': + resolution: {integrity: sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==} + peerDependencies: + react: '>=16.8.0' + '@emnapi/core@1.7.1': resolution: {integrity: sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==} @@ -125,6 +344,165 @@ packages: '@emnapi/wasi-threads@1.1.0': resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} + '@emoji-mart/data@1.2.1': + resolution: {integrity: sha512-no2pQMWiBy6gpBEiqGeU77/bFejDqUTRY7KX+0+iur13op3bqUsXdnwoZs6Xb1zbv0gAj5VvS1PWoUUckSr5Dw==} + + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.9.0': resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -163,6 +541,42 @@ packages: resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@floating-ui/core@1.7.3': + resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==} + + '@floating-ui/dom@1.7.4': + resolution: {integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==} + + '@floating-ui/react-dom@2.1.6': + resolution: {integrity: sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/react@0.27.16': + resolution: {integrity: sha512-9O8N4SeG2z++TSM8QA/KTeKFBVCNEz/AGS7gWPJf6KFRzmRWixFRnCnkPHRDwSVZW6QPDO6uT0P2SpWNKCc9/g==} + peerDependencies: + react: '>=17.0.0' + react-dom: '>=17.0.0' + + '@floating-ui/utils@0.2.10': + resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} + + '@hello-pangea/dnd@18.0.1': + resolution: {integrity: sha512-xojVWG8s/TGrKT1fC8K2tIWeejJYTAeJuj36zM//yEm/ZrnZUSFGS15BpO+jGZT1ybWvyXmeDJwPYb4dhWlbZQ==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + + '@hocuspocus/common@2.15.3': + resolution: {integrity: sha512-Rzh1HF0a2o/tf90A3w2XNdXd9Ym3aQzMDfD3lAUONCX9B9QOdqdyiORrj6M25QEaJrEIbXFy8LtAFcL0wRdWzA==} + + '@hocuspocus/provider@2.15.3': + resolution: {integrity: sha512-oadN05m+KL4ylNKVo5YspNG4MXkT2Y+FUFzrgigpQeTjQibkPUwCNmUnkUxMgrGRgxb+O0lJCfirFIJMxedctA==} + peerDependencies: + y-protocols: ^1.0.6 + yjs: ^13.6.8 + '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -332,6 +746,26 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@lifeomic/attempt@3.1.0': + resolution: {integrity: sha512-QZqem4QuAnAyzfz+Gj5/+SLxqwCAw2qmt7732ZXodr6VDWGeYLG6w1i/vYLa55JQM9wRuBKLmXmiZ2P0LtE5rw==} + + '@mantine/core@8.3.8': + resolution: {integrity: sha512-UM3Za7Yl0FzbZ2zPgHwNyCpLgtSqkAi8ku13+gRS/6JB0FjwSkMwibERUqQIpwqAHdR5KNmIohjuqHu8guJowg==} + peerDependencies: + '@mantine/hooks': 8.3.8 + react: ^18.x || ^19.x + react-dom: ^18.x || ^19.x + + '@mantine/hooks@8.3.8': + resolution: {integrity: sha512-2YCUk5IWz+Ebi7VpbdscUz1MwulyaVPKr236ugMfpK0PFwsun4aBaLCAc8UeMGP0LtoSkuFvnsCPR4U6rhNfeQ==} + peerDependencies: + react: ^18.x || ^19.x + + '@mantine/utils@6.0.22': + resolution: {integrity: sha512-RSKlNZvxhMCkOFZ6slbYvZYbWjHUM+PxDQnupIOxIdsTZQQjx/BFfrfJ7kQFOP+g7MtpOds8weAetEs5obwMOQ==} + peerDependencies: + react: '>=16.8.0' + '@napi-rs/wasm-runtime@0.2.12': resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} @@ -405,9 +839,599 @@ packages: resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} engines: {node: '>=12.4.0'} + '@radix-ui/number@1.1.1': + resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} + + '@radix-ui/primitive@1.1.3': + resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} + + '@radix-ui/react-arrow@1.1.7': + resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-avatar@1.1.11': + resolution: {integrity: sha512-0Qk603AHGV28BOBO34p7IgD5m+V5Sg/YovfayABkoDDBM5d3NCx0Mp4gGrjzLGes1jV5eNOE1r3itqOR33VC6Q==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-collection@1.1.7': + resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-compose-refs@1.1.2': + resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-context@1.1.2': + resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-context@1.1.3': + resolution: {integrity: sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dialog@1.1.15': + resolution: {integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-direction@1.1.1': + resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dismissable-layer@1.1.11': + resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-dropdown-menu@2.1.16': + resolution: {integrity: sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-focus-guards@1.1.3': + resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-focus-scope@1.1.7': + resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-hover-card@1.1.15': + resolution: {integrity: sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-id@1.1.1': + resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-label@2.1.8': + resolution: {integrity: sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-menu@2.1.16': + resolution: {integrity: sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-popover@1.1.15': + resolution: {integrity: sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-popper@1.2.8': + resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-portal@1.1.9': + resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-presence@1.1.5': + resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.1.3': + resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.1.4': + resolution: {integrity: sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-roving-focus@1.1.11': + resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-scroll-area@1.2.10': + resolution: {integrity: sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-separator@1.1.8': + resolution: {integrity: sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slot@1.2.3': + resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-slot@1.2.4': + resolution: {integrity: sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-tabs@1.1.13': + resolution: {integrity: sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-toggle@1.1.10': + resolution: {integrity: sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-use-callback-ref@1.1.1': + resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-controllable-state@1.2.2': + resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-effect-event@0.0.2': + resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-escape-keydown@1.1.1': + resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-is-hydrated@0.1.0': + resolution: {integrity: sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-layout-effect@1.1.1': + resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-rect@1.1.1': + resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-size@1.1.1': + resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/rect@1.1.1': + resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} + + '@remirror/core-constants@3.0.0': + resolution: {integrity: sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==} + + '@rollup/rollup-android-arm-eabi@4.53.3': + resolution: {integrity: sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.53.3': + resolution: {integrity: sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.53.3': + resolution: {integrity: sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.53.3': + resolution: {integrity: sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.53.3': + resolution: {integrity: sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.53.3': + resolution: {integrity: sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.53.3': + resolution: {integrity: sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.53.3': + resolution: {integrity: sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.53.3': + resolution: {integrity: sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.53.3': + resolution: {integrity: sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.53.3': + resolution: {integrity: sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.53.3': + resolution: {integrity: sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.53.3': + resolution: {integrity: sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.53.3': + resolution: {integrity: sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.53.3': + resolution: {integrity: sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.53.3': + resolution: {integrity: sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.53.3': + resolution: {integrity: sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openharmony-arm64@4.53.3': + resolution: {integrity: sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.53.3': + resolution: {integrity: sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.53.3': + resolution: {integrity: sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.53.3': + resolution: {integrity: sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.53.3': + resolution: {integrity: sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==} + cpu: [x64] + os: [win32] + '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} + '@shikijs/types@3.13.0': + resolution: {integrity: sha512-oM9P+NCFri/mmQ8LoFGVfVyemm5Hi27330zuOBp0annwJdKH1kOLndw3zCtAVDehPLg9fKqoEx3Ht/wNZxolfw==} + + '@shikijs/vscode-textmate@10.0.2': + resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + + '@standard-schema/spec@1.0.0': + resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} + + '@supabase/auth-helpers-nextjs@0.10.0': + resolution: {integrity: sha512-2dfOGsM4yZt0oS4TPiE7bD4vf7EVz7NRz/IJrV6vLg0GP7sMUx8wndv2euLGq4BjN9lUCpu6DG/uCC8j+ylwPg==} + deprecated: This package is now deprecated - please use the @supabase/ssr package instead. + peerDependencies: + '@supabase/supabase-js': ^2.39.8 + + '@supabase/auth-helpers-react@0.5.0': + resolution: {integrity: sha512-5QSaV2CGuhDhd7RlQCoviVEAYsP7XnrFMReOcBazDvVmqSIyjKcDwhLhWvnrxMOq5qjOaA44MHo7wXqDiF0puQ==} + deprecated: This package is now deprecated - please use the @supabase/ssr package instead. + peerDependencies: + '@supabase/supabase-js': ^2.39.8 + + '@supabase/auth-helpers-shared@0.7.0': + resolution: {integrity: sha512-FBFf2ei2R7QC+B/5wWkthMha8Ca2bWHAndN+syfuEUUfufv4mLcAgBCcgNg5nJR8L0gZfyuaxgubtOc9aW3Cpg==} + deprecated: This package is now deprecated - please use the @supabase/ssr package instead. + peerDependencies: + '@supabase/supabase-js': ^2.39.8 + + '@supabase/auth-js@2.81.1': + resolution: {integrity: sha512-K20GgiSm9XeRLypxYHa5UCnybWc2K0ok0HLbqCej/wRxDpJxToXNOwKt0l7nO8xI1CyQ+GrNfU6bcRzvdbeopQ==} + engines: {node: '>=20.0.0'} + + '@supabase/functions-js@2.81.1': + resolution: {integrity: sha512-sYgSO3mlgL0NvBFS3oRfCK4OgKGQwuOWJLzfPyWg0k8MSxSFSDeN/JtrDJD5GQrxskP6c58+vUzruBJQY78AqQ==} + engines: {node: '>=20.0.0'} + + '@supabase/postgrest-js@2.81.1': + resolution: {integrity: sha512-DePpUTAPXJyBurQ4IH2e42DWoA+/Qmr5mbgY4B6ZcxVc/ZUKfTVK31BYIFBATMApWraFc8Q/Sg+yxtfJ3E0wSg==} + engines: {node: '>=20.0.0'} + + '@supabase/realtime-js@2.81.1': + resolution: {integrity: sha512-ViQ+Kxm8BuUP/TcYmH9tViqYKGSD1LBjdqx2p5J+47RES6c+0QHedM0PPAjthMdAHWyb2LGATE9PD2++2rO/tw==} + engines: {node: '>=20.0.0'} + + '@supabase/storage-js@2.81.1': + resolution: {integrity: sha512-UNmYtjnZnhouqnbEMC1D5YJot7y0rIaZx7FG2Fv8S3hhNjcGVvO+h9We/tggi273BFkiahQPS/uRsapo1cSapw==} + engines: {node: '>=20.0.0'} + + '@supabase/supabase-js@2.81.1': + resolution: {integrity: sha512-KSdY7xb2L0DlLmlYzIOghdw/na4gsMcqJ8u4sD6tOQJr+x3hLujU9s4R8N3ob84/1bkvpvlU5PYKa1ae+OICnw==} + engines: {node: '>=20.0.0'} + '@swc/helpers@0.5.15': resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} @@ -499,21 +1523,189 @@ packages: '@tailwindcss/postcss@4.1.17': resolution: {integrity: sha512-+nKl9N9mN5uJ+M7dBOOCzINw94MPstNR/GtIhz1fpZysxL/4a+No64jCBD6CPN+bIHWFx3KWuu8XJRrj/572Dw==} + '@tanstack/query-core@5.90.10': + resolution: {integrity: sha512-EhZVFu9rl7GfRNuJLJ3Y7wtbTnENsvzp+YpcAV7kCYiXni1v8qZh++lpw4ch4rrwC0u/EZRnBHIehzCGzwXDSQ==} + + '@tanstack/query-devtools@5.90.1': + resolution: {integrity: sha512-GtINOPjPUH0OegJExZ70UahT9ykmAhmtNVcmtdnOZbxLwT7R5OmRztR5Ahe3/Cu7LArEmR6/588tAycuaWb1xQ==} + + '@tanstack/react-query-devtools@5.90.2': + resolution: {integrity: sha512-vAXJzZuBXtCQtrY3F/yUNJCV4obT/A/n81kb3+YqLbro5Z2+phdAbceO+deU3ywPw8B42oyJlp4FhO0SoivDFQ==} + peerDependencies: + '@tanstack/react-query': ^5.90.2 + react: ^18 || ^19 + + '@tanstack/react-query@5.90.10': + resolution: {integrity: sha512-BKLss9Y8PQ9IUjPYQiv3/Zmlx92uxffUOX8ZZNoQlCIZBJPT5M+GOMQj7xislvVQ6l1BstBjcX0XB/aHfFYVNw==} + peerDependencies: + react: ^18 || ^19 + + '@tanstack/react-virtual@3.13.12': + resolution: {integrity: sha512-Gd13QdxPSukP8ZrkbgS2RwoZseTTbQPLnQEn7HY/rqtM+8Zt95f7xKC7N0EsKs7aoz0WzZ+fditZux+F8EzYxA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@tanstack/virtual-core@3.13.12': + resolution: {integrity: sha512-1YBOJfRHV4sXUmWsFSf5rQor4Ss82G8dQWLRbnk3GA4jeP8hQt1hxXh0tmflpC0dz3VgEv/1+qwPyLeWkQuPFA==} + + '@tiptap/core@3.10.7': + resolution: {integrity: sha512-4rD3oHkXNOS6Fxm0mr+ECyq35iMFnnAXheIO+UsQbOexwTxn2yZ5Q1rQiFKcCf+p+rrg1yt8TtxQPM8VLWS+1g==} + peerDependencies: + '@tiptap/pm': ^3.10.7 + + '@tiptap/extension-bold@3.10.7': + resolution: {integrity: sha512-NWjOIIZdxUSkWLQrEY4Tg60MzS6RGt/1aLnwTyFFzFFShzOmd/xzxp0fRS+p79ZKNcQa9OKgnrlS4xuRq8WOdQ==} + peerDependencies: + '@tiptap/core': ^3.10.7 + + '@tiptap/extension-bubble-menu@3.10.7': + resolution: {integrity: sha512-ezsNpClKQ4Bq6R+Y/jGcmxhSBuYYOCGXV72yy3SlX1w6seA/I8h27ktWy9zAD2RPX560NzpZEyBjaASL3961sQ==} + peerDependencies: + '@tiptap/core': ^3.10.7 + '@tiptap/pm': ^3.10.7 + + '@tiptap/extension-code@3.10.7': + resolution: {integrity: sha512-POK3CCy29LoRI6JVvFRVAmH2G90a7pKJT8sbqOaX1WKmLLDt7drUxGgBNnz/cBXJQHPnXZgRq/P8ZQPISklT7Q==} + peerDependencies: + '@tiptap/core': ^3.10.7 + + '@tiptap/extension-floating-menu@3.10.7': + resolution: {integrity: sha512-yuTIGDbx0Q2IWOUrkhVQ/i1fU0Qi+8fCS8jkGB34/+3nbhtqXNYfFajpeaU9rkcCJqXH4aiFJdSGy44kCnYP2g==} + peerDependencies: + '@floating-ui/dom': ^1.0.0 + '@tiptap/core': ^3.10.7 + '@tiptap/pm': ^3.10.7 + + '@tiptap/extension-gapcursor@3.10.7': + resolution: {integrity: sha512-1VDNX+4ZCKxuoj6nRTZDwHjPYhuSdELYYCSfxscojlwexPxCLcgqOt71xdgnQXW5Hv6ACT4OrGGYcGTupudOHg==} + peerDependencies: + '@tiptap/extensions': ^3.10.7 + + '@tiptap/extension-history@3.10.7': + resolution: {integrity: sha512-QP0JZbA3jm3GY/VZNM3KC5bMkraj3GHglw1HAr1jcwIjwkvGS+Q6SmrfBTovTzT7y23QpZAPNKpToxy7X76C7A==} + peerDependencies: + '@tiptap/extensions': ^3.10.7 + + '@tiptap/extension-horizontal-rule@3.10.7': + resolution: {integrity: sha512-V9uWb341QUBDDbR3aoSs3Sx0PQQaKwZ/ESVEE03El9rkIrf8g5K82x8/M0nvSOvGobt6oRyI/rgbj196YQuXiQ==} + peerDependencies: + '@tiptap/core': ^3.10.7 + '@tiptap/pm': ^3.10.7 + + '@tiptap/extension-italic@3.10.7': + resolution: {integrity: sha512-1CQgHNm51xDyZI188f5xKLcUIjRS+2cyZgS9XwKwIU/3QOsiKsNC+cBc4VmN3aR0A01NjK0ch0MjeKkPPWUt5A==} + peerDependencies: + '@tiptap/core': ^3.10.7 + + '@tiptap/extension-link@3.10.7': + resolution: {integrity: sha512-AIgrtveTQ5QyRpcic2MVSuv9aOaN0n+swdZPvi8XREZX/uf1SU4dYU7p0dNChhcn53GGPDNVRTQXX4YdEAZFQQ==} + peerDependencies: + '@tiptap/core': ^3.10.7 + '@tiptap/pm': ^3.10.7 + + '@tiptap/extension-paragraph@3.10.7': + resolution: {integrity: sha512-53+nCxNaKcmeqQ+aWrSauEWywuWPp8qkUTOO2rHlpmM+rk/1bv3IZePKQ2JtHZzYCeRd3xOC33kl60HE7EwakQ==} + peerDependencies: + '@tiptap/core': ^3.10.7 + + '@tiptap/extension-strike@3.10.7': + resolution: {integrity: sha512-pZMdQhChv59jsahvmjiJjSTPM05J6EHAX/GPdA9w8xSKy73899MhIhWJ7yt2CJEPjwn3ixnomIPhMjxBkizv+g==} + peerDependencies: + '@tiptap/core': ^3.10.7 + + '@tiptap/extension-text@3.10.7': + resolution: {integrity: sha512-b7Rjil/uqiabWnRHyd1P84rWD2XRyZZSrmIAO9mDMD/jB2bE+f7rDJcHG76GF03UicDhEEEf2/8mz0dMLa6mUA==} + peerDependencies: + '@tiptap/core': ^3.10.7 + + '@tiptap/extension-underline@3.10.7': + resolution: {integrity: sha512-yBL81xdbjT5Y7acoBqWpnH/SoH3bpgqaLvJBG3NNk+mdLB5HjBWTlPLKjvjQV0HRN5bZ+RJWeiRnQk1ahcfmQA==} + peerDependencies: + '@tiptap/core': ^3.10.7 + + '@tiptap/extensions@3.10.7': + resolution: {integrity: sha512-jYYR7NA7t2hdyJmSLYVAJ3usyIOZ2mfFqPCCHbSn/k3jqmGaPFZuxJSwmYjfmTxisZ9rGn+49/YJF2y/Yej/0Q==} + peerDependencies: + '@tiptap/core': ^3.10.7 + '@tiptap/pm': ^3.10.7 + + '@tiptap/pm@3.10.7': + resolution: {integrity: sha512-/iiurioqSukJk6CrEtfRpdOEafDybyVPToAllgn7i2XcusXSxJSX+K0GUndMUwVR+UqVOCyMYBTRTnE0hdQqgA==} + + '@tiptap/react@3.10.7': + resolution: {integrity: sha512-hhKj62zvs/mSu5HlcmZDRFHVHCjJ6v6/7vB45MTAziP+cZ0+CEbEh2rnGNRNwooumWwm5pWdkVqI1efp7GtnUA==} + peerDependencies: + '@tiptap/core': ^3.10.7 + '@tiptap/pm': ^3.10.7 + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + '@types/react-dom': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/d3-color@3.1.3': + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + + '@types/d3-drag@3.0.7': + resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==} + + '@types/d3-interpolate@3.0.4': + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + + '@types/d3-selection@3.0.11': + resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==} + + '@types/d3-transition@3.0.9': + resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==} + + '@types/d3-zoom@3.0.8': + resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==} + + '@types/debug@4.1.12': + resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + '@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/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + '@types/node@20.19.25': resolution: {integrity: sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==} + '@types/phoenix@1.6.6': + resolution: {integrity: sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A==} + '@types/react-dom@19.2.3': resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} peerDependencies: @@ -522,6 +1714,15 @@ packages: '@types/react@19.2.5': resolution: {integrity: sha512-keKxkZMqnDicuvFoJbzrhbtdLSPhj/rZThDlKWCDbgXmUg0rEUFtRssDXKYmtXluZlIqiC5VqkCgRwzuyLHKHw==} + '@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/ws@8.18.1': + resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} + '@typescript-eslint/eslint-plugin@8.47.0': resolution: {integrity: sha512-fe0rz9WJQ5t2iaLfdbDc9T80GJy0AeO453q8C3YCilnGozvOyCG5t+EZtg7j7D88+c3FipfP/x+wzGnh1xp8ZA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -581,6 +1782,9 @@ packages: resolution: {integrity: sha512-SIV3/6eftCy1bNzCQoPmbWsRLujS8t5iDIZ4spZOBHqrM+yfX2ogg8Tt3PDTAVKw3sSCiUgg30uOAvK2r9zGjQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + '@unrs/resolver-binding-android-arm-eabi@1.11.1': resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==} cpu: [arm] @@ -676,6 +1880,44 @@ packages: cpu: [x64] os: [win32] + '@vitest/expect@4.0.13': + resolution: {integrity: sha512-zYtcnNIBm6yS7Gpr7nFTmq8ncowlMdOJkWLqYvhr/zweY6tFbDkDi8BPPOeHxEtK1rSI69H7Fd4+1sqvEGli6w==} + + '@vitest/mocker@4.0.13': + resolution: {integrity: sha512-eNCwzrI5djoauklwP1fuslHBjrbR8rqIVbvNlAnkq1OTa6XT+lX68mrtPirNM9TnR69XUPt4puBCx2Wexseylg==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.0.13': + resolution: {integrity: sha512-ooqfze8URWbI2ozOeLDMh8YZxWDpGXoeY3VOgcDnsUxN0jPyPWSUvjPQWqDGCBks+opWlN1E4oP1UYl3C/2EQA==} + + '@vitest/runner@4.0.13': + resolution: {integrity: sha512-9IKlAru58wcVaWy7hz6qWPb2QzJTKt+IOVKjAx5vb5rzEFPTL6H4/R9BMvjZ2ppkxKgTrFONEJFtzvnyEpiT+A==} + + '@vitest/snapshot@4.0.13': + resolution: {integrity: sha512-hb7Usvyika1huG6G6l191qu1urNPsq1iFc2hmdzQY3F5/rTgqQnwwplyf8zoYHkpt7H6rw5UfIw6i/3qf9oSxQ==} + + '@vitest/spy@4.0.13': + resolution: {integrity: sha512-hSu+m4se0lDV5yVIcNWqjuncrmBgwaXa2utFLIrBkQCQkt+pSwyZTPFQAZiiF/63j8jYa8uAeUZ3RSfcdWaYWw==} + + '@vitest/utils@4.0.13': + resolution: {integrity: sha512-ydozWyQ4LZuu8rLp47xFUWis5VOKMdHjXCWhs1LuJsTNKww+pTHQNK4e0assIB9K80TxFyskENL6vCu3j34EYA==} + + '@xyflow/react@12.9.3': + resolution: {integrity: sha512-PSWoJ8vHiEqSIkLIkge+0eiHWiw4C6dyFDA03VKWJkqbU4A13VlDIVwKqf/Znuysn2GQw/zA61zpHE4rGgax7Q==} + peerDependencies: + react: '>=17' + react-dom: '>=17' + + '@xyflow/system@0.0.73': + resolution: {integrity: sha512-C2ymH2V4mYDkdVSiRx0D7R0s3dvfXiupVBcko6tXP5K4tVdSBMo22/e3V9yRNdn+2HQFv44RFKzwOyCcUUDAVQ==} + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -686,6 +1928,10 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} @@ -696,6 +1942,10 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + aria-hidden@1.2.6: + resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} + engines: {node: '>=10'} + aria-query@5.3.2: resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} engines: {node: '>= 0.4'} @@ -732,6 +1982,10 @@ packages: resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} engines: {node: '>= 0.4'} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + ast-types-flow@0.0.8: resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} @@ -739,6 +1993,10 @@ packages: resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} engines: {node: '>= 0.4'} + attr-accept@2.2.5: + resolution: {integrity: sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==} + engines: {node: '>=4'} + available-typed-arrays@1.0.7: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} @@ -751,6 +2009,9 @@ packages: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} engines: {node: '>= 0.4'} + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -758,6 +2019,9 @@ packages: resolution: {integrity: sha512-sXdt2elaVnhpDNRDz+1BDx1JQoJRuNk7oVlAlbGiFkLikHCAQiccexF/9e91zVi6RCgqspl04aP+6Cnl9zRLrA==} hasBin: true + bidi-js@1.0.3: + resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} + brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} @@ -792,13 +2056,45 @@ packages: caniuse-lite@1.0.30001755: resolution: {integrity: sha512-44V+Jm6ctPj7R52Na4TLi3Zri4dWUljJd+RDm+j8LtNCc/ihLCT+X1TzoOAkRETEWqjuLnh9581Tl80FvK7jVA==} + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + + chai@6.2.1: + resolution: {integrity: sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg==} + engines: {node: '>=18'} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + + class-variance-authority@0.7.1: + resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + + classcat@5.0.5: + resolution: {integrity: sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==} + client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + cmdk@1.1.1: + resolution: {integrity: sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==} + peerDependencies: + react: ^18 || ^19 || ^19.0.0-rc + react-dom: ^18 || ^19 || ^19.0.0-rc + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -806,22 +2102,81 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + crelt@1.0.6: + resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + css-box-model@1.2.1: + resolution: {integrity: sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==} + + css-tree@3.1.0: + resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + cssstyle@5.3.3: + resolution: {integrity: sha512-OytmFH+13/QXONJcC75QNdMtKpceNk3u8ThBjyyYjkEcy/ekBwR1mMAuNvi3gdBPW3N5TlCzQ0WZw8H0lN/bDw==} + engines: {node: '>=20'} + csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-dispatch@3.0.1: + resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} + engines: {node: '>=12'} + + d3-drag@3.0.0: + resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} + engines: {node: '>=12'} + + d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-selection@3.0.0: + resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + + d3-transition@3.0.1: + resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==} + engines: {node: '>=12'} + peerDependencies: + d3-selection: 2 - 3 + + d3-zoom@3.0.0: + resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} + engines: {node: '>=12'} + damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} + data-urls@6.0.0: + resolution: {integrity: sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==} + engines: {node: '>=20'} + data-view-buffer@1.0.2: resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} engines: {node: '>= 0.4'} @@ -851,6 +2206,12 @@ packages: supports-color: optional: true + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + + decode-named-character-reference@1.2.0: + resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==} + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -862,10 +2223,20 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} + detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + doctrine@2.1.0: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} engines: {node: '>=0.10.0'} @@ -877,6 +2248,9 @@ packages: electron-to-chromium@1.5.255: resolution: {integrity: sha512-Z9oIp4HrFF/cZkDPMpz2XSuVpc1THDpT4dlmATFlJUIBVCy9Vap5/rIXsASP1CscBacBqhabwh8vLctqBwEerQ==} + emoji-mart@5.6.0: + resolution: {integrity: sha512-eJp3QRe79pjwa+duv+n7+5YsNhRcMl812EcFVwrnRvYKoNPoQb5qxU8DG6Bgwji0akHdp6D4Ln6tYLG58MFSow==} + emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} @@ -884,6 +2258,14 @@ packages: resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} engines: {node: '>=10.13.0'} + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + es-abstract@1.24.0: resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==} engines: {node: '>= 0.4'} @@ -900,6 +2282,9 @@ packages: resolution: {integrity: sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==} engines: {node: '>= 0.4'} + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} @@ -916,6 +2301,11 @@ packages: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -924,6 +2314,10 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + eslint-config-next@16.0.3: resolution: {integrity: sha512-5F6qDjcZldf0Y0ZbqvWvap9xzYUxyDf7/of37aeyhvkrQokj/4bT1JYWZdlWUr283aeVa+s52mPq9ogmGg+5dw==} peerDependencies: @@ -1036,10 +2430,20 @@ packages: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + expect-type@1.2.2: + resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==} + engines: {node: '>=12.0.0'} + + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -1073,6 +2477,10 @@ packages: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} + file-selector@2.1.2: + resolution: {integrity: sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig==} + engines: {node: '>= 12'} + fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -1092,6 +2500,11 @@ packages: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} @@ -1114,6 +2527,10 @@ packages: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} + get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + get-proto@1.0.1: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} @@ -1182,12 +2599,82 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + hast-util-embedded@3.0.0: + resolution: {integrity: sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA==} + + hast-util-format@1.1.0: + resolution: {integrity: sha512-yY1UDz6bC9rDvCWHpx12aIBGRG7krurX0p0Fm6pT547LwDIZZiNr8a+IHDogorAdreULSEzP82Nlv5SZkHZcjA==} + + hast-util-from-dom@5.0.1: + resolution: {integrity: sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q==} + + hast-util-from-html@2.0.3: + resolution: {integrity: sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==} + + hast-util-from-parse5@8.0.3: + resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==} + + hast-util-has-property@3.0.0: + resolution: {integrity: sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==} + + hast-util-is-body-ok-link@3.0.1: + resolution: {integrity: sha512-0qpnzOBLztXHbHQenVB8uNuxTnm/QBFUOmdOSsEn7GnBtyY07+ENTWVFBAnXd/zEgd9/SUG3lRY7hSIBWRgGpQ==} + + hast-util-is-element@3.0.0: + resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==} + + hast-util-minify-whitespace@1.0.1: + resolution: {integrity: sha512-L96fPOVpnclQE0xzdWb/D12VT5FabA7SnZOUMtL1DbXmYiHJMXZvFkIZfiMmTCNJHUeO2K9UYNXoVyfz+QHuOw==} + + hast-util-parse-selector@4.0.0: + resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} + + hast-util-phrasing@3.0.1: + resolution: {integrity: sha512-6h60VfI3uBQUxHqTyMymMZnEbNl1XmEGtOxxKYL7stY2o601COo62AWAYBQR9lZbYXYSBoxag8UpPRXK+9fqSQ==} + + hast-util-to-html@9.0.5: + resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} + + hast-util-to-mdast@10.1.2: + resolution: {integrity: sha512-FiCRI7NmOvM4y+f5w32jPRzcxDIz+PUqDwEqn1A+1q2cdp3B8Gx7aVrXORdOKjMNDQsD1ogOr896+0jJHW1EFQ==} + + hast-util-to-text@4.0.2: + resolution: {integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + + hastscript@9.0.1: + resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} + hermes-estree@0.25.1: resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} hermes-parser@0.25.1: resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + html-encoding-sniffer@4.0.0: + resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} + engines: {node: '>=18'} + + html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + + html-whitespace-sensitive-tag-names@3.0.1: + resolution: {integrity: sha512-q+310vW8zmymYHALr1da4HyXUQ0zgiIwIicEfotYPWGN0OJVEN/58IJ3A4GBYcEq3LGAZqKb+ugvP0GNB9CEAA==} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -1275,6 +2762,13 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + is-regex@1.2.1: resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} engines: {node: '>= 0.4'} @@ -1317,6 +2811,9 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + isomorphic.js@0.2.5: + resolution: {integrity: sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==} + iterator.prototype@1.1.5: resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} engines: {node: '>= 0.4'} @@ -1325,6 +2822,9 @@ packages: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true + jose@4.15.9: + resolution: {integrity: sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -1332,6 +2832,15 @@ packages: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true + jsdom@27.2.0: + resolution: {integrity: sha512-454TI39PeRDW1LgpyLPyURtB4Zx1tklSr6+OFOipsxGUH1WMTvk6C65JQdrj455+DP2uJ1+veBEHTGFKWVLFoA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} @@ -1373,6 +2882,11 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} + lib0@0.2.114: + resolution: {integrity: sha512-gcxmNFzA4hv8UYi8j43uPlQ7CGcyMJ2KQb5kZASw6SnAKAf10hK12i2fjrS3Cl/ugZa5Ui6WwIu1/6MIXiHttQ==} + engines: {node: '>=16'} + hasBin: true + lightningcss-android-arm64@1.30.2: resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==} engines: {node: '>= 12.0.0'} @@ -1443,6 +2957,12 @@ packages: resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==} engines: {node: '>= 12.0.0'} + linkify-it@5.0.0: + resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} + + linkifyjs@4.3.2: + resolution: {integrity: sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==} + locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -1450,24 +2970,169 @@ packages: lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + lru-cache@11.2.2: + resolution: {integrity: sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==} + engines: {node: 20 || >=22} + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lucide-react@0.554.0: + resolution: {integrity: sha512-St+z29uthEJVx0Is7ellNkgTEhaeSoA42I7JjOCBCrc5X6LYMGSv0P/2uS5HDLTExP5tpiqRD2PyUEOS6s9UXA==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + markdown-it@14.1.0: + resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==} + hasBin: true + + markdown-table@3.0.4: + resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + mdast-util-find-and-replace@3.0.2: + resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} + + mdast-util-from-markdown@2.0.2: + resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} + + mdast-util-gfm-autolink-literal@2.0.1: + resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} + + mdast-util-gfm-footnote@2.1.0: + resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==} + + mdast-util-gfm-strikethrough@2.0.0: + resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} + + mdast-util-gfm-table@2.0.0: + resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} + + mdast-util-gfm-task-list-item@2.0.0: + resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} + + mdast-util-gfm@3.1.0: + resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-hast@13.2.0: + resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==} + + mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + + mdn-data@2.12.2: + resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==} + + mdurl@2.0.0: + resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} + micromark-core-commonmark@2.0.3: + resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} + + micromark-extension-gfm-autolink-literal@2.1.0: + resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} + + micromark-extension-gfm-footnote@2.1.0: + resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} + + micromark-extension-gfm-strikethrough@2.1.0: + resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} + + micromark-extension-gfm-table@2.1.1: + resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==} + + micromark-extension-gfm-tagfilter@2.0.0: + resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} + + micromark-extension-gfm-task-list-item@2.1.0: + resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} + + micromark-extension-gfm@3.0.0: + resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} + + micromark-factory-destination@2.0.1: + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} + + micromark-factory-label@2.0.1: + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} + + micromark-factory-space@2.0.1: + resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} + + micromark-factory-title@2.0.1: + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} + + micromark-factory-whitespace@2.0.1: + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-chunked@2.0.1: + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} + + micromark-util-classify-character@2.0.1: + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} + + micromark-util-combine-extensions@2.0.1: + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} + + micromark-util-decode-numeric-character-reference@2.0.2: + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} + + micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-html-tag-name@2.0.1: + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} + + micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} + + micromark-util-resolve-all@2.0.1: + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-subtokenize@2.1.0: + resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + + micromark@4.0.2: + resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} @@ -1558,6 +3223,9 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} + orderedmap@2.1.1: + resolution: {integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==} + own-keys@1.0.1: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} @@ -1574,6 +3242,12 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + parse5@7.3.0: + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + + parse5@8.0.0: + resolution: {integrity: sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -1585,6 +3259,9 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -1615,6 +3292,106 @@ packages: prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + property-information@7.1.0: + resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + + 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.4.0: + resolution: {integrity: sha512-z00qvurSdCEWUIulij/isHaqu4uLS8r/Fi61IbjdIPJEonQgggbJsLnstW7Lgdk4zQ68/yr6B6bf7sJXowIgdQ==} + + prosemirror-highlight@0.13.0: + resolution: {integrity: sha512-GIC2VCTUnukNdsEGLQWWOVpYPl/7/KrVp4xs7XMB48/4rhUrHK8hp8TEog4Irmv+2kmjx24RLnaisGOCP6U8jw==} + peerDependencies: + '@shikijs/types': ^1.29.2 || ^2.0.0 || ^3.0.0 + '@types/hast': ^3.0.0 + highlight.js: ^11.9.0 + lowlight: ^3.1.0 + prosemirror-model: ^1.19.3 + prosemirror-state: ^1.4.3 + prosemirror-transform: ^1.8.0 + prosemirror-view: ^1.32.4 + refractor: ^5.0.0 + sugar-high: ^0.6.1 || ^0.7.0 || ^0.8.0 || ^0.9.0 + peerDependenciesMeta: + '@shikijs/types': + optional: true + '@types/hast': + optional: true + highlight.js: + optional: true + lowlight: + optional: true + prosemirror-model: + optional: true + prosemirror-state: + optional: true + prosemirror-transform: + optional: true + prosemirror-view: + optional: true + refractor: + optional: true + sugar-high: + optional: true + + prosemirror-history@1.5.0: + resolution: {integrity: sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==} + + prosemirror-inputrules@1.5.1: + resolution: {integrity: sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==} + + 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.4: + resolution: {integrity: sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==} + + 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.4: + resolution: {integrity: sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==} + + 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.5: + resolution: {integrity: sha512-RPDQCxIDhIBb1o36xxwsaeAvivO8VLJcgBtzmOwQ64bMtsVFh5SSuJ6dWSxO1UsHTiTXPCgQm3PDJt7p6IOLbw==} + + prosemirror-view@1.41.3: + resolution: {integrity: sha512-SqMiYMUQNNBP9kfPhLO8WXEk/fon47vc52FQsUiJzTBuyjKgEcoAwMyF04eQ4WZ2ArMn7+ReypYL60aKngbACQ==} + + 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'} @@ -1622,18 +3399,89 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + raf-schd@4.0.3: + resolution: {integrity: sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==} + react-dom@19.2.0: resolution: {integrity: sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==} peerDependencies: react: ^19.2.0 + react-dropzone@14.3.8: + resolution: {integrity: sha512-sBgODnq+lcA4P296DY4wacOZz3JFpD99fp+hb//iBO2HHnyeZU3FwWyXJ6salNpqQdsZrgMrotuko/BdJMV8Ug==} + engines: {node: '>= 10.13'} + peerDependencies: + react: '>= 16.8 || 18.0.0' + + react-icons@5.5.0: + resolution: {integrity: sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==} + peerDependencies: + react: '*' + react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + react-number-format@5.4.4: + resolution: {integrity: sha512-wOmoNZoOpvMminhifQYiYSTCLUDOiUbBunrMrMjA+dV52sY+vck1S4UhR6PkgnoCquvvMSeJjErXZ4qSaWCliA==} + peerDependencies: + react: ^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + react-redux@9.2.0: + resolution: {integrity: sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==} + peerDependencies: + '@types/react': ^18.2.25 || ^19 + react: ^18.0 || ^19 + redux: ^5.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + redux: + optional: true + + react-remove-scroll-bar@2.3.8: + resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + react-remove-scroll@2.7.1: + resolution: {integrity: sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react-style-singleton@2.2.3: + resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react-textarea-autosize@8.5.9: + resolution: {integrity: sha512-U1DGlIQN5AwgjTyOEnI1oCcMuEr1pv1qOtklB2l4nyMGbHzWrI0eFsYK0zos2YWqAolJyG0IWJaqWmWj5ETh0A==} + engines: {node: '>=10'} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react@19.2.0: resolution: {integrity: sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==} engines: {node: '>=0.10.0'} + redux@5.0.1: + resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==} + reflect.getprototypeof@1.0.10: resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} engines: {node: '>= 0.4'} @@ -1642,6 +3490,37 @@ packages: resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} engines: {node: '>= 0.4'} + rehype-format@5.0.1: + resolution: {integrity: sha512-zvmVru9uB0josBVpr946OR8ui7nJEdzZobwLOOqHb/OOD88W0Vk2SqLwoVOj0fM6IPCCO6TaV9CvQvJMWwukFQ==} + + rehype-minify-whitespace@6.0.2: + resolution: {integrity: sha512-Zk0pyQ06A3Lyxhe9vGtOtzz3Z0+qZ5+7icZ/PL/2x1SHPbKao5oB/g/rlc6BCTajqBb33JcOe71Ye1oFsuYbnw==} + + rehype-parse@9.0.1: + resolution: {integrity: sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==} + + rehype-remark@10.0.1: + resolution: {integrity: sha512-EmDndlb5NVwXGfUa4c9GPK+lXeItTilLhE6ADSaQuHr4JUlKw9MidzGzx4HpqZrNCt6vnHmEifXQiiA+CEnjYQ==} + + rehype-stringify@10.0.1: + resolution: {integrity: sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==} + + remark-gfm@4.0.1: + resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} + + remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + + remark-rehype@11.1.2: + resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} + + remark-stringify@11.0.0: + resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -1662,6 +3541,14 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rollup@4.53.3: + resolution: {integrity: sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + rope-sequence@1.3.4: + resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -1677,6 +3564,13 @@ packages: resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} engines: {node: '>= 0.4'} + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} @@ -1689,6 +3583,9 @@ packages: engines: {node: '>=10'} hasBin: true + set-cookie-parser@2.7.2: + resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -1729,13 +3626,31 @@ packages: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + sonner@2.0.7: + resolution: {integrity: sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + stable-hash@0.0.5: resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + stop-iteration-iterator@1.1.0: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} @@ -1763,6 +3678,9 @@ packages: resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} engines: {node: '>= 0.4'} + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + strip-bom@3.0.0: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} @@ -1792,6 +3710,15 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + + tabbable@6.3.0: + resolution: {integrity: sha512-EIHvdY5bPLuWForiR/AN2Bxngzpuwn1is4asboytXtpTgsArc+WmSJKVLlhdh71u7jFcryDqB2A8lQvj78MkyQ==} + + tailwind-merge@3.4.0: + resolution: {integrity: sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==} + tailwindcss@4.1.17: resolution: {integrity: sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==} @@ -1799,14 +3726,51 @@ packages: resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} engines: {node: '>=6'} + tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} + tinyrainbow@3.0.3: + resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} + engines: {node: '>=14.0.0'} + + tldts-core@7.0.18: + resolution: {integrity: sha512-jqJC13oP4FFAahv4JT/0WTDrCF9Okv7lpKtOZUGPLiAnNbACcSg8Y8T+Z9xthOmRBqi/Sob4yi0TE0miRCvF7Q==} + + tldts@7.0.18: + resolution: {integrity: sha512-lCcgTAgMxQ1JKOWrVGo6E69Ukbnx4Gc1wiYLRf6J5NN4HRYJtCby1rPF8rkQ4a6qqoFBK5dvjJ1zJ0F7VfDSvw==} + hasBin: true + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + tough-cookie@6.0.0: + resolution: {integrity: sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==} + engines: {node: '>=16'} + + tr46@6.0.0: + resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} + engines: {node: '>=20'} + + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + + trim-trailing-lines@2.1.0: + resolution: {integrity: sha512-5UR5Biq4VlVOtzqkm2AZlgvSlDJtME46uV0br0gENbwN4l5+mMKT4b9gJKqWtuL2zAIqajGJGuvbCbcAJUZqBg==} + + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + ts-api-utils@2.1.0: resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} engines: {node: '>=18.12'} @@ -1819,10 +3783,17 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tw-animate-css@1.4.0: + resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} engines: {node: '>= 0.4'} @@ -1851,6 +3822,9 @@ packages: engines: {node: '>=14.17'} hasBin: true + uc.micro@2.1.0: + resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} + unbox-primitive@1.1.0: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} @@ -1858,6 +3832,27 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + unified@11.0.5: + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + + unist-util-find-after@5.0.0: + resolution: {integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==} + + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} + + unist-util-visit@5.0.0: + resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + unrs-resolver@1.11.1: resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} @@ -1870,6 +3865,184 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + use-callback-ref@1.3.3: + resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-composed-ref@1.4.0: + resolution: {integrity: sha512-djviaxuOOh7wkj0paeO1Q/4wMZ8Zrnag5H6yBvzN7AKKe8beOaED9SF5/ByLqsku8NP4zQqsvM2u3ew/tJK8/w==} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + use-isomorphic-layout-effect@1.2.1: + resolution: {integrity: sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA==} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + use-latest@1.3.0: + resolution: {integrity: sha512-mhg3xdm9NaM8q+gLT8KryJPnRFOz1/5XPBhmDEVZK1webPzDjrPk7f/mbpeLqTgB9msytYWANxgALOCJKnLvcQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + use-sidecar@1.1.3: + resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + uuid@13.0.0: + resolution: {integrity: sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==} + hasBin: true + + uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + + vaul@1.1.2: + resolution: {integrity: sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc + + vfile-location@5.0.3: + resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} + + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + + vite@7.2.4: + resolution: {integrity: sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@4.0.13: + resolution: {integrity: sha512-QSD4I0fN6uZQfftryIXuqvqgBxTvJ3ZNkF6RWECd82YGAYAfhcppBLFXzXJHQAAhVFyYEuFTrq6h0hQqjB7jIQ==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/debug': ^4.1.12 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.0.13 + '@vitest/browser-preview': 4.0.13 + '@vitest/browser-webdriverio': 4.0.13 + '@vitest/ui': 4.0.13 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + 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'} + + web-namespaces@2.0.1: + resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} + + webidl-conversions@8.0.0: + resolution: {integrity: sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==} + engines: {node: '>=20'} + + whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: '>=18'} + + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + + whatwg-url@15.1.0: + resolution: {integrity: sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==} + engines: {node: '>=20'} + which-boxed-primitive@1.1.1: resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} engines: {node: '>= 0.4'} @@ -1891,13 +4064,57 @@ packages: engines: {node: '>= 8'} hasBin: true + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + ws@8.18.3: + resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + + y-prosemirror@1.3.7: + resolution: {integrity: sha512-NpM99WSdD4Fx4if5xOMDpPtU3oAmTSjlzh5U4353ABbRHl1HtAFUx6HlebLZfyFxXN9jzKMDkVbcRjqOZVkYQg==} + engines: {node: '>=16.0.0', npm: '>=8.0.0'} + peerDependencies: + prosemirror-model: ^1.7.1 + prosemirror-state: ^1.2.3 + prosemirror-view: ^1.9.10 + y-protocols: ^1.0.1 + yjs: ^13.5.38 + + y-protocols@1.0.6: + resolution: {integrity: sha512-vHRF2L6iT3rwj1jub/K5tYcTT/mEYDUppgNPXwp8fmLpui9f7Yeq3OEtTLVF012j39QnV+KEQpNqoN7CWU7Y9Q==} + engines: {node: '>=16.0.0', npm: '>=8.0.0'} + peerDependencies: + yjs: ^13.0.0 + yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yjs@13.6.27: + resolution: {integrity: sha512-OIDwaflOaq4wC6YlPBy2L6ceKeKuF7DeTxx+jPzv1FHn9tCZ0ZwSRnUBxD05E3yed46fv/FWJbvR+Ud7x0L7zw==} + engines: {node: '>=16.0.0', npm: '>=8.0.0'} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -1911,10 +4128,66 @@ packages: zod@4.1.12: resolution: {integrity: sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==} + zustand@4.5.7: + resolution: {integrity: sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==} + engines: {node: '>=12.7.0'} + peerDependencies: + '@types/react': '>=16.8' + immer: '>=9.0.6' + react: '>=16.8' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + + zustand@5.0.8: + resolution: {integrity: sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=18.0.0' + immer: '>=9.0.6' + react: '>=18.0.0' + use-sync-external-store: '>=1.2.0' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + use-sync-external-store: + optional: true + + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + snapshots: + '@acemir/cssom@0.9.23': {} + '@alloc/quick-lru@5.2.0': {} + '@asamuzakjp/css-color@4.1.0': + dependencies: + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + lru-cache: 11.2.2 + + '@asamuzakjp/dom-selector@6.7.4': + dependencies: + '@asamuzakjp/nwsapi': 2.3.9 + bidi-js: 1.0.3 + css-tree: 3.1.0 + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.2.2 + + '@asamuzakjp/nwsapi@2.3.9': {} + '@babel/code-frame@7.27.1': dependencies: '@babel/helper-validator-identifier': 7.28.5 @@ -1992,6 +4265,8 @@ snapshots: dependencies: '@babel/types': 7.28.5 + '@babel/runtime@7.28.4': {} + '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 @@ -2015,6 +4290,154 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@blocknote/core@0.42.0(@hocuspocus/provider@2.15.3(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27))(@tiptap/extensions@3.10.7(@tiptap/core@3.10.7(@tiptap/pm@3.10.7))(@tiptap/pm@3.10.7))(@types/hast@3.0.4)': + dependencies: + '@emoji-mart/data': 1.2.1 + '@shikijs/types': 3.13.0 + '@tiptap/core': 3.10.7(@tiptap/pm@3.10.7) + '@tiptap/extension-bold': 3.10.7(@tiptap/core@3.10.7(@tiptap/pm@3.10.7)) + '@tiptap/extension-code': 3.10.7(@tiptap/core@3.10.7(@tiptap/pm@3.10.7)) + '@tiptap/extension-gapcursor': 3.10.7(@tiptap/extensions@3.10.7(@tiptap/core@3.10.7(@tiptap/pm@3.10.7))(@tiptap/pm@3.10.7)) + '@tiptap/extension-history': 3.10.7(@tiptap/extensions@3.10.7(@tiptap/core@3.10.7(@tiptap/pm@3.10.7))(@tiptap/pm@3.10.7)) + '@tiptap/extension-horizontal-rule': 3.10.7(@tiptap/core@3.10.7(@tiptap/pm@3.10.7))(@tiptap/pm@3.10.7) + '@tiptap/extension-italic': 3.10.7(@tiptap/core@3.10.7(@tiptap/pm@3.10.7)) + '@tiptap/extension-link': 3.10.7(@tiptap/core@3.10.7(@tiptap/pm@3.10.7))(@tiptap/pm@3.10.7) + '@tiptap/extension-paragraph': 3.10.7(@tiptap/core@3.10.7(@tiptap/pm@3.10.7)) + '@tiptap/extension-strike': 3.10.7(@tiptap/core@3.10.7(@tiptap/pm@3.10.7)) + '@tiptap/extension-text': 3.10.7(@tiptap/core@3.10.7(@tiptap/pm@3.10.7)) + '@tiptap/extension-underline': 3.10.7(@tiptap/core@3.10.7(@tiptap/pm@3.10.7)) + '@tiptap/pm': 3.10.7 + emoji-mart: 5.6.0 + fast-deep-equal: 3.1.3 + hast-util-from-dom: 5.0.1 + prosemirror-dropcursor: 1.8.2 + prosemirror-highlight: 0.13.0(@shikijs/types@3.13.0)(@types/hast@3.0.4)(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-transform@1.10.5)(prosemirror-view@1.41.3) + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-tables: 1.8.1 + prosemirror-transform: 1.10.5 + prosemirror-view: 1.41.3 + rehype-format: 5.0.1 + rehype-parse: 9.0.1 + rehype-remark: 10.0.1 + rehype-stringify: 10.0.1 + remark-gfm: 4.0.1 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + remark-stringify: 11.0.0 + unified: 11.0.5 + unist-util-visit: 5.0.0 + uuid: 8.3.2 + y-prosemirror: 1.3.7(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.3)(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27) + y-protocols: 1.0.6(yjs@13.6.27) + yjs: 13.6.27 + optionalDependencies: + '@hocuspocus/provider': 2.15.3(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27) + transitivePeerDependencies: + - '@tiptap/extensions' + - '@types/hast' + - highlight.js + - lowlight + - refractor + - sugar-high + - supports-color + + '@blocknote/mantine@0.42.0(@floating-ui/dom@1.7.4)(@hocuspocus/provider@2.15.3(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27))(@mantine/core@8.3.8(@mantine/hooks@8.3.8(react@19.2.0))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@mantine/hooks@8.3.8(react@19.2.0))(@mantine/utils@6.0.22(react@19.2.0))(@tiptap/extensions@3.10.7(@tiptap/core@3.10.7(@tiptap/pm@3.10.7))(@tiptap/pm@3.10.7))(@types/hast@3.0.4)(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@blocknote/core': 0.42.0(@hocuspocus/provider@2.15.3(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27))(@tiptap/extensions@3.10.7(@tiptap/core@3.10.7(@tiptap/pm@3.10.7))(@tiptap/pm@3.10.7))(@types/hast@3.0.4) + '@blocknote/react': 0.42.0(@floating-ui/dom@1.7.4)(@hocuspocus/provider@2.15.3(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27))(@tiptap/extensions@3.10.7(@tiptap/core@3.10.7(@tiptap/pm@3.10.7))(@tiptap/pm@3.10.7))(@types/hast@3.0.4)(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@mantine/core': 8.3.8(@mantine/hooks@8.3.8(react@19.2.0))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@mantine/hooks': 8.3.8(react@19.2.0) + '@mantine/utils': 6.0.22(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + react-icons: 5.5.0(react@19.2.0) + transitivePeerDependencies: + - '@floating-ui/dom' + - '@hocuspocus/provider' + - '@tiptap/extensions' + - '@types/hast' + - '@types/react' + - '@types/react-dom' + - highlight.js + - lowlight + - refractor + - sugar-high + - supports-color + + '@blocknote/react@0.42.0(@floating-ui/dom@1.7.4)(@hocuspocus/provider@2.15.3(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27))(@tiptap/extensions@3.10.7(@tiptap/core@3.10.7(@tiptap/pm@3.10.7))(@tiptap/pm@3.10.7))(@types/hast@3.0.4)(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@blocknote/core': 0.42.0(@hocuspocus/provider@2.15.3(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27))(@tiptap/extensions@3.10.7(@tiptap/core@3.10.7(@tiptap/pm@3.10.7))(@tiptap/pm@3.10.7))(@types/hast@3.0.4) + '@emoji-mart/data': 1.2.1 + '@floating-ui/react': 0.27.16(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@tiptap/core': 3.10.7(@tiptap/pm@3.10.7) + '@tiptap/pm': 3.10.7 + '@tiptap/react': 3.10.7(@floating-ui/dom@1.7.4)(@tiptap/core@3.10.7(@tiptap/pm@3.10.7))(@tiptap/pm@3.10.7)(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + emoji-mart: 5.6.0 + lodash.merge: 4.6.2 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + react-icons: 5.5.0(react@19.2.0) + transitivePeerDependencies: + - '@floating-ui/dom' + - '@hocuspocus/provider' + - '@tiptap/extensions' + - '@types/hast' + - '@types/react' + - '@types/react-dom' + - highlight.js + - lowlight + - refractor + - sugar-high + - supports-color + + '@csstools/color-helpers@5.1.0': {} + + '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/color-helpers': 5.1.0 + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-syntax-patches-for-csstree@1.0.17': {} + + '@csstools/css-tokenizer@3.0.4': {} + + '@dnd-kit/accessibility@3.1.1(react@19.2.0)': + dependencies: + react: 19.2.0 + tslib: 2.8.1 + + '@dnd-kit/core@6.3.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@dnd-kit/accessibility': 3.1.1(react@19.2.0) + '@dnd-kit/utilities': 3.2.2(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + tslib: 2.8.1 + + '@dnd-kit/sortable@10.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react@19.2.0)': + dependencies: + '@dnd-kit/core': 6.3.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@dnd-kit/utilities': 3.2.2(react@19.2.0) + react: 19.2.0 + tslib: 2.8.1 + + '@dnd-kit/utilities@3.2.2(react@19.2.0)': + dependencies: + react: 19.2.0 + tslib: 2.8.1 + '@emnapi/core@1.7.1': dependencies: '@emnapi/wasi-threads': 1.1.0 @@ -2031,6 +4454,86 @@ snapshots: tslib: 2.8.1 optional: true + '@emoji-mart/data@1.2.1': {} + + '@esbuild/aix-ppc64@0.25.12': + optional: true + + '@esbuild/android-arm64@0.25.12': + optional: true + + '@esbuild/android-arm@0.25.12': + optional: true + + '@esbuild/android-x64@0.25.12': + optional: true + + '@esbuild/darwin-arm64@0.25.12': + optional: true + + '@esbuild/darwin-x64@0.25.12': + optional: true + + '@esbuild/freebsd-arm64@0.25.12': + optional: true + + '@esbuild/freebsd-x64@0.25.12': + optional: true + + '@esbuild/linux-arm64@0.25.12': + optional: true + + '@esbuild/linux-arm@0.25.12': + optional: true + + '@esbuild/linux-ia32@0.25.12': + optional: true + + '@esbuild/linux-loong64@0.25.12': + optional: true + + '@esbuild/linux-mips64el@0.25.12': + optional: true + + '@esbuild/linux-ppc64@0.25.12': + optional: true + + '@esbuild/linux-riscv64@0.25.12': + optional: true + + '@esbuild/linux-s390x@0.25.12': + optional: true + + '@esbuild/linux-x64@0.25.12': + optional: true + + '@esbuild/netbsd-arm64@0.25.12': + optional: true + + '@esbuild/netbsd-x64@0.25.12': + optional: true + + '@esbuild/openbsd-arm64@0.25.12': + optional: true + + '@esbuild/openbsd-x64@0.25.12': + optional: true + + '@esbuild/openharmony-arm64@0.25.12': + optional: true + + '@esbuild/sunos-x64@0.25.12': + optional: true + + '@esbuild/win32-arm64@0.25.12': + optional: true + + '@esbuild/win32-ia32@0.25.12': + optional: true + + '@esbuild/win32-x64@0.25.12': + optional: true + '@eslint-community/eslint-utils@4.9.0(eslint@9.39.1(jiti@2.6.1))': dependencies: eslint: 9.39.1(jiti@2.6.1) @@ -2077,6 +4580,59 @@ snapshots: '@eslint/core': 0.17.0 levn: 0.4.1 + '@floating-ui/core@1.7.3': + dependencies: + '@floating-ui/utils': 0.2.10 + + '@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.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@floating-ui/dom': 1.7.4 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@floating-ui/react@0.27.16(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@floating-ui/react-dom': 2.1.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@floating-ui/utils': 0.2.10 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + tabbable: 6.3.0 + + '@floating-ui/utils@0.2.10': {} + + '@hello-pangea/dnd@18.0.1(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@babel/runtime': 7.28.4 + css-box-model: 1.2.1 + raf-schd: 4.0.3 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + react-redux: 9.2.0(@types/react@19.2.5)(react@19.2.0)(redux@5.0.1) + redux: 5.0.1 + transitivePeerDependencies: + - '@types/react' + + '@hocuspocus/common@2.15.3': + dependencies: + lib0: 0.2.114 + + '@hocuspocus/provider@2.15.3(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27)': + dependencies: + '@hocuspocus/common': 2.15.3 + '@lifeomic/attempt': 3.1.0 + lib0: 0.2.114 + ws: 8.18.3 + y-protocols: 1.0.6(yjs@13.6.27) + yjs: 13.6.27 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.7': @@ -2204,6 +4760,30 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@lifeomic/attempt@3.1.0': {} + + '@mantine/core@8.3.8(@mantine/hooks@8.3.8(react@19.2.0))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@floating-ui/react': 0.27.16(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@mantine/hooks': 8.3.8(react@19.2.0) + clsx: 2.1.1 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + react-number-format: 5.4.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react-remove-scroll: 2.7.1(@types/react@19.2.5)(react@19.2.0) + react-textarea-autosize: 8.5.9(@types/react@19.2.5)(react@19.2.0) + type-fest: 4.41.0 + transitivePeerDependencies: + - '@types/react' + + '@mantine/hooks@8.3.8(react@19.2.0)': + dependencies: + react: 19.2.0 + + '@mantine/utils@6.0.22(react@19.2.0)': + dependencies: + react: 19.2.0 + '@napi-rs/wasm-runtime@0.2.12': dependencies: '@emnapi/core': 1.7.1 @@ -2255,8 +4835,545 @@ snapshots: '@nolyfill/is-core-module@1.0.39': {} + '@radix-ui/number@1.1.1': {} + + '@radix-ui/primitive@1.1.3': {} + + '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.5 + '@types/react-dom': 19.2.3(@types/react@19.2.5) + + '@radix-ui/react-avatar@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-context': 1.1.3(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.5)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.5 + '@types/react-dom': 19.2.3(@types/react@19.2.5) + + '@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.5)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.5 + '@types/react-dom': 19.2.3(@types/react@19.2.5) + + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.5)(react@19.2.0)': + dependencies: + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.5 + + '@radix-ui/react-context@1.1.2(@types/react@19.2.5)(react@19.2.0)': + dependencies: + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.5 + + '@radix-ui/react-context@1.1.3(@types/react@19.2.5)(react@19.2.0)': + dependencies: + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.5 + + '@radix-ui/react-dialog@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.5)(react@19.2.0) + aria-hidden: 1.2.6 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + react-remove-scroll: 2.7.1(@types/react@19.2.5)(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.5 + '@types/react-dom': 19.2.3(@types/react@19.2.5) + + '@radix-ui/react-direction@1.1.1(@types/react@19.2.5)(react@19.2.0)': + dependencies: + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.5 + + '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.5)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.5 + '@types/react-dom': 19.2.3(@types/react@19.2.5) + + '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.5)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.5 + '@types/react-dom': 19.2.3(@types/react@19.2.5) + + '@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.5)(react@19.2.0)': + dependencies: + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.5 + + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.5)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.5 + '@types/react-dom': 19.2.3(@types/react@19.2.5) + + '@radix-ui/react-hover-card@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.5)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.5 + '@types/react-dom': 19.2.3(@types/react@19.2.5) + + '@radix-ui/react-id@1.1.1(@types/react@19.2.5)(react@19.2.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.5)(react@19.2.0) + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.5 + + '@radix-ui/react-label@2.1.8(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.5 + '@types/react-dom': 19.2.3(@types/react@19.2.5) + + '@radix-ui/react-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.5)(react@19.2.0) + aria-hidden: 1.2.6 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + react-remove-scroll: 2.7.1(@types/react@19.2.5)(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.5 + '@types/react-dom': 19.2.3(@types/react@19.2.5) + + '@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.5)(react@19.2.0) + aria-hidden: 1.2.6 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + react-remove-scroll: 2.7.1(@types/react@19.2.5)(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.5 + '@types/react-dom': 19.2.3(@types/react@19.2.5) + + '@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@floating-ui/react-dom': 2.1.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-use-rect': 1.1.1(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/rect': 1.1.1 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.5 + '@types/react-dom': 19.2.3(@types/react@19.2.5) + + '@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.5)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.5 + '@types/react-dom': 19.2.3(@types/react@19.2.5) + + '@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.5)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.5 + '@types/react-dom': 19.2.3(@types/react@19.2.5) + + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.5)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.5 + '@types/react-dom': 19.2.3(@types/react@19.2.5) + + '@radix-ui/react-primitive@2.1.4(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-slot': 1.2.4(@types/react@19.2.5)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.5 + '@types/react-dom': 19.2.3(@types/react@19.2.5) + + '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.5)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.5 + '@types/react-dom': 19.2.3(@types/react@19.2.5) + + '@radix-ui/react-scroll-area@1.2.10(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.5)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.5 + '@types/react-dom': 19.2.3(@types/react@19.2.5) + + '@radix-ui/react-separator@1.1.8(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.5 + '@types/react-dom': 19.2.3(@types/react@19.2.5) + + '@radix-ui/react-slot@1.2.3(@types/react@19.2.5)(react@19.2.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.5)(react@19.2.0) + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.5 + + '@radix-ui/react-slot@1.2.4(@types/react@19.2.5)(react@19.2.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.5)(react@19.2.0) + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.5 + + '@radix-ui/react-tabs@1.1.13(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-context': 1.1.2(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.5)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.5 + '@types/react-dom': 19.2.3(@types/react@19.2.5) + + '@radix-ui/react-toggle@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.5)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.5 + '@types/react-dom': 19.2.3(@types/react@19.2.5) + + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.5)(react@19.2.0)': + dependencies: + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.5 + + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.5)(react@19.2.0)': + dependencies: + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.5)(react@19.2.0) + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.5 + + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.5)(react@19.2.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.5)(react@19.2.0) + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.5 + + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.5)(react@19.2.0)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.5)(react@19.2.0) + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.5 + + '@radix-ui/react-use-is-hydrated@0.1.0(@types/react@19.2.5)(react@19.2.0)': + dependencies: + react: 19.2.0 + use-sync-external-store: 1.6.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.5 + + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.5)(react@19.2.0)': + dependencies: + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.5 + + '@radix-ui/react-use-rect@1.1.1(@types/react@19.2.5)(react@19.2.0)': + dependencies: + '@radix-ui/rect': 1.1.1 + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.5 + + '@radix-ui/react-use-size@1.1.1(@types/react@19.2.5)(react@19.2.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.5)(react@19.2.0) + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.5 + + '@radix-ui/rect@1.1.1': {} + + '@remirror/core-constants@3.0.0': {} + + '@rollup/rollup-android-arm-eabi@4.53.3': + optional: true + + '@rollup/rollup-android-arm64@4.53.3': + optional: true + + '@rollup/rollup-darwin-arm64@4.53.3': + optional: true + + '@rollup/rollup-darwin-x64@4.53.3': + optional: true + + '@rollup/rollup-freebsd-arm64@4.53.3': + optional: true + + '@rollup/rollup-freebsd-x64@4.53.3': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.53.3': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.53.3': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.53.3': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.53.3': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-x64-musl@4.53.3': + optional: true + + '@rollup/rollup-openharmony-arm64@4.53.3': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.53.3': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.53.3': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.53.3': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.53.3': + optional: true + '@rtsao/scc@1.1.0': {} + '@shikijs/types@3.13.0': + dependencies: + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + '@shikijs/vscode-textmate@10.0.2': {} + + '@standard-schema/spec@1.0.0': {} + + '@supabase/auth-helpers-nextjs@0.10.0(@supabase/supabase-js@2.81.1)': + dependencies: + '@supabase/auth-helpers-shared': 0.7.0(@supabase/supabase-js@2.81.1) + '@supabase/supabase-js': 2.81.1 + set-cookie-parser: 2.7.2 + + '@supabase/auth-helpers-react@0.5.0(@supabase/supabase-js@2.81.1)': + dependencies: + '@supabase/supabase-js': 2.81.1 + + '@supabase/auth-helpers-shared@0.7.0(@supabase/supabase-js@2.81.1)': + dependencies: + '@supabase/supabase-js': 2.81.1 + jose: 4.15.9 + + '@supabase/auth-js@2.81.1': + dependencies: + tslib: 2.8.1 + + '@supabase/functions-js@2.81.1': + dependencies: + tslib: 2.8.1 + + '@supabase/postgrest-js@2.81.1': + dependencies: + tslib: 2.8.1 + + '@supabase/realtime-js@2.81.1': + dependencies: + '@types/phoenix': 1.6.6 + '@types/ws': 8.18.1 + tslib: 2.8.1 + ws: 8.18.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@supabase/storage-js@2.81.1': + dependencies: + tslib: 2.8.1 + + '@supabase/supabase-js@2.81.1': + dependencies: + '@supabase/auth-js': 2.81.1 + '@supabase/functions-js': 2.81.1 + '@supabase/postgrest-js': 2.81.1 + '@supabase/realtime-js': 2.81.1 + '@supabase/storage-js': 2.81.1 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + '@swc/helpers@0.5.15': dependencies: tslib: 2.8.1 @@ -2330,21 +5447,205 @@ snapshots: postcss: 8.5.6 tailwindcss: 4.1.17 + '@tanstack/query-core@5.90.10': {} + + '@tanstack/query-devtools@5.90.1': {} + + '@tanstack/react-query-devtools@5.90.2(@tanstack/react-query@5.90.10(react@19.2.0))(react@19.2.0)': + dependencies: + '@tanstack/query-devtools': 5.90.1 + '@tanstack/react-query': 5.90.10(react@19.2.0) + react: 19.2.0 + + '@tanstack/react-query@5.90.10(react@19.2.0)': + dependencies: + '@tanstack/query-core': 5.90.10 + react: 19.2.0 + + '@tanstack/react-virtual@3.13.12(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@tanstack/virtual-core': 3.13.12 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@tanstack/virtual-core@3.13.12': {} + + '@tiptap/core@3.10.7(@tiptap/pm@3.10.7)': + dependencies: + '@tiptap/pm': 3.10.7 + + '@tiptap/extension-bold@3.10.7(@tiptap/core@3.10.7(@tiptap/pm@3.10.7))': + dependencies: + '@tiptap/core': 3.10.7(@tiptap/pm@3.10.7) + + '@tiptap/extension-bubble-menu@3.10.7(@tiptap/core@3.10.7(@tiptap/pm@3.10.7))(@tiptap/pm@3.10.7)': + dependencies: + '@floating-ui/dom': 1.7.4 + '@tiptap/core': 3.10.7(@tiptap/pm@3.10.7) + '@tiptap/pm': 3.10.7 + optional: true + + '@tiptap/extension-code@3.10.7(@tiptap/core@3.10.7(@tiptap/pm@3.10.7))': + dependencies: + '@tiptap/core': 3.10.7(@tiptap/pm@3.10.7) + + '@tiptap/extension-floating-menu@3.10.7(@floating-ui/dom@1.7.4)(@tiptap/core@3.10.7(@tiptap/pm@3.10.7))(@tiptap/pm@3.10.7)': + dependencies: + '@floating-ui/dom': 1.7.4 + '@tiptap/core': 3.10.7(@tiptap/pm@3.10.7) + '@tiptap/pm': 3.10.7 + optional: true + + '@tiptap/extension-gapcursor@3.10.7(@tiptap/extensions@3.10.7(@tiptap/core@3.10.7(@tiptap/pm@3.10.7))(@tiptap/pm@3.10.7))': + dependencies: + '@tiptap/extensions': 3.10.7(@tiptap/core@3.10.7(@tiptap/pm@3.10.7))(@tiptap/pm@3.10.7) + + '@tiptap/extension-history@3.10.7(@tiptap/extensions@3.10.7(@tiptap/core@3.10.7(@tiptap/pm@3.10.7))(@tiptap/pm@3.10.7))': + dependencies: + '@tiptap/extensions': 3.10.7(@tiptap/core@3.10.7(@tiptap/pm@3.10.7))(@tiptap/pm@3.10.7) + + '@tiptap/extension-horizontal-rule@3.10.7(@tiptap/core@3.10.7(@tiptap/pm@3.10.7))(@tiptap/pm@3.10.7)': + dependencies: + '@tiptap/core': 3.10.7(@tiptap/pm@3.10.7) + '@tiptap/pm': 3.10.7 + + '@tiptap/extension-italic@3.10.7(@tiptap/core@3.10.7(@tiptap/pm@3.10.7))': + dependencies: + '@tiptap/core': 3.10.7(@tiptap/pm@3.10.7) + + '@tiptap/extension-link@3.10.7(@tiptap/core@3.10.7(@tiptap/pm@3.10.7))(@tiptap/pm@3.10.7)': + dependencies: + '@tiptap/core': 3.10.7(@tiptap/pm@3.10.7) + '@tiptap/pm': 3.10.7 + linkifyjs: 4.3.2 + + '@tiptap/extension-paragraph@3.10.7(@tiptap/core@3.10.7(@tiptap/pm@3.10.7))': + dependencies: + '@tiptap/core': 3.10.7(@tiptap/pm@3.10.7) + + '@tiptap/extension-strike@3.10.7(@tiptap/core@3.10.7(@tiptap/pm@3.10.7))': + dependencies: + '@tiptap/core': 3.10.7(@tiptap/pm@3.10.7) + + '@tiptap/extension-text@3.10.7(@tiptap/core@3.10.7(@tiptap/pm@3.10.7))': + dependencies: + '@tiptap/core': 3.10.7(@tiptap/pm@3.10.7) + + '@tiptap/extension-underline@3.10.7(@tiptap/core@3.10.7(@tiptap/pm@3.10.7))': + dependencies: + '@tiptap/core': 3.10.7(@tiptap/pm@3.10.7) + + '@tiptap/extensions@3.10.7(@tiptap/core@3.10.7(@tiptap/pm@3.10.7))(@tiptap/pm@3.10.7)': + dependencies: + '@tiptap/core': 3.10.7(@tiptap/pm@3.10.7) + '@tiptap/pm': 3.10.7 + + '@tiptap/pm@3.10.7': + dependencies: + prosemirror-changeset: 2.3.1 + prosemirror-collab: 1.3.1 + prosemirror-commands: 1.7.1 + prosemirror-dropcursor: 1.8.2 + prosemirror-gapcursor: 1.4.0 + prosemirror-history: 1.5.0 + prosemirror-inputrules: 1.5.1 + prosemirror-keymap: 1.2.3 + prosemirror-markdown: 1.13.2 + prosemirror-menu: 1.2.5 + prosemirror-model: 1.25.4 + prosemirror-schema-basic: 1.2.4 + prosemirror-schema-list: 1.5.1 + prosemirror-state: 1.4.4 + prosemirror-tables: 1.8.1 + prosemirror-trailing-node: 3.0.0(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.3) + prosemirror-transform: 1.10.5 + prosemirror-view: 1.41.3 + + '@tiptap/react@3.10.7(@floating-ui/dom@1.7.4)(@tiptap/core@3.10.7(@tiptap/pm@3.10.7))(@tiptap/pm@3.10.7)(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@tiptap/core': 3.10.7(@tiptap/pm@3.10.7) + '@tiptap/pm': 3.10.7 + '@types/react': 19.2.5 + '@types/react-dom': 19.2.3(@types/react@19.2.5) + '@types/use-sync-external-store': 0.0.6 + fast-deep-equal: 3.1.3 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + use-sync-external-store: 1.6.0(react@19.2.0) + optionalDependencies: + '@tiptap/extension-bubble-menu': 3.10.7(@tiptap/core@3.10.7(@tiptap/pm@3.10.7))(@tiptap/pm@3.10.7) + '@tiptap/extension-floating-menu': 3.10.7(@floating-ui/dom@1.7.4)(@tiptap/core@3.10.7(@tiptap/pm@3.10.7))(@tiptap/pm@3.10.7) + transitivePeerDependencies: + - '@floating-ui/dom' + '@tybys/wasm-util@0.10.1': dependencies: tslib: 2.8.1 optional: true + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/d3-color@3.1.3': {} + + '@types/d3-drag@3.0.7': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-interpolate@3.0.4': + dependencies: + '@types/d3-color': 3.1.3 + + '@types/d3-selection@3.0.11': {} + + '@types/d3-transition@3.0.9': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-zoom@3.0.8': + dependencies: + '@types/d3-interpolate': 3.0.4 + '@types/d3-selection': 3.0.11 + + '@types/debug@4.1.12': + dependencies: + '@types/ms': 2.1.0 + + '@types/deep-eql@4.0.2': {} + '@types/estree@1.0.8': {} + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + '@types/json-schema@7.0.15': {} '@types/json5@0.0.29': {} + '@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/ms@2.1.0': {} + '@types/node@20.19.25': dependencies: undici-types: 6.21.0 + '@types/phoenix@1.6.6': {} + '@types/react-dom@19.2.3(@types/react@19.2.5)': dependencies: '@types/react': 19.2.5 @@ -2353,6 +5654,14 @@ snapshots: dependencies: csstype: 3.2.3 + '@types/unist@3.0.3': {} + + '@types/use-sync-external-store@0.0.6': {} + + '@types/ws@8.18.1': + dependencies: + '@types/node': 20.19.25 + '@typescript-eslint/eslint-plugin@8.47.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 @@ -2446,6 +5755,8 @@ snapshots: '@typescript-eslint/types': 8.47.0 eslint-visitor-keys: 4.2.1 + '@ungap/structured-clone@1.3.0': {} + '@unrs/resolver-binding-android-arm-eabi@1.11.1': optional: true @@ -2505,12 +5816,76 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.11.1': optional: true + '@vitest/expect@4.0.13': + dependencies: + '@standard-schema/spec': 1.0.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.0.13 + '@vitest/utils': 4.0.13 + chai: 6.2.1 + tinyrainbow: 3.0.3 + + '@vitest/mocker@4.0.13(vite@7.2.4(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2))': + dependencies: + '@vitest/spy': 4.0.13 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 7.2.4(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2) + + '@vitest/pretty-format@4.0.13': + dependencies: + tinyrainbow: 3.0.3 + + '@vitest/runner@4.0.13': + dependencies: + '@vitest/utils': 4.0.13 + pathe: 2.0.3 + + '@vitest/snapshot@4.0.13': + dependencies: + '@vitest/pretty-format': 4.0.13 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.0.13': {} + + '@vitest/utils@4.0.13': + dependencies: + '@vitest/pretty-format': 4.0.13 + tinyrainbow: 3.0.3 + + '@xyflow/react@12.9.3(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@xyflow/system': 0.0.73 + classcat: 5.0.5 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + zustand: 4.5.7(@types/react@19.2.5)(react@19.2.0) + transitivePeerDependencies: + - '@types/react' + - immer + + '@xyflow/system@0.0.73': + dependencies: + '@types/d3-drag': 3.0.7 + '@types/d3-interpolate': 3.0.4 + '@types/d3-selection': 3.0.11 + '@types/d3-transition': 3.0.9 + '@types/d3-zoom': 3.0.8 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-zoom: 3.0.0 + acorn-jsx@5.3.2(acorn@8.15.0): dependencies: acorn: 8.15.0 acorn@8.15.0: {} + agent-base@7.1.4: {} + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -2524,6 +5899,10 @@ snapshots: argparse@2.0.1: {} + aria-hidden@1.2.6: + dependencies: + tslib: 2.8.1 + aria-query@5.3.2: {} array-buffer-byte-length@1.0.2: @@ -2593,10 +5972,14 @@ snapshots: get-intrinsic: 1.3.0 is-array-buffer: 3.0.5 + assertion-error@2.0.1: {} + ast-types-flow@0.0.8: {} async-function@1.0.0: {} + attr-accept@2.2.5: {} + available-typed-arrays@1.0.7: dependencies: possible-typed-array-names: 1.1.0 @@ -2605,10 +5988,16 @@ snapshots: axobject-query@4.1.0: {} + bail@2.0.2: {} + balanced-match@1.0.2: {} baseline-browser-mapping@2.8.29: {} + bidi-js@1.0.3: + dependencies: + require-from-string: 2.0.2 + brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 @@ -2651,33 +6040,123 @@ snapshots: caniuse-lite@1.0.30001755: {} + ccount@2.0.1: {} + + chai@6.2.1: {} + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + character-entities@2.0.2: {} + + class-variance-authority@0.7.1: + dependencies: + clsx: 2.1.1 + + classcat@5.0.5: {} + client-only@0.0.1: {} + clsx@2.1.1: {} + + cmdk@1.1.1(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.5)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + color-convert@2.0.1: dependencies: color-name: 1.1.4 color-name@1.1.4: {} + comma-separated-tokens@2.0.3: {} + concat-map@0.0.1: {} convert-source-map@2.0.0: {} + crelt@1.0.6: {} + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 + css-box-model@1.2.1: + dependencies: + tiny-invariant: 1.3.3 + + css-tree@3.1.0: + dependencies: + mdn-data: 2.12.2 + source-map-js: 1.2.1 + + cssstyle@5.3.3: + dependencies: + '@asamuzakjp/css-color': 4.1.0 + '@csstools/css-syntax-patches-for-csstree': 1.0.17 + css-tree: 3.1.0 + csstype@3.2.3: {} + d3-color@3.1.0: {} + + d3-dispatch@3.0.1: {} + + d3-drag@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-selection: 3.0.0 + + d3-ease@3.0.1: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-selection@3.0.0: {} + + d3-timer@3.0.1: {} + + d3-transition@3.0.1(d3-selection@3.0.0): + dependencies: + d3-color: 3.1.0 + d3-dispatch: 3.0.1 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-timer: 3.0.1 + + d3-zoom@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + damerau-levenshtein@1.0.8: {} + data-urls@6.0.0: + dependencies: + whatwg-mimetype: 4.0.0 + whatwg-url: 15.1.0 + data-view-buffer@1.0.2: dependencies: call-bound: 1.0.4 @@ -2704,6 +6183,12 @@ snapshots: dependencies: ms: 2.1.3 + decimal.js@10.6.0: {} + + decode-named-character-reference@1.2.0: + dependencies: + character-entities: 2.0.2 + deep-is@0.1.4: {} define-data-property@1.1.4: @@ -2718,8 +6203,16 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 + dequal@2.0.3: {} + detect-libc@2.1.2: {} + detect-node-es@1.1.0: {} + + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + doctrine@2.1.0: dependencies: esutils: 2.0.3 @@ -2732,6 +6225,8 @@ snapshots: electron-to-chromium@1.5.255: {} + emoji-mart@5.6.0: {} + emoji-regex@9.2.2: {} enhanced-resolve@5.18.3: @@ -2739,6 +6234,10 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.3.0 + entities@4.5.0: {} + + entities@6.0.1: {} + es-abstract@1.24.0: dependencies: array-buffer-byte-length: 1.0.2 @@ -2819,6 +6318,8 @@ snapshots: iterator.prototype: 1.1.5 safe-array-concat: 1.1.3 + es-module-lexer@1.7.0: {} + es-object-atoms@1.1.1: dependencies: es-errors: 1.3.0 @@ -2840,10 +6341,41 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + escalade@3.2.0: {} escape-string-regexp@4.0.0: {} + escape-string-regexp@5.0.0: {} + eslint-config-next@16.0.3(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3): dependencies: '@next/eslint-plugin-next': 16.0.3 @@ -3045,8 +6577,16 @@ snapshots: estraverse@5.3.0: {} + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + esutils@2.0.3: {} + expect-type@1.2.2: {} + + extend@3.0.2: {} + fast-deep-equal@3.1.3: {} fast-glob@3.3.1: @@ -3081,6 +6621,10 @@ snapshots: dependencies: flat-cache: 4.0.1 + file-selector@2.1.2: + dependencies: + tslib: 2.8.1 + fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -3101,6 +6645,9 @@ snapshots: dependencies: is-callable: 1.2.7 + fsevents@2.3.3: + optional: true + function-bind@1.1.2: {} function.prototype.name@1.1.8: @@ -3131,6 +6678,8 @@ snapshots: hasown: 2.0.2 math-intrinsics: 1.1.0 + get-nonce@1.0.1: {} + get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 @@ -3191,12 +6740,161 @@ snapshots: dependencies: function-bind: 1.1.2 + hast-util-embedded@3.0.0: + dependencies: + '@types/hast': 3.0.4 + hast-util-is-element: 3.0.0 + + hast-util-format@1.1.0: + dependencies: + '@types/hast': 3.0.4 + hast-util-embedded: 3.0.0 + hast-util-minify-whitespace: 1.0.1 + hast-util-phrasing: 3.0.1 + hast-util-whitespace: 3.0.0 + html-whitespace-sensitive-tag-names: 3.0.1 + unist-util-visit-parents: 6.0.2 + + hast-util-from-dom@5.0.1: + dependencies: + '@types/hast': 3.0.4 + hastscript: 9.0.1 + web-namespaces: 2.0.1 + + hast-util-from-html@2.0.3: + dependencies: + '@types/hast': 3.0.4 + devlop: 1.1.0 + hast-util-from-parse5: 8.0.3 + parse5: 7.3.0 + vfile: 6.0.3 + vfile-message: 4.0.3 + + hast-util-from-parse5@8.0.3: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + devlop: 1.1.0 + hastscript: 9.0.1 + property-information: 7.1.0 + vfile: 6.0.3 + vfile-location: 5.0.3 + web-namespaces: 2.0.1 + + hast-util-has-property@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + hast-util-is-body-ok-link@3.0.1: + dependencies: + '@types/hast': 3.0.4 + + hast-util-is-element@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + hast-util-minify-whitespace@1.0.1: + dependencies: + '@types/hast': 3.0.4 + hast-util-embedded: 3.0.0 + hast-util-is-element: 3.0.0 + hast-util-whitespace: 3.0.0 + unist-util-is: 6.0.1 + + hast-util-parse-selector@4.0.0: + dependencies: + '@types/hast': 3.0.4 + + hast-util-phrasing@3.0.1: + dependencies: + '@types/hast': 3.0.4 + hast-util-embedded: 3.0.0 + hast-util-has-property: 3.0.0 + hast-util-is-body-ok-link: 3.0.1 + hast-util-is-element: 3.0.0 + + hast-util-to-html@9.0.5: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + comma-separated-tokens: 2.0.3 + hast-util-whitespace: 3.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.0 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + stringify-entities: 4.0.4 + zwitch: 2.0.4 + + hast-util-to-mdast@10.1.2: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.0 + hast-util-phrasing: 3.0.1 + hast-util-to-html: 9.0.5 + hast-util-to-text: 4.0.2 + hast-util-whitespace: 3.0.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-hast: 13.2.0 + mdast-util-to-string: 4.0.0 + rehype-minify-whitespace: 6.0.2 + trim-trailing-lines: 2.1.0 + unist-util-position: 5.0.0 + unist-util-visit: 5.0.0 + + hast-util-to-text@4.0.2: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + hast-util-is-element: 3.0.0 + unist-util-find-after: 5.0.0 + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + hastscript@9.0.1: + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + hast-util-parse-selector: 4.0.0 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + hermes-estree@0.25.1: {} hermes-parser@0.25.1: dependencies: hermes-estree: 0.25.1 + html-encoding-sniffer@4.0.0: + dependencies: + whatwg-encoding: 3.1.1 + + html-void-elements@3.0.0: {} + + html-whitespace-sensitive-tag-names@3.0.1: {} + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + ignore@5.3.2: {} ignore@7.0.5: {} @@ -3287,6 +6985,10 @@ snapshots: is-number@7.0.0: {} + is-plain-obj@4.1.0: {} + + is-potential-custom-element-name@1.0.1: {} + is-regex@1.2.1: dependencies: call-bound: 1.0.4 @@ -3330,6 +7032,8 @@ snapshots: isexe@2.0.0: {} + isomorphic.js@0.2.5: {} + iterator.prototype@1.1.5: dependencies: define-data-property: 1.1.4 @@ -3341,12 +7045,41 @@ snapshots: jiti@2.6.1: {} + jose@4.15.9: {} + js-tokens@4.0.0: {} js-yaml@4.1.1: dependencies: argparse: 2.0.1 + jsdom@27.2.0: + dependencies: + '@acemir/cssom': 0.9.23 + '@asamuzakjp/dom-selector': 6.7.4 + cssstyle: 5.3.3 + data-urls: 6.0.0 + decimal.js: 10.6.0 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + is-potential-custom-element-name: 1.0.1 + parse5: 8.0.0 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 6.0.0 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 8.0.0 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 15.1.0 + ws: 8.18.3 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + jsesc@3.1.0: {} json-buffer@3.0.1: {} @@ -3383,6 +7116,10 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 + lib0@0.2.114: + dependencies: + isomorphic.js: 0.2.5 + lightningcss-android-arm64@1.30.2: optional: true @@ -3432,28 +7169,362 @@ snapshots: lightningcss-win32-arm64-msvc: 1.30.2 lightningcss-win32-x64-msvc: 1.30.2 + linkify-it@5.0.0: + dependencies: + uc.micro: 2.1.0 + + linkifyjs@4.3.2: {} + locate-path@6.0.0: dependencies: p-locate: 5.0.0 lodash.merge@4.6.2: {} + longest-streak@3.1.0: {} + loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 + lru-cache@11.2.2: {} + lru-cache@5.1.1: dependencies: yallist: 3.1.1 + lucide-react@0.554.0(react@19.2.0): + dependencies: + react: 19.2.0 + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + 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: {} + math-intrinsics@1.1.0: {} + mdast-util-find-and-replace@3.0.2: + dependencies: + '@types/mdast': 4.0.4 + escape-string-regexp: 5.0.0 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + mdast-util-from-markdown@2.0.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.2.0 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.2 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-autolink-literal@2.0.1: + dependencies: + '@types/mdast': 4.0.4 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-find-and-replace: 3.0.2 + micromark-util-character: 2.1.1 + + mdast-util-gfm-footnote@2.1.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + micromark-util-normalize-identifier: 2.0.1 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-strikethrough@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-table@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + markdown-table: 3.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-task-list-item@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm@3.1.0: + dependencies: + mdast-util-from-markdown: 2.0.2 + mdast-util-gfm-autolink-literal: 2.0.1 + mdast-util-gfm-footnote: 2.1.0 + mdast-util-gfm-strikethrough: 2.0.0 + mdast-util-gfm-table: 2.0.0 + mdast-util-gfm-task-list-item: 2.0.0 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.1 + + mdast-util-to-hast@13.2.0: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + + mdast-util-to-markdown@2.1.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + unist-util-visit: 5.0.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + + mdn-data@2.12.2: {} + + mdurl@2.0.0: {} + merge2@1.4.1: {} + micromark-core-commonmark@2.0.3: + dependencies: + decode-named-character-reference: 1.2.0 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.0.1 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-autolink-literal@2.1.0: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-footnote@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-strikethrough@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-table@2.1.1: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-tagfilter@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-gfm-task-list-item@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm@3.0.0: + dependencies: + micromark-extension-gfm-autolink-literal: 2.1.0 + micromark-extension-gfm-footnote: 2.1.0 + micromark-extension-gfm-strikethrough: 2.1.0 + micromark-extension-gfm-table: 2.1.1 + micromark-extension-gfm-tagfilter: 2.0.0 + micromark-extension-gfm-task-list-item: 2.1.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-destination@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-label@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-space@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.2 + + micromark-factory-title@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-whitespace@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-chunked@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-classify-character@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-combine-extensions@2.0.1: + dependencies: + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-decode-numeric-character-reference@2.0.2: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-decode-string@2.0.1: + dependencies: + decode-named-character-reference: 1.2.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 + + micromark-util-encode@2.0.1: {} + + micromark-util-html-tag-name@2.0.1: {} + + micromark-util-normalize-identifier@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-resolve-all@2.0.1: + dependencies: + micromark-util-types: 2.0.2 + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-subtokenize@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + + micromark@4.0.2: + dependencies: + '@types/debug': 4.1.12 + debug: 4.4.3 + decode-named-character-reference: 1.2.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + transitivePeerDependencies: + - supports-color + micromatch@4.0.8: dependencies: braces: 3.0.3 @@ -3553,6 +7624,8 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 + orderedmap@2.1.1: {} + own-keys@1.0.1: dependencies: get-intrinsic: 1.3.0 @@ -3571,12 +7644,22 @@ snapshots: dependencies: callsites: 3.1.0 + parse5@7.3.0: + dependencies: + entities: 6.0.1 + + parse5@8.0.0: + dependencies: + entities: 6.0.1 + path-exists@4.0.0: {} path-key@3.1.1: {} path-parse@1.0.7: {} + pathe@2.0.3: {} + picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -3605,19 +7688,200 @@ snapshots: object-assign: 4.1.1 react-is: 16.13.1 + property-information@7.1.0: {} + + prosemirror-changeset@2.3.1: + dependencies: + prosemirror-transform: 1.10.5 + + prosemirror-collab@1.3.1: + dependencies: + prosemirror-state: 1.4.4 + + prosemirror-commands@1.7.1: + dependencies: + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.10.5 + + prosemirror-dropcursor@1.8.2: + dependencies: + prosemirror-state: 1.4.4 + prosemirror-transform: 1.10.5 + prosemirror-view: 1.41.3 + + prosemirror-gapcursor@1.4.0: + dependencies: + prosemirror-keymap: 1.2.3 + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-view: 1.41.3 + + prosemirror-highlight@0.13.0(@shikijs/types@3.13.0)(@types/hast@3.0.4)(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-transform@1.10.5)(prosemirror-view@1.41.3): + optionalDependencies: + '@shikijs/types': 3.13.0 + '@types/hast': 3.0.4 + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.10.5 + prosemirror-view: 1.41.3 + + prosemirror-history@1.5.0: + dependencies: + prosemirror-state: 1.4.4 + prosemirror-transform: 1.10.5 + prosemirror-view: 1.41.3 + rope-sequence: 1.3.4 + + prosemirror-inputrules@1.5.1: + dependencies: + prosemirror-state: 1.4.4 + prosemirror-transform: 1.10.5 + + prosemirror-keymap@1.2.3: + dependencies: + prosemirror-state: 1.4.4 + 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.4 + + prosemirror-menu@1.2.5: + dependencies: + crelt: 1.0.6 + prosemirror-commands: 1.7.1 + prosemirror-history: 1.5.0 + prosemirror-state: 1.4.4 + + prosemirror-model@1.25.4: + dependencies: + orderedmap: 2.1.1 + + prosemirror-schema-basic@1.2.4: + dependencies: + prosemirror-model: 1.25.4 + + prosemirror-schema-list@1.5.1: + dependencies: + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.10.5 + + prosemirror-state@1.4.4: + dependencies: + prosemirror-model: 1.25.4 + prosemirror-transform: 1.10.5 + prosemirror-view: 1.41.3 + + prosemirror-tables@1.8.1: + dependencies: + prosemirror-keymap: 1.2.3 + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.10.5 + prosemirror-view: 1.41.3 + + prosemirror-trailing-node@3.0.0(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.3): + dependencies: + '@remirror/core-constants': 3.0.0 + escape-string-regexp: 4.0.0 + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-view: 1.41.3 + + prosemirror-transform@1.10.5: + dependencies: + prosemirror-model: 1.25.4 + + prosemirror-view@1.41.3: + dependencies: + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.10.5 + + punycode.js@2.3.1: {} + punycode@2.3.1: {} queue-microtask@1.2.3: {} + raf-schd@4.0.3: {} + react-dom@19.2.0(react@19.2.0): dependencies: react: 19.2.0 scheduler: 0.27.0 + react-dropzone@14.3.8(react@19.2.0): + dependencies: + attr-accept: 2.2.5 + file-selector: 2.1.2 + prop-types: 15.8.1 + react: 19.2.0 + + react-icons@5.5.0(react@19.2.0): + dependencies: + react: 19.2.0 + react-is@16.13.1: {} + react-number-format@5.4.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + dependencies: + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + react-redux@9.2.0(@types/react@19.2.5)(react@19.2.0)(redux@5.0.1): + dependencies: + '@types/use-sync-external-store': 0.0.6 + react: 19.2.0 + use-sync-external-store: 1.6.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.5 + redux: 5.0.1 + + react-remove-scroll-bar@2.3.8(@types/react@19.2.5)(react@19.2.0): + dependencies: + react: 19.2.0 + react-style-singleton: 2.2.3(@types/react@19.2.5)(react@19.2.0) + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.5 + + react-remove-scroll@2.7.1(@types/react@19.2.5)(react@19.2.0): + dependencies: + react: 19.2.0 + react-remove-scroll-bar: 2.3.8(@types/react@19.2.5)(react@19.2.0) + react-style-singleton: 2.2.3(@types/react@19.2.5)(react@19.2.0) + tslib: 2.8.1 + use-callback-ref: 1.3.3(@types/react@19.2.5)(react@19.2.0) + use-sidecar: 1.1.3(@types/react@19.2.5)(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.5 + + react-style-singleton@2.2.3(@types/react@19.2.5)(react@19.2.0): + dependencies: + get-nonce: 1.0.1 + react: 19.2.0 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.5 + + react-textarea-autosize@8.5.9(@types/react@19.2.5)(react@19.2.0): + dependencies: + '@babel/runtime': 7.28.4 + react: 19.2.0 + use-composed-ref: 1.4.0(@types/react@19.2.5)(react@19.2.0) + use-latest: 1.3.0(@types/react@19.2.5)(react@19.2.0) + transitivePeerDependencies: + - '@types/react' + react@19.2.0: {} + redux@5.0.1: {} + reflect.getprototypeof@1.0.10: dependencies: call-bind: 1.0.8 @@ -3638,6 +7902,72 @@ snapshots: gopd: 1.2.0 set-function-name: 2.0.2 + rehype-format@5.0.1: + dependencies: + '@types/hast': 3.0.4 + hast-util-format: 1.1.0 + + rehype-minify-whitespace@6.0.2: + dependencies: + '@types/hast': 3.0.4 + hast-util-minify-whitespace: 1.0.1 + + rehype-parse@9.0.1: + dependencies: + '@types/hast': 3.0.4 + hast-util-from-html: 2.0.3 + unified: 11.0.5 + + rehype-remark@10.0.1: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + hast-util-to-mdast: 10.1.2 + unified: 11.0.5 + vfile: 6.0.3 + + rehype-stringify@10.0.1: + dependencies: + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.5 + unified: 11.0.5 + + remark-gfm@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-gfm: 3.1.0 + micromark-extension-gfm: 3.0.0 + remark-parse: 11.0.0 + remark-stringify: 11.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-parse@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.2 + micromark-util-types: 2.0.2 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-rehype@11.1.2: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + mdast-util-to-hast: 13.2.0 + unified: 11.0.5 + vfile: 6.0.3 + + remark-stringify@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-to-markdown: 2.1.2 + unified: 11.0.5 + + require-from-string@2.0.2: {} + resolve-from@4.0.0: {} resolve-pkg-maps@1.0.0: {} @@ -3656,6 +7986,36 @@ snapshots: reusify@1.1.0: {} + rollup@4.53.3: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.53.3 + '@rollup/rollup-android-arm64': 4.53.3 + '@rollup/rollup-darwin-arm64': 4.53.3 + '@rollup/rollup-darwin-x64': 4.53.3 + '@rollup/rollup-freebsd-arm64': 4.53.3 + '@rollup/rollup-freebsd-x64': 4.53.3 + '@rollup/rollup-linux-arm-gnueabihf': 4.53.3 + '@rollup/rollup-linux-arm-musleabihf': 4.53.3 + '@rollup/rollup-linux-arm64-gnu': 4.53.3 + '@rollup/rollup-linux-arm64-musl': 4.53.3 + '@rollup/rollup-linux-loong64-gnu': 4.53.3 + '@rollup/rollup-linux-ppc64-gnu': 4.53.3 + '@rollup/rollup-linux-riscv64-gnu': 4.53.3 + '@rollup/rollup-linux-riscv64-musl': 4.53.3 + '@rollup/rollup-linux-s390x-gnu': 4.53.3 + '@rollup/rollup-linux-x64-gnu': 4.53.3 + '@rollup/rollup-linux-x64-musl': 4.53.3 + '@rollup/rollup-openharmony-arm64': 4.53.3 + '@rollup/rollup-win32-arm64-msvc': 4.53.3 + '@rollup/rollup-win32-ia32-msvc': 4.53.3 + '@rollup/rollup-win32-x64-gnu': 4.53.3 + '@rollup/rollup-win32-x64-msvc': 4.53.3 + fsevents: 2.3.3 + + rope-sequence@1.3.4: {} + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -3679,12 +8039,20 @@ snapshots: es-errors: 1.3.0 is-regex: 1.2.1 + safer-buffer@2.1.2: {} + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + scheduler@0.27.0: {} semver@6.3.1: {} semver@7.7.3: {} + set-cookie-parser@2.7.2: {} + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -3773,10 +8141,23 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + siginfo@2.0.0: {} + + sonner@2.0.7(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + dependencies: + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + source-map-js@1.2.1: {} + space-separated-tokens@2.0.2: {} + stable-hash@0.0.5: {} + stackback@0.0.2: {} + + std-env@3.10.0: {} + stop-iteration-iterator@1.1.0: dependencies: es-errors: 1.3.0 @@ -3832,6 +8213,11 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + strip-bom@3.0.0: {} strip-json-comments@3.1.1: {} @@ -3849,19 +8235,53 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + symbol-tree@3.2.4: {} + + tabbable@6.3.0: {} + + tailwind-merge@3.4.0: {} + tailwindcss@4.1.17: {} tapable@2.3.0: {} + tiny-invariant@1.3.3: {} + + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + tinyglobby@0.2.15: dependencies: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 + tinyrainbow@3.0.3: {} + + tldts-core@7.0.18: {} + + tldts@7.0.18: + dependencies: + tldts-core: 7.0.18 + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 + tough-cookie@6.0.0: + dependencies: + tldts: 7.0.18 + + tr46@6.0.0: + dependencies: + punycode: 2.3.1 + + trim-lines@3.0.1: {} + + trim-trailing-lines@2.1.0: {} + + trough@2.2.0: {} + ts-api-utils@2.1.0(typescript@5.9.3): dependencies: typescript: 5.9.3 @@ -3875,10 +8295,14 @@ snapshots: tslib@2.8.1: {} + tw-animate-css@1.4.0: {} + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 + type-fest@4.41.0: {} + typed-array-buffer@1.0.3: dependencies: call-bound: 1.0.4 @@ -3925,6 +8349,8 @@ snapshots: typescript@5.9.3: {} + uc.micro@2.1.0: {} + unbox-primitive@1.1.0: dependencies: call-bound: 1.0.4 @@ -3934,6 +8360,44 @@ snapshots: undici-types@6.21.0: {} + unified@11.0.5: + dependencies: + '@types/unist': 3.0.3 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 + + unist-util-find-after@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-is@6.0.1: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + unrs-resolver@1.11.1: dependencies: napi-postinstall: 0.3.4 @@ -3968,6 +8432,147 @@ snapshots: dependencies: punycode: 2.3.1 + use-callback-ref@1.3.3(@types/react@19.2.5)(react@19.2.0): + dependencies: + react: 19.2.0 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.5 + + use-composed-ref@1.4.0(@types/react@19.2.5)(react@19.2.0): + dependencies: + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.5 + + use-isomorphic-layout-effect@1.2.1(@types/react@19.2.5)(react@19.2.0): + dependencies: + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.5 + + use-latest@1.3.0(@types/react@19.2.5)(react@19.2.0): + dependencies: + react: 19.2.0 + use-isomorphic-layout-effect: 1.2.1(@types/react@19.2.5)(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.5 + + use-sidecar@1.1.3(@types/react@19.2.5)(react@19.2.0): + dependencies: + detect-node-es: 1.1.0 + react: 19.2.0 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.5 + + use-sync-external-store@1.6.0(react@19.2.0): + dependencies: + react: 19.2.0 + + uuid@13.0.0: {} + + uuid@8.3.2: {} + + vaul@1.1.2(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + dependencies: + '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.5))(@types/react@19.2.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + + vfile-location@5.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile: 6.0.3 + + vfile-message@4.0.3: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + + vite@7.2.4(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2): + dependencies: + esbuild: 0.25.12 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.53.3 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 20.19.25 + fsevents: 2.3.3 + jiti: 2.6.1 + lightningcss: 1.30.2 + + vitest@4.0.13(@types/debug@4.1.12)(@types/node@20.19.25)(jiti@2.6.1)(jsdom@27.2.0)(lightningcss@1.30.2): + dependencies: + '@vitest/expect': 4.0.13 + '@vitest/mocker': 4.0.13(vite@7.2.4(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)) + '@vitest/pretty-format': 4.0.13 + '@vitest/runner': 4.0.13 + '@vitest/snapshot': 4.0.13 + '@vitest/spy': 4.0.13 + '@vitest/utils': 4.0.13 + debug: 4.4.3 + es-module-lexer: 1.7.0 + expect-type: 1.2.2 + magic-string: 0.30.21 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.15 + tinyrainbow: 3.0.3 + vite: 7.2.4(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/debug': 4.1.12 + '@types/node': 20.19.25 + jsdom: 27.2.0 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + w3c-keyname@2.2.8: {} + + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + + web-namespaces@2.0.1: {} + + webidl-conversions@8.0.0: {} + + whatwg-encoding@3.1.1: + dependencies: + iconv-lite: 0.6.3 + + whatwg-mimetype@4.0.0: {} + + whatwg-url@15.1.0: + dependencies: + tr46: 6.0.0 + webidl-conversions: 8.0.0 + which-boxed-primitive@1.1.1: dependencies: is-bigint: 1.1.0 @@ -4013,10 +8618,39 @@ snapshots: dependencies: isexe: 2.0.0 + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + word-wrap@1.2.5: {} + ws@8.18.3: {} + + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + + y-prosemirror@1.3.7(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.3)(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27): + dependencies: + lib0: 0.2.114 + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-view: 1.41.3 + y-protocols: 1.0.6(yjs@13.6.27) + yjs: 13.6.27 + + y-protocols@1.0.6(yjs@13.6.27): + dependencies: + lib0: 0.2.114 + yjs: 13.6.27 + yallist@3.1.1: {} + yjs@13.6.27: + dependencies: + lib0: 0.2.114 + yocto-queue@0.1.0: {} zod-validation-error@4.0.2(zod@4.1.12): @@ -4024,3 +8658,18 @@ snapshots: zod: 4.1.12 zod@4.1.12: {} + + zustand@4.5.7(@types/react@19.2.5)(react@19.2.0): + dependencies: + use-sync-external-store: 1.6.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.5 + react: 19.2.0 + + zustand@5.0.8(@types/react@19.2.5)(react@19.2.0)(use-sync-external-store@1.6.0(react@19.2.0)): + optionalDependencies: + '@types/react': 19.2.5 + react: 19.2.0 + use-sync-external-store: 1.6.0(react@19.2.0) + + zwitch@2.0.4: {} diff --git a/src/app/(app)/documents/[id]/loading.tsx b/src/app/(app)/documents/[id]/loading.tsx new file mode 100644 index 00000000..c1020817 --- /dev/null +++ b/src/app/(app)/documents/[id]/loading.tsx @@ -0,0 +1,11 @@ +import { Skeleton } from "@/components/ui/skeleton"; + +export default function LoadingDocument() { + return ( +
+ + + +
+ ); +} diff --git a/src/app/(app)/documents/[id]/page.tsx b/src/app/(app)/documents/[id]/page.tsx new file mode 100644 index 00000000..df8137c0 --- /dev/null +++ b/src/app/(app)/documents/[id]/page.tsx @@ -0,0 +1,61 @@ +import { notFound, redirect } from "next/navigation"; +import { createSupabaseServerClient } from "@/lib/supabase/server"; +import { DocumentShell } from "@/components/editor/document-shell"; +import type { PageOptionsState, DocumentStats } from "@/types/page-options"; + +interface DocumentPageProps { + params: Promise<{ id: string }>; +} + +export default async function DocumentPage({ params }: DocumentPageProps) { + const { id } = await params; + const supabase = await createSupabaseServerClient(); + const { + data: { session }, + } = await supabase.auth.getSession(); + + if (!session) { + redirect("/login"); + } + + const { data: document } = await supabase + .from("documents") + .select( + "id,title,content,updated_at,workspace_id,wide_layout,use_small_text,show_heading_numbers,show_toc,show_structure,protect_editing,show_word_count,word_count,character_count,block_count", + ) + .eq("user_id", session.user.id) + .eq("id", id) + .single(); + + if (!document) { + notFound(); + } + + const initialOptions: PageOptionsState = { + wideLayout: document.wide_layout ?? false, + smallText: document.use_small_text ?? false, + showHeadingNumbers: document.show_heading_numbers ?? true, + showToc: document.show_toc ?? false, + showStructure: document.show_structure ?? false, + protectEditing: document.protect_editing ?? false, + showWordCount: document.show_word_count ?? true, + }; + + const initialStats: DocumentStats = { + wordCount: document.word_count ?? 0, + characterCount: document.character_count ?? 0, + blockCount: document.block_count ?? 0, + }; + + return ( + + ); +} diff --git a/src/app/(app)/layout.tsx b/src/app/(app)/layout.tsx new file mode 100644 index 00000000..0dd793e7 --- /dev/null +++ b/src/app/(app)/layout.tsx @@ -0,0 +1,55 @@ +import { redirect } from "next/navigation"; +import type { ReactNode } from "react"; +import { Sidebar } from "@/components/sidebar/sidebar"; +import { Breadcrumb } from "@/components/breadcrumb"; +import { BottomToolbar } from "@/components/bottom-toolbar"; +import { MobileSidebarTrigger } from "@/components/mobile-sidebar-trigger"; +import type { DocumentRecord } from "@/lib/documents"; +import { createSupabaseServerClient } from "@/lib/supabase/server"; +import { ensureDefaultWorkspace, fetchWorkspaceSummaries } from "@/lib/workspaces"; +import { fetchSidebarDataset } from "@/lib/sidebar-tree"; +import type { SidebarInitialData } from "@/components/sidebar/types"; +import { SearchPalette } from "@/components/search/search-palette"; + +export default async function AppLayout({ children }: { children: ReactNode }) { + const supabase = await createSupabaseServerClient(); + const { + data: { session }, + } = await supabase.auth.getSession(); + + if (!session) { + redirect("/login"); + } + + await ensureDefaultWorkspace(supabase, session.user.id, session.user.email ?? "我的空间"); + const { workspaces, activeWorkspaceId } = await fetchWorkspaceSummaries(supabase, session.user.id); + + let documents: DocumentRecord[] = []; + let sidebarInitialData: SidebarInitialData | null = null; + + if (activeWorkspaceId) { + const dataset = await fetchSidebarDataset(supabase, activeWorkspaceId); + documents = dataset.documents; + sidebarInitialData = { + activeWorkspaceId, + workspaces, + documents: dataset.documents, + trashedDocuments: dataset.trashedDocuments, + }; + } + + return ( +
+ {sidebarInitialData && } +
+
+ + +
+
{children}
+ + +
+
+ ); +} diff --git a/src/app/(auth)/login/page.tsx b/src/app/(auth)/login/page.tsx new file mode 100644 index 00000000..971513e3 --- /dev/null +++ b/src/app/(auth)/login/page.tsx @@ -0,0 +1,91 @@ +"use client"; + +import { useCallback, useState } from "react"; +import { useRouter } from "next/navigation"; +import { supabaseBrowser } from "@/lib/supabase/client"; +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Separator } from "@/components/ui/separator"; + +export default function LoginPage() { + const router = useRouter(); + const [email, setEmail] = useState("test@163.com"); + const [password, setPassword] = useState("123456"); + const [loading, setLoading] = useState(false); + const [message, setMessage] = useState(""); + const [sessionInfo, setSessionInfo] = useState(""); + + const handleLogin = useCallback(async () => { + setLoading(true); + setMessage(""); + const { data, error } = await supabaseBrowser.auth.signInWithPassword({ + email, + password, + }); + setLoading(false); + if (error) { + setMessage(`登录失败:${error.message}`); + return; + } + setMessage("登录成功,准备跳转..."); + setSessionInfo(JSON.stringify(data.session, null, 2)); + router.replace("/"); + }, [email, password, router]); + + const handleGetSession = useCallback(async () => { + setLoading(true); + const { + data: { session }, + error, + } = await supabaseBrowser.auth.getSession(); + setLoading(false); + if (error) { + setMessage(`获取会话失败:${error.message}`); + return; + } + if (!session) { + setMessage("当前未登录"); + setSessionInfo(""); + return; + } + setMessage("发现有效 Session"); + setSessionInfo(JSON.stringify(session, null, 2)); + }, []); + + return ( +
+ + + Supabase 登录 +

测试账号:test@163.com / 123456

+
+ +
+ + setEmail(e.target.value)} /> +
+
+ + setPassword(e.target.value)} /> +
+
+ + +
+ {message &&

{message}

} + + {sessionInfo && ( +
+              {sessionInfo}
+            
+ )} +
+
+
+ ); +} diff --git a/src/app/api/.gitkeep b/src/app/api/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/src/app/api/documents/create-child/route.ts b/src/app/api/documents/create-child/route.ts new file mode 100644 index 00000000..cd72c30a --- /dev/null +++ b/src/app/api/documents/create-child/route.ts @@ -0,0 +1,99 @@ +import { NextResponse } from "next/server"; +import { createSupabaseRouteClient } from "@/lib/supabase/server"; +import { ensureDefaultWorkspace, resolveActiveWorkspaceId } from "@/lib/workspaces"; + +type CreateChildPayload = { + parentId: string | null; + title?: string; + blocks?: unknown; +}; + +export const dynamic = "force-dynamic"; + +export async function POST(request: Request) { + const supabase = await createSupabaseRouteClient(); + const { + data: { session }, + } = await supabase.auth.getSession(); + + if (!session) { + return NextResponse.json({ error: "未登录无法创建页面" }, { status: 401 }); + } + + const { parentId, title, blocks }: CreateChildPayload = await request.json(); + + if (typeof parentId === "undefined") { + return NextResponse.json({ error: "缺少 parentId" }, { status: 400 }); + } + + let workspaceId: string; + let accessScope: "private" | "shared" | "public" = "private"; + + if (parentId) { + const { data: parentDoc, error: parentError } = await supabase + .from("documents") + .select("workspace_id,access_scope") + .eq("id", parentId) + .single(); + + if (parentError || !parentDoc) { + return NextResponse.json({ error: "父页面不存在" }, { status: 404 }); + } + workspaceId = parentDoc.workspace_id; + accessScope = (parentDoc.access_scope ?? "private") as typeof accessScope; + } else { + await ensureDefaultWorkspace(supabase, session.user.id, session.user.email ?? "我的空间"); + workspaceId = await resolveActiveWorkspaceId(supabase, session.user.id); + if (!workspaceId) { + return NextResponse.json({ error: "缺少目标工作空间" }, { status: 400 }); + } + } + + const siblingQuery = supabase + .from("documents") + .select("id", { head: true, count: "exact" }) + .eq("workspace_id", workspaceId); + + if (parentId) { + siblingQuery.eq("parent_id", parentId); + } else { + siblingQuery.is("parent_id", null); + } + + const { count: siblingCount = 0, error: countError } = await siblingQuery; + + if (countError) { + return NextResponse.json({ error: countError.message }, { status: 500 }); + } + + const resolvedTitle = + title && title.trim().length > 0 ? title.trim() : "未命名页面"; + + const contentPayload = Array.isArray(blocks) ? blocks : []; + + const { data, error } = await supabase + .from("documents") + .insert({ + user_id: session.user.id, + parent_id: parentId, + workspace_id: workspaceId, + access_scope: accessScope, + title: resolvedTitle, + content: contentPayload, + sort_order: siblingCount, + }) + .select("id,title") + .single(); + + if (error || !data) { + return NextResponse.json( + { error: error?.message ?? "创建子页面失败" }, + { status: 500 }, + ); + } + + return NextResponse.json({ + pageId: data.id, + title: data.title ?? resolvedTitle, + }); +} diff --git a/src/app/api/documents/create/route.ts b/src/app/api/documents/create/route.ts new file mode 100644 index 00000000..9672e21c --- /dev/null +++ b/src/app/api/documents/create/route.ts @@ -0,0 +1,123 @@ +import { randomUUID } from "crypto"; +import { NextResponse } from "next/server"; +import { createSupabaseRouteClient } from "@/lib/supabase/server"; +import { ensureDefaultWorkspace, resolveActiveWorkspaceId } from "@/lib/workspaces"; +import type { Json } from "@/types/supabase"; +import { composeContentWithBlocks, extractBlocksFromContent } from "@/lib/document-content"; + +export async function POST(request: Request) { + try { + return await handleCreateRequest(request); + } catch (error) { + console.error("创建页面失败", error); + const message = error instanceof Error ? error.message : "创建页面失败,请稍后再试"; + return NextResponse.json({ error: message }, { status: 500 }); + } +} + +async function handleCreateRequest(request: Request) { + const supabase = await createSupabaseRouteClient(); + const { + data: { session }, + } = await supabase.auth.getSession(); + + if (!session) { + return NextResponse.json({ error: "未登录" }, { status: 401 }); + } + + const { parentId }: { parentId?: string | null } = await request.json(); + + await ensureDefaultWorkspace(supabase, session.user.id, session.user.email ?? "我的空间"); + + let workspaceId: string | null = null; + let parentContent: Json | null = null; + let accessScope: "private" | "shared" | "public" = "private"; + + if (parentId) { + const { data: parentDoc, error: parentError } = await supabase + .from("documents") + .select("workspace_id,access_scope,content,user_id") + .eq("id", parentId) + .eq("user_id", session.user.id) + .single(); + + if (parentError || !parentDoc) { + return NextResponse.json({ error: "父页面不存在或无权限" }, { status: 404 }); + } + workspaceId = parentDoc.workspace_id; + accessScope = (parentDoc.access_scope ?? "private") as typeof accessScope; + parentContent = parentDoc.content; + } else { + workspaceId = await resolveActiveWorkspaceId(supabase, session.user.id); + if (!workspaceId) { + return NextResponse.json({ error: "缺少目标工作空间" }, { status: 400 }); + } + } + + if (!workspaceId) { + return NextResponse.json({ error: "缺少目标工作空间" }, { status: 400 }); + } + + const siblingQuery = supabase + .from("documents") + .select("id", { head: true, count: "exact" }) + .eq("workspace_id", workspaceId); + + if (parentId) { + siblingQuery.eq("parent_id", parentId); + } else { + siblingQuery.is("parent_id", null); + } + + const { count: siblingCount = 0, error: countError } = await siblingQuery; + + if (countError) { + return NextResponse.json({ error: countError.message }, { status: 500 }); + } + + const { data, error } = await supabase + .from("documents") + .insert({ + user_id: session.user.id, + parent_id: parentId ?? null, + workspace_id: workspaceId, + title: "无标题", + content: { blocks: [] }, + access_scope: accessScope, + sort_order: siblingCount, + }) + .select( + "id,title,parent_id,sort_order,is_starred,created_at,updated_at,workspace_id,access_scope,is_template", + ) + .single(); + + if (error) { + return NextResponse.json({ error: error.message }, { status: 400 }); + } + + if (parentId && data) { + const existingBlocks = extractBlocksFromContent(parentContent); + const pageReferenceBlock = { + id: randomUUID(), + type: "pageReference", + props: { + pageId: data.id, + title: data.title ?? "无标题", + }, + }; + const nextBlocks = [...existingBlocks, pageReferenceBlock as Json]; + const payload = composeContentWithBlocks(parentContent, nextBlocks); + const timestamp = new Date().toISOString(); + const { error: parentUpdateError } = await supabase + .from("documents") + .update({ content: payload, updated_at: timestamp }) + .eq("id", parentId) + .eq("user_id", session.user.id); + + if (parentUpdateError) { + return NextResponse.json({ error: parentUpdateError.message }, { status: 500 }); + } + } + + return NextResponse.json(data); +} diff --git a/src/app/api/documents/delete/route.ts b/src/app/api/documents/delete/route.ts new file mode 100644 index 00000000..8e75acfa --- /dev/null +++ b/src/app/api/documents/delete/route.ts @@ -0,0 +1,36 @@ +import { NextResponse } from "next/server"; +import { createSupabaseRouteClient } from "@/lib/supabase/server"; + +export const dynamic = "force-dynamic"; + +export async function POST(request: Request) { + const supabase = await createSupabaseRouteClient(); + const { + data: { session }, + } = await supabase.auth.getSession(); + + if (!session) { + return NextResponse.json({ error: "未登录" }, { status: 401 }); + } + + const { documentId } = await request.json(); + + if (!documentId) { + return NextResponse.json({ error: "缺少 documentId" }, { status: 400 }); + } + + const { error } = await supabase + .from("documents") + .update({ + deleted_at: new Date().toISOString(), + deleted_by: session.user.id, + }) + .eq("id", documentId) + .eq("user_id", session.user.id); + + if (error) { + return NextResponse.json({ error: error.message }, { status: 500 }); + } + + return NextResponse.json({ success: true }); +} diff --git a/src/app/api/documents/duplicate/route.ts b/src/app/api/documents/duplicate/route.ts new file mode 100644 index 00000000..6dc2a403 --- /dev/null +++ b/src/app/api/documents/duplicate/route.ts @@ -0,0 +1,74 @@ +import { NextResponse } from "next/server"; +import { createSupabaseRouteClient } from "@/lib/supabase/server"; + +interface DuplicatePayload { + documentId: string; +} + +export async function POST(request: Request) { + const supabase = await createSupabaseRouteClient(); + const { + data: { session }, + } = await supabase.auth.getSession(); + + if (!session) { + return NextResponse.json({ error: "未登录" }, { status: 401 }); + } + + const { documentId }: DuplicatePayload = await request.json(); + + if (!documentId) { + return NextResponse.json({ error: "缺少 documentId" }, { status: 400 }); + } + + const { data: sourceDoc, error: sourceError } = await supabase + .from("documents") + .select("id,title,content,parent_id,workspace_id,access_scope") + .eq("id", documentId) + .eq("user_id", session.user.id) + .single(); + + if (sourceError || !sourceDoc) { + return NextResponse.json({ error: "页面不存在或无权访问" }, { status: 404 }); + } + + const siblingQuery = supabase + .from("documents") + .select("id", { head: true, count: "exact" }) + .eq("workspace_id", sourceDoc.workspace_id); + + if (sourceDoc.parent_id) { + siblingQuery.eq("parent_id", sourceDoc.parent_id); + } else { + siblingQuery.is("parent_id", null); + } + + const { count: siblingCount = 0, error: countError } = await siblingQuery; + + if (countError) { + return NextResponse.json({ error: countError.message }, { status: 500 }); + } + + const fallbackTitle = sourceDoc.title?.trim() && sourceDoc.title.trim().length > 0 ? sourceDoc.title.trim() : "无标题"; + const duplicatedTitle = `${fallbackTitle} 副本`; + + const { data: duplicated, error: duplicateError } = await supabase + .from("documents") + .insert({ + user_id: session.user.id, + workspace_id: sourceDoc.workspace_id, + parent_id: sourceDoc.parent_id, + access_scope: sourceDoc.access_scope ?? "private", + title: duplicatedTitle, + content: sourceDoc.content, + sort_order: siblingCount, + }) + .select("id,title,parent_id,sort_order") + .single(); + + if (duplicateError || !duplicated) { + return NextResponse.json({ error: duplicateError?.message ?? "复制失败" }, { status: 500 }); + } + + return NextResponse.json(duplicated); +} diff --git a/src/app/api/documents/embed/route.ts b/src/app/api/documents/embed/route.ts new file mode 100644 index 00000000..6387d993 --- /dev/null +++ b/src/app/api/documents/embed/route.ts @@ -0,0 +1,76 @@ +import { randomUUID } from "crypto"; +import { NextResponse } from "next/server"; +import { createSupabaseRouteClient } from "@/lib/supabase/server"; +import type { Json } from "@/types/supabase"; +import { composeContentWithBlocks, extractBlocksFromContent } from "@/lib/document-content"; + +interface EmbedPayload { + sourceId: string; + targetId: string; +} + +export async function POST(request: Request) { + const supabase = await createSupabaseRouteClient(); + const { + data: { session }, + } = await supabase.auth.getSession(); + + if (!session) { + return NextResponse.json({ error: "未登录" }, { status: 401 }); + } + + const { sourceId, targetId }: EmbedPayload = await request.json(); + + if (!sourceId || !targetId) { + return NextResponse.json({ error: "缺少参数" }, { status: 400 }); + } + + const { data: sourceDoc, error: sourceError } = await supabase + .from("documents") + .select("id,title,user_id") + .eq("id", sourceId) + .eq("user_id", session.user.id) + .single(); + + if (sourceError || !sourceDoc) { + return NextResponse.json({ error: "原始页面不存在或无权限" }, { status: 404 }); + } + + const { data: targetDoc, error: targetError } = await supabase + .from("documents") + .select("id,content,user_id") + .eq("id", targetId) + .eq("user_id", session.user.id) + .single(); + + if (targetError || !targetDoc) { + return NextResponse.json({ error: "目标页面不存在或无权限" }, { status: 404 }); + } + + const currentBlocks = extractBlocksFromContent(targetDoc.content); + const nextBlocks: Json[] = [ + ...currentBlocks, + { + id: randomUUID(), + type: "pageReference", + props: { + pageId: sourceDoc.id, + title: sourceDoc.title ?? "无标题", + }, + }, + ]; + + const payload: Json = composeContentWithBlocks(targetDoc.content, nextBlocks); + + const { error: updateError } = await supabase + .from("documents") + .update({ content: payload }) + .eq("id", targetDoc.id) + .eq("user_id", session.user.id); + + if (updateError) { + return NextResponse.json({ error: updateError.message }, { status: 500 }); + } + + return NextResponse.json({ ok: true }); +} diff --git a/src/app/api/documents/empty-trash/route.ts b/src/app/api/documents/empty-trash/route.ts new file mode 100644 index 00000000..d6cd5578 --- /dev/null +++ b/src/app/api/documents/empty-trash/route.ts @@ -0,0 +1,53 @@ +import { NextResponse } from "next/server"; +import { createSupabaseRouteClient } from "@/lib/supabase/server"; + +export const dynamic = "force-dynamic"; + +interface EmptyTrashPayload { + workspaceId?: string; +} + +export async function POST(request: Request) { + const supabase = await createSupabaseRouteClient(); + const { + data: { session }, + } = await supabase.auth.getSession(); + + if (!session) { + return NextResponse.json({ error: "未登录" }, { status: 401 }); + } + + const { workspaceId }: EmptyTrashPayload = await request.json(); + + if (!workspaceId) { + return NextResponse.json({ error: "缺少 workspaceId" }, { status: 400 }); + } + + const { data: membership, error: membershipError } = await supabase + .from("workspace_members") + .select("workspace_id") + .eq("workspace_id", workspaceId) + .eq("user_id", session.user.id) + .limit(1); + + if (membershipError) { + return NextResponse.json({ error: membershipError.message }, { status: 500 }); + } + + if (!membership || membership.length === 0) { + return NextResponse.json({ error: "无权操作该工作空间" }, { status: 403 }); + } + + const { error } = await supabase + .from("documents") + .delete() + .eq("workspace_id", workspaceId) + .eq("user_id", session.user.id) + .not("deleted_at", "is", null); + + if (error) { + return NextResponse.json({ error: error.message }, { status: 500 }); + } + + return NextResponse.json({ success: true }); +} diff --git a/src/app/api/documents/move/route.ts b/src/app/api/documents/move/route.ts new file mode 100644 index 00000000..90b6158b --- /dev/null +++ b/src/app/api/documents/move/route.ts @@ -0,0 +1,37 @@ +import { NextResponse } from "next/server"; +import { createSupabaseRouteClient } from "@/lib/supabase/server"; + +interface MovePayload { + documentId: string; + parentId?: string | null; + position: number; +} + +export async function POST(request: Request) { + const supabase = await createSupabaseRouteClient(); + const { + data: { session }, + } = await supabase.auth.getSession(); + + if (!session) { + return NextResponse.json({ error: "未登录" }, { status: 401 }); + } + + const { documentId, parentId = null, position }: MovePayload = await request.json(); + const sortOrder = Number.isFinite(position) ? Math.floor(position) : 0; + + const { error } = await supabase + .from("documents") + .update({ + parent_id: parentId, + sort_order: sortOrder, + }) + .eq("id", documentId) + .eq("user_id", session.user.id); + + if (error) { + return NextResponse.json({ error: error.message }, { status: 400 }); + } + + return NextResponse.json({ ok: true }); +} diff --git a/src/app/api/documents/options/route.ts b/src/app/api/documents/options/route.ts new file mode 100644 index 00000000..63a6e9c2 --- /dev/null +++ b/src/app/api/documents/options/route.ts @@ -0,0 +1,61 @@ +import { NextResponse } from "next/server"; +import { createSupabaseRouteClient } from "@/lib/supabase/server"; +import type { PageOptionsState } from "@/types/page-options"; +import type { Database } from "@/types/supabase"; + +type OptionsPayload = { + documentId: string; + options: Partial; +}; + +const COLUMN_MAP: Record = { + wideLayout: "wide_layout", + smallText: "use_small_text", + showHeadingNumbers: "show_heading_numbers", + showToc: "show_toc", + showStructure: "show_structure", + protectEditing: "protect_editing", + showWordCount: "show_word_count", +}; + +export async function POST(request: Request) { + const supabase = await createSupabaseRouteClient(); + const { + data: { session }, + } = await supabase.auth.getSession(); + + if (!session) { + return NextResponse.json({ error: "未登录" }, { status: 401 }); + } + + const { documentId, options }: OptionsPayload = await request.json(); + if (!documentId || !options) { + return NextResponse.json({ error: "缺少参数" }, { status: 400 }); + } + + const updatePayload: Record = {}; + (Object.keys(options) as (keyof PageOptionsState)[]).forEach((key) => { + const column = COLUMN_MAP[key]; + if (!column) return; + const value = options[key]; + if (typeof value === "boolean") { + updatePayload[column as string] = value; + } + }); + + if (Object.keys(updatePayload).length === 0) { + return NextResponse.json({ error: "缺少可更新的选项" }, { status: 400 }); + } + + const { error } = await supabase + .from("documents") + .update(updatePayload) + .eq("id", documentId) + .eq("user_id", session.user.id); + + if (error) { + return NextResponse.json({ error: error.message }, { status: 500 }); + } + + return NextResponse.json({ ok: true }); +} diff --git a/src/app/api/documents/purge/route.ts b/src/app/api/documents/purge/route.ts new file mode 100644 index 00000000..ac1a7569 --- /dev/null +++ b/src/app/api/documents/purge/route.ts @@ -0,0 +1,33 @@ +import { NextResponse } from "next/server"; +import { createSupabaseRouteClient } from "@/lib/supabase/server"; + +export const dynamic = "force-dynamic"; + +export async function POST(request: Request) { + const supabase = await createSupabaseRouteClient(); + const { + data: { session }, + } = await supabase.auth.getSession(); + + if (!session) { + return NextResponse.json({ error: "未登录" }, { status: 401 }); + } + + const { documentId } = await request.json(); + + if (!documentId) { + return NextResponse.json({ error: "缺少 documentId" }, { status: 400 }); + } + + const { error } = await supabase + .from("documents") + .delete() + .eq("id", documentId) + .eq("user_id", session.user.id); + + if (error) { + return NextResponse.json({ error: error.message }, { status: 500 }); + } + + return NextResponse.json({ success: true }); +} diff --git a/src/app/api/documents/restore/route.ts b/src/app/api/documents/restore/route.ts new file mode 100644 index 00000000..f66c2894 --- /dev/null +++ b/src/app/api/documents/restore/route.ts @@ -0,0 +1,38 @@ +import { NextResponse } from "next/server"; +import { createSupabaseRouteClient } from "@/lib/supabase/server"; + +export const dynamic = "force-dynamic"; + +export async function POST(request: Request) { + const supabase = await createSupabaseRouteClient(); + const { + data: { session }, + } = await supabase.auth.getSession(); + + if (!session) { + return NextResponse.json({ error: "未登录" }, { status: 401 }); + } + + const { documentId } = await request.json(); + + if (!documentId) { + return NextResponse.json({ error: "缺少 documentId" }, { status: 400 }); + } + + const { error } = await supabase + .from("documents") + .update({ + deleted_at: null, + deleted_by: null, + parent_id: null, + access_scope: "private", + }) + .eq("id", documentId) + .eq("user_id", session.user.id); + + if (error) { + return NextResponse.json({ error: error.message }, { status: 500 }); + } + + return NextResponse.json({ success: true }); +} diff --git a/src/app/api/documents/save/route.ts b/src/app/api/documents/save/route.ts new file mode 100644 index 00000000..70e917c8 --- /dev/null +++ b/src/app/api/documents/save/route.ts @@ -0,0 +1,32 @@ +import { NextResponse } from "next/server"; +import { createSupabaseRouteClient } from "@/lib/supabase/server"; + +interface SavePayload { + documentId: string; + content: unknown; +} + +export async function POST(request: Request) { + const supabase = await createSupabaseRouteClient(); + const { + data: { session }, + } = await supabase.auth.getSession(); + + if (!session) { + return NextResponse.json({ error: "未登录" }, { status: 401 }); + } + + const { documentId, content }: SavePayload = await request.json(); + + const { error } = await supabase + .from("documents") + .update({ content }) + .eq("id", documentId) + .eq("user_id", session.user.id); + + if (error) { + return NextResponse.json({ error: error.message }, { status: 400 }); + } + + return NextResponse.json({ ok: true }); +} diff --git a/src/app/api/documents/stats/route.ts b/src/app/api/documents/stats/route.ts new file mode 100644 index 00000000..a69e6127 --- /dev/null +++ b/src/app/api/documents/stats/route.ts @@ -0,0 +1,40 @@ +import { NextResponse } from "next/server"; +import { createSupabaseRouteClient } from "@/lib/supabase/server"; +import type { DocumentStats } from "@/types/page-options"; + +interface StatsPayload { + documentId: string; + stats: DocumentStats; +} + +export async function POST(request: Request) { + const supabase = await createSupabaseRouteClient(); + const { + data: { session }, + } = await supabase.auth.getSession(); + + if (!session) { + return NextResponse.json({ error: "未登录" }, { status: 401 }); + } + + const { documentId, stats }: StatsPayload = await request.json(); + if (!documentId || !stats) { + return NextResponse.json({ error: "缺少参数" }, { status: 400 }); + } + + const { error } = await supabase + .from("documents") + .update({ + word_count: stats.wordCount, + character_count: stats.characterCount, + block_count: stats.blockCount, + }) + .eq("id", documentId) + .eq("user_id", session.user.id); + + if (error) { + return NextResponse.json({ error: error.message }, { status: 500 }); + } + + return NextResponse.json({ ok: true }); +} diff --git a/src/app/api/documents/title/route.ts b/src/app/api/documents/title/route.ts new file mode 100644 index 00000000..f3888bb1 --- /dev/null +++ b/src/app/api/documents/title/route.ts @@ -0,0 +1,32 @@ +import { NextResponse } from "next/server"; +import { createSupabaseRouteClient } from "@/lib/supabase/server"; + +interface RenamePayload { + documentId: string; + title: string; +} + +export async function POST(request: Request) { + const supabase = await createSupabaseRouteClient(); + const { + data: { session }, + } = await supabase.auth.getSession(); + + if (!session) { + return NextResponse.json({ error: "未登录" }, { status: 401 }); + } + + const { documentId, title }: RenamePayload = await request.json(); + + const { error } = await supabase + .from("documents") + .update({ title }) + .eq("id", documentId) + .eq("user_id", session.user.id); + + if (error) { + return NextResponse.json({ error: error.message }, { status: 400 }); + } + + return NextResponse.json({ ok: true }); +} diff --git a/src/app/api/media/assets/route.ts b/src/app/api/media/assets/route.ts new file mode 100644 index 00000000..6c717a05 --- /dev/null +++ b/src/app/api/media/assets/route.ts @@ -0,0 +1,92 @@ +import { NextResponse } from "next/server"; +import { createSupabaseRouteClient } from "@/lib/supabase/server"; +import type { MediaAsset } from "@/types/media"; + +export const dynamic = "force-dynamic"; + +export async function GET(request: Request) { + const supabase = await createSupabaseRouteClient(); + const { + data: { session }, + } = await supabase.auth.getSession(); + + if (!session) { + return NextResponse.json({ error: "未登录" }, { status: 401 }); + } + + const { searchParams } = new URL(request.url); + const workspaceId = searchParams.get("workspaceId"); + const limit = Number.parseInt(searchParams.get("limit") ?? "12", 10); + + if (!workspaceId) { + return NextResponse.json({ error: "缺少 workspaceId" }, { status: 400 }); + } + + let query = supabase + .from("media_assets") + .select("*") + .eq("workspace_id", workspaceId) + .order("created_at", { ascending: false }) + .limit(Number.isNaN(limit) ? 12 : limit); + + const assetType = searchParams.get("assetType"); + if (assetType) { + query = query.eq("asset_type", assetType); + } + + const { data, error } = await query; + + if (error) { + return NextResponse.json({ error: error.message }, { status: 500 }); + } + + return NextResponse.json({ items: (data ?? []) as MediaAsset[] }); +} + +export async function POST(request: Request) { + const supabase = await createSupabaseRouteClient(); + const { + data: { session }, + } = await supabase.auth.getSession(); + + if (!session) { + return NextResponse.json({ error: "未登录" }, { status: 401 }); + } + + const payload = (await request.json()) as { + workspaceId: string; + documentId: string; + fileUrl: string; + thumbnailUrl?: string; + assetType?: string; + fileName?: string; + fileSize?: number; + mimeType?: string; + }; + + if (!payload.workspaceId || !payload.documentId || !payload.fileUrl) { + return NextResponse.json({ error: "参数不完整" }, { status: 400 }); + } + + const { data, error } = await supabase + .from("media_assets") + .insert({ + workspace_id: payload.workspaceId, + document_id: payload.documentId, + file_url: payload.fileUrl, + thumbnail_url: payload.thumbnailUrl ?? payload.fileUrl, + asset_type: payload.assetType ?? "image", + file_name: payload.fileName, + file_size: payload.fileSize ?? null, + mime_type: payload.mimeType ?? null, + created_by: session.user.id, + }) + .select("*") + .single(); + + if (error) { + return NextResponse.json({ error: error.message }, { status: 500 }); + } + + return NextResponse.json({ asset: data as MediaAsset }); +} diff --git a/src/app/api/media/ocr/route.ts b/src/app/api/media/ocr/route.ts new file mode 100644 index 00000000..7a172cc2 --- /dev/null +++ b/src/app/api/media/ocr/route.ts @@ -0,0 +1,43 @@ +import { NextResponse } from "next/server"; +import { createSupabaseRouteClient } from "@/lib/supabase/server"; + +export async function POST(request: Request) { + const supabase = await createSupabaseRouteClient(); + const { + data: { session }, + } = await supabase.auth.getSession(); + + if (!session) { + return NextResponse.json({ error: "未登录" }, { status: 401 }); + } + + const { assetId } = (await request.json()) as { assetId?: string }; + if (!assetId) { + return NextResponse.json({ error: "缺少 assetId" }, { status: 400 }); + } + + const { error } = await supabase + .from("media_assets") + .update({ ocr_status: "processing" }) + .eq("id", assetId) + .limit(1); + + if (error) { + return NextResponse.json({ error: error.message }, { status: 500 }); + } + + const backendUrl = process.env.NEXT_PUBLIC_BACKEND_URL; + if (backendUrl) { + void fetch(`${backendUrl}/api/v1/tasks/media-ocr`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ asset_id: assetId }), + }).catch((err) => { + console.warn("触发后端 OCR 失败", err); + }); + } + + return NextResponse.json({ ok: true }); +} diff --git a/src/app/api/media/upload/route.ts b/src/app/api/media/upload/route.ts new file mode 100644 index 00000000..ddb79e0c --- /dev/null +++ b/src/app/api/media/upload/route.ts @@ -0,0 +1,82 @@ +import { NextResponse } from "next/server"; +import { createSupabaseRouteClient } from "@/lib/supabase/server"; +import type { MediaAsset } from "@/types/media"; +import { extname } from "path"; + +export const dynamic = "force-dynamic"; + +const MEDIA_BUCKET = process.env.NEXT_PUBLIC_SUPABASE_MEDIA_BUCKET ?? "media"; + +const resolveAssetType = (mime: string): "image" | "video" | "audio" | "file" => { + if (mime.startsWith("image/")) return "image"; + if (mime.startsWith("video/")) return "video"; + if (mime.startsWith("audio/")) return "audio"; + return "file"; +}; + +export async function POST(request: Request) { + const supabase = await createSupabaseRouteClient(); + const { + data: { session }, + } = await supabase.auth.getSession(); + + if (!session) { + return NextResponse.json({ error: "未登录" }, { status: 401 }); + } + + const formData = await request.formData(); + const file = formData.get("file"); + const workspaceId = String(formData.get("workspaceId") ?? ""); + const documentId = String(formData.get("documentId") ?? ""); + + if (!(file instanceof File) || !workspaceId || !documentId) { + return NextResponse.json({ error: "缺少必要参数" }, { status: 400 }); + } + + try { + const arrayBuffer = await file.arrayBuffer(); + const buffer = Buffer.from(arrayBuffer); + const extension = extname(file.name || "").replace(/\s+/g, ""); + const uniqueId = typeof crypto.randomUUID === "function" ? crypto.randomUUID() : Math.random().toString(36).slice(2); + const path = `${workspaceId}/${Date.now()}-${uniqueId}${extension}`; + const assetType = resolveAssetType(file.type || ""); + + const { error: uploadError } = await supabase.storage.from(MEDIA_BUCKET).upload(path, buffer, { + contentType: file.type, + upsert: false, + }); + + if (uploadError) { + return NextResponse.json({ error: uploadError.message }, { status: 500 }); + } + + const { + data: { publicUrl }, + } = supabase.storage.from(MEDIA_BUCKET).getPublicUrl(path); + + const { data: asset, error } = await supabase + .from("media_assets") + .insert({ + workspace_id: workspaceId, + document_id: documentId, + file_url: publicUrl, + thumbnail_url: publicUrl, + asset_type: assetType, + file_name: file.name, + file_size: file.size, + mime_type: file.type, + created_by: session.user.id, + }) + .select("*") + .single(); + + if (error) { + return NextResponse.json({ error: error.message }, { status: 500 }); + } + + return NextResponse.json({ asset: asset as MediaAsset }); + } catch (error) { + console.error(error); + return NextResponse.json({ error: "上传失败" }, { status: 500 }); + } +} diff --git a/src/app/api/references/backlinks/route.ts b/src/app/api/references/backlinks/route.ts new file mode 100644 index 00000000..2686f3de --- /dev/null +++ b/src/app/api/references/backlinks/route.ts @@ -0,0 +1,50 @@ +import { NextResponse } from "next/server"; +import { createSupabaseRouteClient } from "@/lib/supabase/server"; + +export async function GET(request: Request) { + const supabase = await createSupabaseRouteClient(); + const { + data: { session }, + } = await supabase.auth.getSession(); + + if (!session) { + return NextResponse.json({ error: "未登录" }, { status: 401 }); + } + + const { searchParams } = new URL(request.url); + const workspaceId = searchParams.get("workspaceId"); + const pageId = searchParams.get("pageId"); + const limit = Number(searchParams.get("limit") ?? "50"); + const offset = Number(searchParams.get("offset") ?? "0"); + + if (!workspaceId || !pageId) { + return NextResponse.json({ error: "缺少 workspaceId 或 pageId" }, { status: 400 }); + } + + const { data: membership, error: membershipError } = await supabase + .from("workspace_members") + .select("workspace_id") + .eq("workspace_id", workspaceId) + .eq("user_id", session.user.id) + .maybeSingle(); + + if (membershipError) { + return NextResponse.json({ error: membershipError.message }, { status: 500 }); + } + if (!membership) { + return NextResponse.json({ error: "无权访问该工作空间" }, { status: 403 }); + } + + const { data, error } = await supabase.rpc("list_backlinks", { + p_workspace_id: workspaceId, + p_target_page_id: pageId, + p_limit: Number.isFinite(limit) ? limit : 50, + p_offset: Number.isFinite(offset) ? offset : 0, + }); + + if (error) { + return NextResponse.json({ error: error.message }, { status: 500 }); + } + + return NextResponse.json({ backlinks: data ?? [] }); +} diff --git a/src/app/api/references/record/route.ts b/src/app/api/references/record/route.ts new file mode 100644 index 00000000..48a0ce95 --- /dev/null +++ b/src/app/api/references/record/route.ts @@ -0,0 +1,68 @@ +import { NextResponse } from "next/server"; +import { createSupabaseRouteClient } from "@/lib/supabase/server"; + +type DisplayMode = "inline" | "embed"; + +interface RecordReferencePayload { + workspaceId: string; + sourcePageId: string; + targetPageId: string; + sourceBlockId?: string | null; + alias?: string | null; + displayMode: DisplayMode; + isPreviewable?: boolean; +} + +const isValidDisplayMode = (mode: string): mode is DisplayMode => mode === "inline" || mode === "embed"; + +export async function POST(request: Request) { + const supabase = await createSupabaseRouteClient(); + const { + data: { session }, + } = await supabase.auth.getSession(); + + if (!session) { + return NextResponse.json({ error: "未登录" }, { status: 401 }); + } + + const body = (await request.json()) as RecordReferencePayload; + const { workspaceId, sourcePageId, targetPageId, sourceBlockId, alias, displayMode, isPreviewable = true } = body; + + if (!workspaceId || !sourcePageId || !targetPageId) { + return NextResponse.json({ error: "缺少必要参数" }, { status: 400 }); + } + if (!isValidDisplayMode(displayMode)) { + return NextResponse.json({ error: "非法的引用模式" }, { status: 400 }); + } + + const { data: membership, error: membershipError } = await supabase + .from("workspace_members") + .select("workspace_id") + .eq("workspace_id", workspaceId) + .eq("user_id", session.user.id) + .maybeSingle(); + + if (membershipError) { + return NextResponse.json({ error: membershipError.message }, { status: 500 }); + } + if (!membership) { + return NextResponse.json({ error: "无权访问该工作空间" }, { status: 403 }); + } + + const { data, error } = await supabase.rpc("record_page_ref", { + p_workspace_id: workspaceId, + p_source_page_id: sourcePageId, + p_source_block_id: sourceBlockId ?? null, + p_target_page_id: targetPageId, + p_alias: alias ?? null, + p_display_mode: displayMode, + p_is_previewable: isPreviewable, + p_created_by: session.user.id, + }); + + if (error) { + return NextResponse.json({ error: error.message }, { status: 500 }); + } + + return NextResponse.json({ reference: data }); +} diff --git a/src/app/api/search/documents/route.ts b/src/app/api/search/documents/route.ts new file mode 100644 index 00000000..027cb181 --- /dev/null +++ b/src/app/api/search/documents/route.ts @@ -0,0 +1,303 @@ +import { NextResponse } from "next/server"; +import { createSupabaseRouteClient } from "@/lib/supabase/server"; +import type { + DocumentSearchFilters, + DocumentSearchRequest, + DocumentSearchResponse, + DocumentSearchResult, + DocumentSearchTimeRange, +} from "@/types/search"; +import { buildSnippet } from "@/lib/search/snippet"; + +const MAX_LIMIT = 50; + +const DEFAULT_FILTERS: DocumentSearchFilters = { + titleOnly: false, + exact: false, + onlyCurrentPage: false, + includeOcr: false, + timeRange: "any", + timeField: "updated", +}; + +const TIME_RANGE_TO_MS: Record = { + any: null, + "7d": 1000 * 60 * 60 * 24 * 7, + "30d": 1000 * 60 * 60 * 24 * 30, +}; + +const TIME_FIELD_COLUMN = { + updated: "updated_at", + created: "created_at", +} as const; + +const escapeLike = (value: string): string => value.replace(/[%_\\]/g, (match) => `\\${match}`); +interface DocumentRow { + id: string; + title: string | null; + raw_text: string | null; + updated_at: string | null; + created_at: string | null; +} + +const buildIsoBoundary = (value: string, isEnd = false): string | null => { + if (!value) { + return null; + } + const normalized = value.trim(); + if (!normalized) { + return null; + } + const suffix = isEnd ? "T23:59:59.999Z" : "T00:00:00.000Z"; + const date = new Date(`${normalized}${suffix}`); + if (Number.isNaN(date.getTime())) { + return null; + } + return date.toISOString(); +}; + +const mapDocumentToResult = ( + row: DocumentRow, + keyword: string | null, + forcedMatch?: DocumentSearchResult["matchField"], +): DocumentSearchResult => { + const normalizedKeyword = keyword?.trim() ?? ""; + const normalizedTitle = row.title ?? "无标题"; + let matchField: DocumentSearchResult["matchField"] = "recent"; + if (forcedMatch) { + matchField = forcedMatch; + } else if (normalizedKeyword) { + const matchesTitle = normalizedTitle.toLowerCase().includes(normalizedKeyword.toLowerCase()); + matchField = matchesTitle ? "title" : "content"; + } + + return { + id: row.id, + title: normalizedTitle, + snippet: buildSnippet(row.raw_text, normalizedKeyword || null), + updatedAt: row.updated_at, + createdAt: row.created_at, + matchField, + hasOcr: Boolean(row.raw_text), + publicPath: `/documents/${row.id}`, + score: matchField === "title" ? 2 : 1, + }; +}; + +type RouteSupabaseClient = Awaited>; + +const fetchRecentResults = async ( + supabase: RouteSupabaseClient, + userId: string, + workspaceId: string, +): Promise => { + const { data: recentRows, error: recentError } = await supabase + .from("user_recent_pages") + .select("document_id,last_accessed_at") + .eq("user_id", userId) + .eq("workspace_id", workspaceId) + .order("last_accessed_at", { ascending: false }) + .limit(10); + + if (recentError || !recentRows || recentRows.length === 0) { + return []; + } + + const documentIds = recentRows.map((row) => row.document_id); + + const { data: docRows, error: docsError } = await supabase + .from("documents") + .select("id,title,updated_at,created_at,raw_text") + .in("id", documentIds) + .is("deleted_at", null); + + if (docsError || !docRows) { + return []; + } + + const docMap = new Map(docRows.map((row) => [row.id, row])); + return recentRows + .map((row) => docMap.get(row.document_id)) + .filter((row): row is DocumentRow => Boolean(row)) + .map((row) => mapDocumentToResult(row, null, "recent")); +}; + +const fetchOcrMatches = async ( + supabase: RouteSupabaseClient, + workspaceId: string, + likePattern: string, + limit: number, +) => { + const { data, error } = await supabase + .from("media_assets") + .select("document_id,ocr_text") + .eq("workspace_id", workspaceId) + .not("ocr_text", "is", null) + .ilike("ocr_text", likePattern) + .limit(limit); + + if (error || !data) { + return []; + } + return data; +}; + +export async function POST(request: Request) { + const supabase = await createSupabaseRouteClient(); + const { + data: { session }, + } = await supabase.auth.getSession(); + + if (!session) { + return NextResponse.json({ error: "未登录" }, { status: 401 }); + } + + const payload = (await request.json()) as DocumentSearchRequest; + const workspaceId = payload.workspaceId; + + if (!workspaceId) { + return NextResponse.json({ error: "缺少 workspaceId" }, { status: 400 }); + } + + const filters: DocumentSearchFilters = { + ...DEFAULT_FILTERS, + ...payload.filters, + }; + + const { data: membership, error: membershipError } = await supabase + .from("workspace_members") + .select("workspace_id") + .eq("workspace_id", workspaceId) + .eq("user_id", session.user.id) + .limit(1); + + if (membershipError) { + return NextResponse.json({ error: membershipError.message }, { status: 500 }); + } + + if (!membership || membership.length === 0) { + return NextResponse.json({ error: "无权访问该工作空间" }, { status: 403 }); + } + + if (filters.onlyCurrentPage && !payload.documentId) { + filters.onlyCurrentPage = false; + } + + const limit = Math.min(payload.limit ?? 30, MAX_LIMIT); + const normalizedQuery = payload.query?.trim() ?? ""; + const likePattern = filters.exact ? normalizedQuery : `%${escapeLike(normalizedQuery)}%`; + const timeColumn = TIME_FIELD_COLUMN[filters.timeField ?? "updated"]; + + let builder = supabase + .from("documents") + .select("id,title,updated_at,created_at,raw_text,is_starred,access_scope") + .eq("workspace_id", workspaceId) + .is("deleted_at", null) + .order(timeColumn, { ascending: false }) + .limit(limit); + + if (filters.onlyCurrentPage && payload.documentId) { + builder = builder.eq("id", payload.documentId); + } + + const customFrom = filters.customRange?.from ? buildIsoBoundary(filters.customRange.from, false) : null; + const customTo = filters.customRange?.to ? buildIsoBoundary(filters.customRange.to, true) : null; + + if (customFrom) { + builder = builder.gte(timeColumn, customFrom); + } + if (customTo) { + builder = builder.lte(timeColumn, customTo); + } + + if (!customFrom && !customTo) { + const now = Date.now(); + const offset = TIME_RANGE_TO_MS[filters.timeRange]; + if (offset) { + const from = new Date(now - offset).toISOString(); + builder = builder.gte(timeColumn, from); + } + } + + if (normalizedQuery) { + if (filters.titleOnly) { + builder = filters.exact ? builder.eq("title", normalizedQuery) : builder.ilike("title", likePattern); + } else { + const clauses = [ + `title.${filters.exact ? `eq.${normalizedQuery}` : `ilike.${likePattern}`}`, + ]; + if (filters.includeOcr) { + clauses.push(`raw_text.ilike.${likePattern}`); + } + builder = builder.or(clauses.join(",")); + } + } + + const shouldSearchOcr = Boolean(filters.includeOcr && normalizedQuery); + const [{ data, error }, recentResults, ocrMatches] = await Promise.all([ + builder, + fetchRecentResults(supabase, session.user.id, workspaceId), + shouldSearchOcr ? fetchOcrMatches(supabase, workspaceId, likePattern, limit) : Promise.resolve([]), + ]); + + if (error) { + return NextResponse.json({ error: error.message }, { status: 500 }); + } + + let results: DocumentSearchResult[] = (data ?? []).map((row) => + mapDocumentToResult(row, normalizedQuery || null), + ); + + if (ocrMatches.length > 0 && normalizedQuery) { + const snippetMap = new Map(); + ocrMatches.forEach((row: { document_id: string; ocr_text: string | null }) => { + if (!row.ocr_text) return; + if (!snippetMap.has(row.document_id)) { + snippetMap.set(row.document_id, row.ocr_text); + } + }); + if (snippetMap.size > 0) { + const resultMap = new Map(results.map((item) => [item.id, item])); + const missingDocIds: string[] = []; + snippetMap.forEach((text, docId) => { + const snippet = buildSnippet(text, normalizedQuery || null); + if (resultMap.has(docId)) { + const existing = resultMap.get(docId)!; + resultMap.set(docId, { + ...existing, + snippet: snippet || existing.snippet, + hasOcr: true, + matchField: "content", + }); + } else { + missingDocIds.push(docId); + } + }); + let extraResults: DocumentSearchResult[] = []; + if (missingDocIds.length > 0) { + const { data: extraDocs } = await supabase + .from("documents") + .select("id,title,updated_at,created_at,raw_text") + .in("id", missingDocIds) + .is("deleted_at", null); + extraResults = + extraDocs?.map((row) => { + const snippetText = snippetMap.get(row.id) ?? row.raw_text ?? ""; + return { + ...mapDocumentToResult(row, normalizedQuery || null, "content"), + snippet: buildSnippet(snippetText, normalizedQuery || null), + hasOcr: true, + }; + }) ?? []; + } + results = [...resultMap.values(), ...extraResults]; + } + } + + const response: DocumentSearchResponse = { + results, + recent: recentResults, + }; + + return NextResponse.json(response); +} diff --git a/src/app/api/search/recent/route.ts b/src/app/api/search/recent/route.ts new file mode 100644 index 00000000..71d4a10e --- /dev/null +++ b/src/app/api/search/recent/route.ts @@ -0,0 +1,54 @@ +import { NextResponse } from "next/server"; +import { createSupabaseRouteClient } from "@/lib/supabase/server"; + +interface RecentPayload { + workspaceId: string; + documentId: string; +} + +export async function POST(request: Request) { + const supabase = await createSupabaseRouteClient(); + const { + data: { session }, + } = await supabase.auth.getSession(); + + if (!session) { + return NextResponse.json({ error: "未登录" }, { status: 401 }); + } + + const { workspaceId, documentId }: RecentPayload = await request.json(); + + if (!workspaceId || !documentId) { + return NextResponse.json({ error: "缺少参数" }, { status: 400 }); + } + + const { data: membership, error: membershipError } = await supabase + .from("workspace_members") + .select("workspace_id") + .eq("workspace_id", workspaceId) + .eq("user_id", session.user.id) + .maybeSingle(); + + if (membershipError) { + return NextResponse.json({ error: membershipError.message }, { status: 500 }); + } + if (!membership) { + return NextResponse.json({ error: "无权访问该工作空间" }, { status: 403 }); + } + + const { error } = await supabase.from("user_recent_pages").upsert( + { + user_id: session.user.id, + workspace_id: workspaceId, + document_id: documentId, + last_accessed_at: new Date().toISOString(), + }, + { onConflict: "user_id,document_id" }, + ); + + if (error) { + return NextResponse.json({ error: error.message }, { status: 500 }); + } + + return NextResponse.json({ ok: true }); +} diff --git a/src/app/api/sidebar/route.ts b/src/app/api/sidebar/route.ts new file mode 100644 index 00000000..29911e98 --- /dev/null +++ b/src/app/api/sidebar/route.ts @@ -0,0 +1,47 @@ +import { NextResponse } from "next/server"; +import { createSupabaseRouteClient } from "@/lib/supabase/server"; +import { ensureDefaultWorkspace, fetchWorkspaceSummaries } from "@/lib/workspaces"; +import { fetchSidebarDataset } from "@/lib/sidebar-tree"; +import type { SidebarInitialData } from "@/components/sidebar/types"; + +export const dynamic = "force-dynamic"; + +export async function GET(request: Request) { + const supabase = await createSupabaseRouteClient(); + const { + data: { session }, + } = await supabase.auth.getSession(); + + if (!session) { + return NextResponse.json({ error: "未登录" }, { status: 401 }); + } + + const url = new URL(request.url); + const workspaceIdParam = url.searchParams.get("workspaceId"); + + await ensureDefaultWorkspace(supabase, session.user.id, session.user.email ?? "我的空间"); + const { workspaces, activeWorkspaceId } = await fetchWorkspaceSummaries(supabase, session.user.id); + const targetWorkspaceId = workspaceIdParam || activeWorkspaceId; + + if (!targetWorkspaceId) { + return NextResponse.json({ error: "暂无可用工作空间" }, { status: 404 }); + } + + try { + const dataset = await fetchSidebarDataset(supabase, targetWorkspaceId); + + const payload: SidebarInitialData = { + activeWorkspaceId: targetWorkspaceId, + workspaces, + documents: dataset.documents, + trashedDocuments: dataset.trashedDocuments, + }; + + return NextResponse.json(payload); + } catch (error) { + return NextResponse.json( + { error: error instanceof Error ? error.message : "拉取侧边栏数据失败" }, + { status: 500 }, + ); + } +} diff --git a/src/app/api/workspaces/switch/route.ts b/src/app/api/workspaces/switch/route.ts new file mode 100644 index 00000000..d921118b --- /dev/null +++ b/src/app/api/workspaces/switch/route.ts @@ -0,0 +1,54 @@ +import { NextResponse } from "next/server"; +import { createSupabaseRouteClient } from "@/lib/supabase/server"; + +export const dynamic = "force-dynamic"; + +export async function POST(request: Request) { + const supabase = await createSupabaseRouteClient(); + const { + data: { session }, + } = await supabase.auth.getSession(); + + if (!session) { + return NextResponse.json({ error: "未登录" }, { status: 401 }); + } + + const payload = await request.json().catch(() => ({})); + const workspaceId = payload.workspaceId as string | undefined; + + if (!workspaceId) { + return NextResponse.json({ error: "缺少 workspaceId" }, { status: 400 }); + } + + const { data: membership, error: membershipError } = await supabase + .from("workspace_members") + .select("id") + .eq("workspace_id", workspaceId) + .eq("user_id", session.user.id) + .single(); + + if (membershipError || !membership) { + return NextResponse.json({ error: "无权切换至该工作空间" }, { status: 403 }); + } + + const { error: resetError } = await supabase + .from("workspace_members") + .update({ is_default: false }) + .eq("user_id", session.user.id); + + if (resetError) { + return NextResponse.json({ error: resetError.message }, { status: 500 }); + } + + const { error: switchError } = await supabase + .from("workspace_members") + .update({ is_default: true }) + .eq("user_id", session.user.id) + .eq("workspace_id", workspaceId); + + if (switchError) { + return NextResponse.json({ error: switchError.message }, { status: 500 }); + } + + return NextResponse.json({ success: true }); +} diff --git a/src/app/globals.css b/src/app/globals.css index a2dc41ec..21318910 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1,26 +1,410 @@ +@import url("https://rsms.me/inter/inter.css"); @import "tailwindcss"; +@import "tw-animate-css"; -:root { - --background: #ffffff; - --foreground: #171717; -} +@custom-variant dark (&:is(.dark *)); @theme inline { --color-background: var(--background); --color-foreground: var(--foreground); --font-sans: var(--font-geist-sans); --font-mono: var(--font-geist-mono); + --color-sidebar-ring: var(--sidebar-ring); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar: var(--sidebar); + --color-chart-5: var(--chart-5); + --color-chart-4: var(--chart-4); + --color-chart-3: var(--chart-3); + --color-chart-2: var(--chart-2); + --color-chart-1: var(--chart-1); + --color-ring: var(--ring); + --color-input: var(--input); + --color-border: var(--border); + --color-destructive: var(--destructive); + --color-accent-foreground: var(--accent-foreground); + --color-accent: var(--accent); + --color-muted-foreground: var(--muted-foreground); + --color-muted: var(--muted); + --color-secondary-foreground: var(--secondary-foreground); + --color-secondary: var(--secondary); + --color-primary-foreground: var(--primary-foreground); + --color-primary: var(--primary); + --color-popover-foreground: var(--popover-foreground); + --color-popover: var(--popover); + --color-card-foreground: var(--card-foreground); + --color-card: var(--card); + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); } -@media (prefers-color-scheme: dark) { - :root { - --background: #0a0a0a; - --foreground: #ededed; +:root { + --radius: 0.625rem; + --background: oklch(1 0 0); + --foreground: oklch(0.145 0 0); + --card: oklch(1 0 0); + --card-foreground: oklch(0.145 0 0); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.145 0 0); + --primary: oklch(0.205 0 0); + --primary-foreground: oklch(0.985 0 0); + --secondary: oklch(0.97 0 0); + --secondary-foreground: oklch(0.205 0 0); + --muted: oklch(0.97 0 0); + --muted-foreground: oklch(0.556 0 0); + --accent: oklch(0.97 0 0); + --accent-foreground: oklch(0.205 0 0); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.922 0 0); + --input: oklch(0.922 0 0); + --ring: oklch(0.708 0 0); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.205 0 0); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.97 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.922 0 0); + --sidebar-ring: oklch(0.708 0 0); +} + +.dark { + --background: oklch(0.145 0 0); + --foreground: oklch(0.985 0 0); + --card: oklch(0.205 0 0); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.205 0 0); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.922 0 0); + --primary-foreground: oklch(0.205 0 0); + --secondary: oklch(0.269 0 0); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.269 0 0); + --muted-foreground: oklch(0.708 0 0); + --accent: oklch(0.269 0 0); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.704 0.191 22.216); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.556 0 0); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.205 0 0); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.269 0 0); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.556 0 0); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + html { + font-family: "Inter", system-ui, sans-serif; + } + body { + @apply bg-background text-foreground; } } -body { - background: var(--background); - color: var(--foreground); - font-family: Arial, Helvetica, sans-serif; +@layer components { + .wolai-hover { + @apply transition-colors duration-150 hover:bg-gray-50; + } + .wolai-selected { + @apply border-l-2 border-blue-500 bg-blue-50; + } + .wolai-blue { + @apply cursor-pointer text-[#2563eb] hover:underline; + } + .wolai-editor .bn-block-outer { + padding: 2px 0; + } + .wolai-editor .bn-inline-content a { + color: #2563eb; + text-decoration: underline; + } + .wolai-editor .bn-drag-handle { + opacity: 0; + transition: opacity 0.2s ease; + } + .wolai-editor .bn-block-group:hover .bn-drag-handle { + opacity: 1; + } + .wolai-editor[data-heading-numbering="true"] { + counter-reset: wolai-h1 wolai-h2 wolai-h3 wolai-h4 wolai-h5; + } + .wolai-editor[data-heading-numbering="true"] h1 { + counter-increment: wolai-h1; + counter-reset: wolai-h2; + } + .wolai-editor[data-heading-numbering="true"] h1::before { + content: counter(wolai-h1) ". "; + color: #94a3b8; + margin-right: 8px; + } + .wolai-editor[data-heading-numbering="true"] h2 { + counter-increment: wolai-h2; + counter-reset: wolai-h3; + } + .wolai-editor[data-heading-numbering="true"] h2::before { + content: counter(wolai-h1) "." counter(wolai-h2) ". "; + color: #94a3b8; + margin-right: 6px; + } + .wolai-editor[data-heading-numbering="true"] h3 { + counter-increment: wolai-h3; + counter-reset: wolai-h4; + } + .wolai-editor[data-heading-numbering="true"] h3::before { + content: counter(wolai-h1) "." counter(wolai-h2) "." counter(wolai-h3) ". "; + color: #cbd5f5; + margin-right: 4px; + } + .wolai-editor[data-heading-numbering="true"] h4 { + counter-increment: wolai-h4; + counter-reset: wolai-h5; + } + .wolai-editor[data-heading-numbering="true"] h4::before { + content: counter(wolai-h1) "." counter(wolai-h2) "." counter(wolai-h3) "." counter(wolai-h4) ". "; + color: #d0d7e7; + margin-right: 4px; + } + .wolai-editor[data-heading-numbering="true"] h5 { + counter-increment: wolai-h5; + } + .wolai-editor[data-heading-numbering="true"] h5::before { + content: counter(wolai-h1) "." counter(wolai-h2) "." counter(wolai-h3) "." counter(wolai-h4) "." counter(wolai-h5) ". "; + color: #d4d4d8; + margin-right: 4px; + } + .wolai-editor-show-structure .bn-block-outer { + outline: 1px dashed #d4d4d8; + border-radius: 8px; + padding: 4px 8px; + } + .wolai-advanced-todo { + display: flex; + align-items: center; + gap: 10px; + } + .wolai-advanced-todo__status { + border-radius: 999px; + border: 1px solid #c7d2fe; + padding: 2px 10px; + font-size: 12px; + cursor: pointer; + background-color: #eef2ff; + color: #312e81; + } + .wolai-advanced-todo__status.status-doing { + border-color: #fcd34d; + background-color: #fff7ed; + color: #92400e; + } + .wolai-advanced-todo__status.status-done { + border-color: #4ade80; + background-color: #ecfdf5; + color: #047857; + } + .wolai-advanced-todo__status.status-cancelled { + border-color: #fecdd3; + background-color: #fff1f2; + color: #9f1239; + } + .wolai-progress { + border-radius: 12px; + padding: 12px; + border: 1px solid #e5e7eb; + background-color: #f8fafc; + margin: 8px 0; + } + .wolai-progress__header { + display: flex; + align-items: center; + justify-content: space-between; + font-size: 12px; + color: #475569; + margin-bottom: 8px; + } + .wolai-progress__mode { + font-size: 11px; + border-radius: 12px; + background-color: #e0edff; + color: #1d4ed8; + padding: 2px 8px; + } + .wolai-progress__bar { + position: relative; + width: 100%; + height: 10px; + border-radius: 999px; + background-color: #e4e4e7; + overflow: hidden; + cursor: pointer; + } + .wolai-progress__fill { + position: absolute; + top: 0; + left: 0; + bottom: 0; + border-radius: inherit; + background-image: linear-gradient(90deg, #60a5fa, #2563eb); + } + .wolai-progress__percent { + display: block; + margin-top: 6px; + font-size: 12px; + color: #1d4ed8; + font-weight: 600; + } + .wolai-progress__description { + font-size: 13px; + color: #6b7280; + margin-top: 6px; + } + .wolai-media { + position: relative; + margin: 24px 0; + border-radius: 18px; + } + .wolai-media::after { + content: ""; + position: absolute; + inset: -8px; + border: 1px solid transparent; + border-radius: 24px; + transition: border-color 0.2s ease; + pointer-events: none; + } + .wolai-media:hover::after, + .wolai-media:focus-within::after { + border-color: rgba(37, 99, 235, 0.15); + } + .wolai-media--empty { + border: 1px dashed #bed5ff; + background-color: #fff; + padding: 20px; + border-radius: 16px; + } + .wolai-media__canvas { + position: relative; + border-radius: 18px; + overflow: hidden; + background-color: #fff; + box-shadow: 0 10px 40px rgba(15, 23, 42, 0.05); + margin: 0 auto; + } + .wolai-media__figure { + border-radius: inherit; + overflow: hidden; + background-color: #fff; + } + .wolai-media__figure img { + width: 100%; + border-radius: inherit; + object-fit: cover; + } + .wolai-media__figure--border img { + border: 1px solid #e2e8f0; + } + .wolai-media__figure figcaption { + padding: 8px 12px; + } + .wolai-media__figure--center figcaption { + text-align: center; + } + .wolai-media__figure--right figcaption { + text-align: right; + } + .wolai-media__quickbar { + position: absolute; + top: 12px; + right: 12px; + display: flex; + gap: 6px; + padding: 4px; + border-radius: 9999px; + background-color: rgba(15, 23, 42, 0.85); + box-shadow: 0 12px 32px rgba(15, 23, 42, 0.35); + opacity: 0; + transform: translateY(-4px); + transition: opacity 0.15s ease, transform 0.15s ease; + pointer-events: none; + } + .wolai-media:hover .wolai-media__quickbar, + .wolai-media:focus-within .wolai-media__quickbar { + opacity: 1; + transform: translateY(0); + pointer-events: auto; + } + .wolai-media__quickbutton { + display: inline-flex; + align-items: center; + justify-content: center; + width: 34px; + height: 34px; + border-radius: 9999px; + background-color: transparent; + color: #e2e8f0; + border: none; + cursor: pointer; + transition: background-color 0.15s ease, color 0.15s ease; + } + .wolai-media__quickbutton:hover, + .wolai-media__quickbutton:focus-visible { + background-color: rgba(255, 255, 255, 0.15); + color: #fff; + outline: none; + } + .wolai-media__hint { + margin-top: 6px; + font-size: 12px; + color: #94a3b8; + text-align: left; + } + .wolai-media__resize-handle { + position: absolute; + top: 50%; + width: 12px; + height: 44px; + border-radius: 2px; + background: rgba(15, 23, 42, 0.5); + transform: translateY(-50%); + opacity: 0; + transition: opacity 0.15s ease; + cursor: ew-resize; + z-index: 20; + } + .wolai-media__resize-handle--left { + left: -6px; + } + .wolai-media__resize-handle--right { + right: -6px; + } + .wolai-media:hover .wolai-media__resize-handle, + .wolai-media:focus-within .wolai-media__resize-handle { + opacity: 1; + } + .wolai-media__resize-handle.is-dragging { + background: rgba(37, 99, 235, 0.85); + } } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index f7fa87eb..f422648c 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,33 +1,36 @@ import type { Metadata } from "next"; -import { Geist, Geist_Mono } from "next/font/google"; +import { Inter } from "next/font/google"; import "./globals.css"; +import { SupabaseProvider } from "@/components/providers/supabase-provider"; +import { QueryProvider } from "@/components/providers/query-provider"; +import { createSupabaseServerClient } from "@/lib/supabase/server"; -const geistSans = Geist({ - variable: "--font-geist-sans", - subsets: ["latin"], -}); - -const geistMono = Geist_Mono({ - variable: "--font-geist-mono", +const inter = Inter({ subsets: ["latin"], + variable: "--font-inter", }); export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", + title: "Wolai Clone", + description: "Stage0 + Stage1 implementation powered by Supabase", }; -export default function RootLayout({ +export default async function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { + const supabase = await createSupabaseServerClient(); + const { + data: { session }, + } = await supabase.auth.getSession(); + return ( - - - {children} + + + + {children} + ); diff --git a/src/app/page.tsx b/src/app/page.tsx index 295f8fdf..f591e488 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,65 +1,41 @@ -import Image from "next/image"; +import { redirect } from "next/navigation"; +import { createSupabaseServerClient } from "@/lib/supabase/server"; -export default function Home() { - return ( -
-
- Next.js logo -
-

- To get started, edit the page.tsx file. -

-

- Looking for a starting point or more instructions? Head over to{" "} - - Templates - {" "} - or the{" "} - - Learning - {" "} - center. -

-
- -
-
- ); +export default async function Home() { + const supabase = await createSupabaseServerClient(); + const { + data: { session }, + } = await supabase.auth.getSession(); + + if (!session) { + redirect("/login"); + } + + const { data: firstDoc } = await supabase + .from("documents") + .select("id") + .eq("user_id", session.user.id) + .order("created_at", { ascending: true }) + .limit(1) + .maybeSingle(); + + if (firstDoc?.id) { + redirect(`/documents/${firstDoc.id}`); + } + + const { data: createdDoc, error } = await supabase + .from("documents") + .insert({ + user_id: session.user.id, + title: "新页面", + content: {}, + }) + .select("id") + .single(); + + if (error || !createdDoc) { + redirect("/login"); + } + + redirect(`/documents/${createdDoc.id}`); } diff --git a/src/components/bottom-toolbar.tsx b/src/components/bottom-toolbar.tsx new file mode 100644 index 00000000..b458b6fc --- /dev/null +++ b/src/components/bottom-toolbar.tsx @@ -0,0 +1,36 @@ +"use client"; + +import { MessageCircle, Sparkles, Wand2 } from "lucide-react"; +import { useBackendHealth } from "@/hooks/use-backend-health"; +import { cn } from "@/lib/utils"; + +export function BottomToolbar() { + const status = useBackendHealth(); + const indicatorColor = + status === "ok" ? "bg-green-500" : status === "error" ? "bg-red-500" : "bg-gray-300"; + + return ( +
+
+ + 后端连接:{status === "ok" ? "正常" : status === "error" ? "异常" : "检测中"} +
+
+ + + +
+
+ ); +} diff --git a/src/components/breadcrumb.tsx b/src/components/breadcrumb.tsx new file mode 100644 index 00000000..1090faf8 --- /dev/null +++ b/src/components/breadcrumb.tsx @@ -0,0 +1,63 @@ +"use client"; + +import Link from "next/link"; +import { useParams, useSelectedLayoutSegments } from "next/navigation"; +import { ChevronRight, MoreHorizontal, Star } from "lucide-react"; +import type { DocumentRecord } from "@/lib/documents"; +import { findBreadcrumb } from "@/lib/documents"; +import { usePageLayoutStore } from "@/store/page-layout"; + +interface BreadcrumbProps { + documents: DocumentRecord[]; +} + +export function Breadcrumb({ documents }: BreadcrumbProps) { + const segments = useSelectedLayoutSegments(); + const params = useParams<{ id?: string }>(); + const paramId = typeof params?.id === "string" ? params.id : ""; + const activeId = paramId || segments?.[1] || ""; + const path = findBreadcrumb(documents, activeId); + const showInspector = usePageLayoutStore((state) => state.showInspector); + const toggleInspector = usePageLayoutStore((state) => state.toggleInspector); + + if (!path.length) { + return
请选择一个页面
; + } + + return ( +
+ +
+ + +
+
+ ); +} diff --git a/src/components/common/.gitkeep b/src/components/common/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/src/components/document-task-panel.tsx b/src/components/document-task-panel.tsx new file mode 100644 index 00000000..6dc437b3 --- /dev/null +++ b/src/components/document-task-panel.tsx @@ -0,0 +1,87 @@ +"use client"; + +import { useEffect, useMemo, useState } from "react"; +import { useSessionContext } from "@supabase/auth-helpers-react"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent } from "@/components/ui/card"; + +interface TaskResponse { + task_id: string; + status: string; + progress: number; + message?: string | null; +} + +interface Props { + documentId: string; +} + +export function DocumentTaskPanel({ documentId }: Props) { + const { session } = useSessionContext(); + const [task, setTask] = useState(null); + const [pending, setPending] = useState(false); + const backendUrl = useMemo(() => process.env.NEXT_PUBLIC_BACKEND_URL, []); + + const triggerTask = async () => { + if (!backendUrl || !session?.access_token) return; + setPending(true); + try { + const response = await fetch(`${backendUrl}/api/v1/tasks/ocr`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${session.access_token}`, + }, + body: JSON.stringify({ + document_id: documentId, + file_url: "https://example.com/sample.pdf", + }), + }); + const data = await response.json(); + if (response.ok) { + setTask(data); + } + } finally { + setPending(false); + } + }; + + useEffect(() => { + if (!backendUrl || !session?.access_token || !task?.task_id) { + return; + } + const timer = setInterval(async () => { + const response = await fetch(`${backendUrl}/api/v1/tasks/${task.task_id}`, { + headers: { + Authorization: `Bearer ${session.access_token}`, + }, + }); + if (!response.ok) { + return; + } + const data = (await response.json()) as TaskResponse; + setTask(data); + if (data.status === "completed") { + clearInterval(timer); + } + }, 2000); + return () => clearInterval(timer); + }, [backendUrl, session?.access_token, task?.task_id]); + + return ( + + +
+
后端 OCR 流程
+
+ 状态:{task ? task.status : "未开始"} · 进度:{task ? `${task.progress}%` : "0%"} +
+ {task?.message &&
提示:{task.message}
} +
+ +
+
+ ); +} diff --git a/src/components/editor/.gitkeep b/src/components/editor/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/src/components/editor/blocknote-editor.tsx b/src/components/editor/blocknote-editor.tsx new file mode 100644 index 00000000..cc7b8f07 --- /dev/null +++ b/src/components/editor/blocknote-editor.tsx @@ -0,0 +1,585 @@ +"use client"; + +import "@blocknote/core/style.css"; +import "@blocknote/react/style.css"; +import "@blocknote/mantine/style.css"; + +import { useCallback, useEffect, useMemo, useState } from "react"; +import { BlockNoteView } from "@blocknote/mantine"; +import { + SideMenuController, + useCreateBlockNote, + type SideMenuProps, +} from "@blocknote/react"; +import { HocuspocusProvider } from "@hocuspocus/provider"; +import * as Y from "yjs"; +import type { Block } from "@blocknote/core"; +import { cn } from "@/lib/utils"; +import type { Json } from "@/types/supabase"; +import type { DocumentStats, PageOptionsState } from "@/types/page-options"; +import type { MediaAsset } from "@/types/media"; +import { customBlockSchema, type CustomBlockSchema } from "./schema"; +import { CustomSideMenu } from "./menus/CustomSideMenu"; +import { CustomSlashMenu } from "./menus/CustomSlashMenu"; +import { useDebouncedCallback } from "@/hooks/use-debounced-callback"; +import { DocumentToc, type TocEntry } from "@/components/editor/document-toc"; +import { useSearchPaletteStore } from "@/store/search-palette"; +import { useEditorBridgeStore } from "@/store/editor-bridge"; +import type { ReferenceTarget } from "@/types/search"; + +interface BlockNoteEditorProps { + documentId: string; + workspaceId: string; + initialContent: unknown; + pageOptions: PageOptionsState; + onStatsChange?: (stats: DocumentStats) => void; + onSnapshot?: (payload: { blocks: Json; stats: DocumentStats }) => void; +} + +const extractInitialBlocks = (content: unknown): Json | undefined => { + if (Array.isArray(content) && content.length > 0) { + return content as Json; + } + if (content && typeof content === "object") { + const maybeBlocks = (content as Record).blocks; + if (Array.isArray(maybeBlocks) && maybeBlocks.length > 0) { + return maybeBlocks as Json; + } + } + return undefined; +}; + +const extractInlineText = (block: Block): string => { + const inlineNodes = (block.content ?? []) as Array<{ text?: string }>; + return inlineNodes.map((node) => (typeof node.text === "string" ? node.text : "")).join("").trim(); +}; + +const buildHeadingToc = (blocks: Block[]): TocEntry[] => { + const counters = [0, 0, 0, 0, 0]; + const entries: TocEntry[] = []; + + const walk = (targetBlocks: Block[]) => { + targetBlocks.forEach((block) => { + if (block.type === "heading") { + const level = Math.min(5, Math.max(1, Number(block.props.level) || 1)); + counters[level - 1] += 1; + for (let i = level; i < counters.length; i += 1) { + counters[i] = 0; + } + const numbering = counters.slice(0, level).filter((value) => value > 0).join("."); + entries.push({ + id: block.id, + level, + numbering, + title: extractInlineText(block), + }); + } + if (block.children && block.children.length > 0) { + walk(block.children as Block[]); + } + }); + }; + + walk(blocks); + return entries; +}; + +const findBlockById = ( + blocks: Block[], + id: string, +): Block | undefined => { + for (const block of blocks) { + if (block.id === id) return block; + if (block.children && block.children.length > 0) { + const child = findBlockById(block.children as Block[], id); + if (child) return child; + } + } + return undefined; +}; + +const syncProgressMeters = (editorInstance: ReturnType) => { + if (!editorInstance) { + return; + } + const blocks = editorInstance.topLevelBlocks as Block[]; + const progressStats = new Map< + string, + { done: number; doing: number; total: number } + >(); + let activeProgressId: string | null = null; + + const traverse = (targetBlocks: Block[]) => { + targetBlocks.forEach((block) => { + if (block.type === "progressMeter" && block.props.auto) { + activeProgressId = block.id; + progressStats.set(block.id, { done: 0, doing: 0, total: 0 }); + } else if (block.type === "progressMeter" && !block.props.auto) { + activeProgressId = null; + } else if (block.type === "heading") { + activeProgressId = null; + } else if (block.type === "advancedTodo" && activeProgressId) { + const currentStat = progressStats.get(activeProgressId); + if (!currentStat) return; + if (block.props.status === "cancelled") { + return; + } + currentStat.total += 1; + if (block.props.status === "done") { + currentStat.done += 1; + } else if (block.props.status === "doing") { + currentStat.doing += 1; + } + } + + if (block.children && block.children.length > 0) { + traverse(block.children as Block[]); + } + }); + }; + + traverse(blocks); + + progressStats.forEach((stat, progressId) => { + const block = findBlockById(blocks, progressId); + if (!block) return; + const weightedDone = stat.done + stat.doing * 0.5; + const percent = stat.total === 0 ? 0 : Math.min(100, Math.round((weightedDone / stat.total) * 100)); + const summary = stat.total === 0 ? "暂无条目" : `${stat.done}/${stat.total} 完成`; + + if (block.props.percent !== percent || block.props.summary !== summary) { + editorInstance.updateBlock(block, { + props: { + percent, + summary, + }, + }); + } + }); +}; + +export function BlockNoteEditor({ + documentId, + workspaceId, + initialContent, + pageOptions, + onStatsChange, + onSnapshot, +}: BlockNoteEditorProps) { + const [isSaving, setIsSaving] = useState(false); + const [tocEntries, setTocEntries] = useState([]); + const openReferencePalette = useSearchPaletteStore((state) => state.openReference); + const registerEditorBridge = useEditorBridgeStore((state) => state.registerBridge); + + const normalizedInitialContent = useMemo( + () => extractInitialBlocks(initialContent), + [initialContent], + ); + + const collaboration = useMemo(() => { + const url = process.env.NEXT_PUBLIC_HOCUSPOCUS_URL; + if (!url) return null; + const doc = new Y.Doc(); + const provider = new HocuspocusProvider({ + url, + name: `document.${documentId}`, + document: doc, + }); + return { doc, provider }; + }, [documentId]); + + const editor = useCreateBlockNote( + { + initialContent: normalizedInitialContent as never, + schema: customBlockSchema, + collaboration: collaboration + ? { + provider: collaboration.provider, + fragment: collaboration.doc.getXmlFragment("wolai"), + user: { + name: "访客", + color: "#2563eb", + }, + } + : undefined, + }, + [documentId], + ); + + useEffect( + () => () => { + collaboration?.provider.destroy(); + collaboration?.doc.destroy(); + }, + [collaboration], + ); + + const saveContent = useCallback( + async (content: Json) => { + setIsSaving(true); + try { + await fetch("/api/documents/save", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ documentId, content }), + }); + } finally { + setIsSaving(false); + } + }, + [documentId], + ); + + const debouncedSave = useDebouncedCallback(saveContent, 800); + + useEffect(() => { + if (!editor) { + return undefined; + } + + let disposed = false; + const runSync = () => { + if (disposed) { + return; + } + const blocks = editor.topLevelBlocks; + debouncedSave(blocks as Json); + const typedBlocks = blocks as Block[]; + setTocEntries(buildHeadingToc(typedBlocks)); + syncProgressMeters(editor); + const stats = computeDocumentStats(typedBlocks); + onStatsChange?.(stats); + onSnapshot?.({ blocks: blocks as Json, stats }); + }; + + runSync(); + const unsubscribe = editor.onEditorContentChange(runSync); + return () => { + disposed = true; + if (typeof unsubscribe === "function") { + unsubscribe(); + } + }; + }, [editor, debouncedSave, onSnapshot, onStatsChange]); + + const jumpToHeading = useCallback((headingId: string) => { + const target = document.querySelector(`[data-id="${headingId}"]`); + if (target) { + target.scrollIntoView({ behavior: "smooth", block: "center" }); + } + }, []); + + const editorWrapperClass = cn( + "relative min-h-[60vh] rounded-2xl border border-transparent bg-white p-4 shadow-sm", + pageOptions.smallText ? "text-[15px]" : "text-[16px]", + ); + +const blocknoteClass = cn( + "wolai-editor min-h-full", + pageOptions.showStructure && "wolai-editor-show-structure", +); + +const buildDocumentPath = (documentId: string): string => { + if (typeof window === "undefined" || !window.location) { + return `/documents/${documentId}`; + } + return `${window.location.origin}/documents/${documentId}`; +}; + +const trimTrailingCharacter = ( + editorInstance: ReturnType | null, + block: Block, + char: string, +) => { + if (!editorInstance) { + return; + } + const content = Array.isArray(block.content) ? [...block.content] : []; + for (let index = content.length - 1; index >= 0; index -= 1) { + const node = content[index] as { text?: string }; + if (typeof node?.text === "string" && node.text.endsWith(char)) { + const nextText = node.text.slice(0, -1); + if (nextText.length === 0) { + content.splice(index, 1); + } else { + content[index] = { ...node, text: nextText }; + } + editorInstance.updateBlock(block, { content }); + break; + } + } +}; + +const generateBlockId = () => { + if (typeof crypto !== "undefined" && "randomUUID" in crypto) { + return crypto.randomUUID(); + } + return `ref_${Math.random().toString(36).slice(2, 10)}`; +}; + +const computeDocumentStats = (blocks: Block[]): DocumentStats => { + let characterCount = 0; + let wordCount = 0; + const accumulate = (targetBlocks: Block[]) => { + targetBlocks.forEach((block) => { + if (Array.isArray(block.content)) { + block.content.forEach((node: { text?: string }) => { + if (typeof node.text === "string") { + const text = node.text; + characterCount += text.length; + const trimmed = text.trim(); + if (trimmed.length === 0) { + return; + } + const tokens = trimmed.split(/\s+/).filter(Boolean); + if (tokens.length > 1) { + wordCount += tokens.length; + } else { + wordCount += trimmed.replace(/\s+/g, "").length; + } + } + }); + } + if (block.children && block.children.length > 0) { + accumulate(block.children as Block[]); + } + }); + }; + accumulate(blocks); + return { + wordCount, + characterCount, + blockCount: blocks.length, + }; +}; + + const insertMediaAssetBlock = useCallback( + (asset: MediaAsset) => { + if (!editor) { + return; + } + const fileUrl = asset.file_url ?? ""; + if (!fileUrl) { + return; + } + const cursor = editor.getTextCursorPosition(); + const referenceBlock = + cursor?.block ?? (editor.topLevelBlocks[editor.topLevelBlocks.length - 1] as Block | undefined); + if (!referenceBlock) { + return; + } + editor.insertBlocks( + [ + { + type: "media", + props: { + fileUrl, + thumbnailUrl: asset.thumbnail_url ?? fileUrl, + assetId: asset.id, + assetType: asset.asset_type ?? "image", + fileName: asset.file_name ?? "", + fileSize: asset.file_size ?? null, + mimeType: asset.mime_type ?? "", + ocrStatus: asset.ocr_status ?? "idle", + }, + }, + ], + referenceBlock, + "after", + ); + }, + [editor], + ); + + const uploadClipboardMedia = useCallback( + async (file: File) => { + if (!workspaceId) { + throw new Error("缺少空间信息,无法上传文件"); + } + const form = new FormData(); + form.append("file", file); + form.append("workspaceId", workspaceId); + form.append("documentId", documentId); + const response = await fetch("/api/media/upload", { + method: "POST", + body: form, + }); + if (!response.ok) { + const payload = await response.json().catch(() => null); + throw new Error(payload?.error ?? "上传失败"); + } + const payload = (await response.json()) as { asset: MediaAsset }; + if (!payload.asset) { + throw new Error("上传返回数据缺失"); + } + return payload.asset; + }, + [documentId, workspaceId], + ); + + useEffect(() => { + if (!editor) { + registerEditorBridge(null); + return; + } + const bridge = { + insertInlineReference: (target: ReferenceTarget, aliasText?: string) => { + editor.focus(); + const cursor = editor.getTextCursorPosition(); + const blockId = cursor?.block?.id ?? null; + const text = aliasText || target.title || "无标题"; + editor.insertInlineContent([ + { + type: "link", + href: buildDocumentPath(target.id), + content: text, + }, + { type: "text", text: " " }, + ]); + return { blockId }; + }, + insertEmbedReference: (target: ReferenceTarget) => { + editor.focus(); + const cursor = editor.getTextCursorPosition(); + const referenceBlock = + cursor?.block ?? editor.topLevelBlocks[editor.topLevelBlocks.length - 1]; + const blockId = generateBlockId(); + editor.insertBlocks( + [ + { + id: blockId, + type: "pageReference", + props: { + pageId: target.id, + title: target.title ?? "无标题", + }, + }, + ], + referenceBlock, + "after", + ); + return { blockId }; + }, + replaceWithSnapshot: (payload: Json) => { + editor.focus(); + const nextBlocks = Array.isArray(payload) + ? payload + : Array.isArray((payload as { blocks?: Json }).blocks) + ? ((payload as { blocks?: Json }).blocks as Json) + : []; + if (!Array.isArray(nextBlocks)) { + return; + } + editor.replaceBlocks(editor.topLevelBlocks, nextBlocks as never); + }, + }; + registerEditorBridge(bridge); + return () => registerEditorBridge(null); + }, [editor, registerEditorBridge]); + + useEffect(() => { + if (!editor) { + return undefined; + } + const handlePaste = (event: ClipboardEvent) => { + const activeElement = event.target; + if (!(activeElement instanceof HTMLElement) || !activeElement.closest(".wolai-editor")) { + return; + } + const items = Array.from(event.clipboardData?.files ?? []); + if (items.length === 0) { + return; + } + const imageFile = items.find((candidate) => candidate.type?.startsWith("image/")); + if (!imageFile) { + return; + } + event.preventDefault(); + void (async () => { + try { + const asset = await uploadClipboardMedia(imageFile); + insertMediaAssetBlock(asset); + } catch (error) { + console.error(error); + window.alert((error as Error).message ?? "粘贴图片失败,请稍后重试"); + } + })(); + }; + window.addEventListener("paste", handlePaste); + return () => window.removeEventListener("paste", handlePaste); + }, [editor, insertMediaAssetBlock, uploadClipboardMedia]); + + useEffect(() => { + if (!editor) { + return; + } + const buffer = { char: "", blockId: "", timestamp: 0 }; + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key !== "[" && event.key !== "#") { + buffer.char = ""; + buffer.blockId = ""; + buffer.timestamp = 0; + return; + } + const activeElement = document.activeElement; + if (!(activeElement instanceof HTMLElement) || !activeElement.closest(".wolai-editor")) { + return; + } + const { block } = editor.getTextCursorPosition(); + if (!block) { + return; + } + const now = Date.now(); + if ( + buffer.char === event.key && + buffer.blockId === block.id && + now - buffer.timestamp < 450 + ) { + event.preventDefault(); + trimTrailingCharacter(editor, block, event.key); + openReferencePalette({ + referenceMode: event.key === "[" ? "inline" : "embed", + }); + buffer.char = ""; + buffer.blockId = ""; + buffer.timestamp = 0; + } else { + buffer.char = event.key; + buffer.blockId = block.id; + buffer.timestamp = now; + } + }; + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, [editor, openReferencePalette]); + + const layoutClass = cn( + "relative mx-auto w-full", + pageOptions.wideLayout ? "max-w-none" : "max-w-[980px]", + ); + + return ( +
+
+ + ) => ( + + )} + /> + + +
+ {isSaving ? "保存中..." : "已保存"} +
+
+ +
+ ); +} diff --git a/src/components/editor/blocks/AdvancedTodoBlock.tsx b/src/components/editor/blocks/AdvancedTodoBlock.tsx new file mode 100644 index 00000000..b501a572 --- /dev/null +++ b/src/components/editor/blocks/AdvancedTodoBlock.tsx @@ -0,0 +1,56 @@ +"use client"; + +import { createReactBlockSpec } from "@blocknote/react"; + +const TODO_STATES = ["todo", "doing", "done", "cancelled"] as const; + +const STATUS_LABELS: Record<(typeof TODO_STATES)[number], string> = { + todo: "未开始", + doing: "进行中", + done: "已完成", + cancelled: "已取消", +}; + +export const advancedTodoBlock = createReactBlockSpec( + { + type: "advancedTodo", + propSchema: { + status: { + default: "todo", + values: TODO_STATES, + }, + }, + content: "inline", + }, + { + render: ({ block, editor }) => { + const updateStatus = (next: (typeof TODO_STATES)[number]) => { + editor.updateBlock(block, { props: { status: next } }); + }; + + const handleClick = (event: React.MouseEvent) => { + const current = block.props.status as (typeof TODO_STATES)[number]; + if (event.altKey) { + updateStatus("cancelled"); + return; + } + const index = TODO_STATES.indexOf(current); + const nextState = TODO_STATES[(index + 1) % TODO_STATES.length]; + updateStatus(nextState); + }; + + return ( +
+ +
+
+ ); + }, + }, +)(); diff --git a/src/components/editor/blocks/MediaBlock.tsx b/src/components/editor/blocks/MediaBlock.tsx new file mode 100644 index 00000000..fe6ac7d7 --- /dev/null +++ b/src/components/editor/blocks/MediaBlock.tsx @@ -0,0 +1,498 @@ +"use client"; + +import { + useEffect, + useRef, + useState, + useMemo, + type JSX, + type MouseEvent as ReactMouseEvent, +} from "react"; +import { createReactBlockSpec } from "@blocknote/react"; +import type { Block, BlockNoteEditor } from "@blocknote/core"; +import { Download, Image as ImageIcon, Link as LinkIcon, MoreHorizontal, Paperclip, RefreshCcw, Type } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { cn } from "@/lib/utils"; +import { useImagePicker } from "@/components/media/image-picker-context"; +import type { MediaKind } from "@/types/media"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import type { CustomBlockSchema } from "../schema"; + +type MediaAlign = "left" | "center" | "right"; + +type MediaBlockRenderProps = { + block: Block; + editor: BlockNoteEditor; +}; + +const TYPE_LABEL_MAP: Record = { + image: "图片", + video: "视频", + audio: "音频", + file: "文件", +}; + +const deriveFileName = (value?: string) => { + if (!value) { + return "未命名资源"; + } + try { + const url = new URL(value); + const last = url.pathname.split("/").filter(Boolean).pop(); + if (last) { + return decodeURIComponent(last); + } + } catch { + const segments = value.split("?")[0]?.split("/") ?? []; + const last = segments.pop(); + if (last) { + return decodeURIComponent(last); + } + } + return "未命名资源"; +}; + +const formatFileSize = (size?: number | null) => { + if (!size || size <= 0) { + return "未知大小"; + } + const units = ["B", "KB", "MB", "GB", "TB"]; + let idx = 0; + let current = size; + while (current >= 1024 && idx < units.length - 1) { + current /= 1024; + idx += 1; + } + return `${current.toFixed(current >= 10 ? 0 : 1)} ${units[idx]}`; +}; + +const MediaBlockContent = ({ block, editor }: MediaBlockRenderProps) => { + const { openPicker } = useImagePicker(); + const [busy, setBusy] = useState(false); + const fileUrl = block.props.fileUrl as string; + const rawAssetType = (block.props.assetType as string) || "image"; + const assetType: MediaKind = + rawAssetType === "video" || rawAssetType === "audio" || rawAssetType === "file" + ? (rawAssetType as MediaKind) + : "image"; + const typeLabel = TYPE_LABEL_MAP[assetType] ?? TYPE_LABEL_MAP.image; + const canAlign = assetType === "image" || assetType === "video"; + const canToggleBorder = assetType === "image"; + const canTriggerOcr = assetType === "image"; + const canResize = assetType === "image" || assetType === "video"; + const [dragging, setDragging] = useState(null); + const [localWidth, setLocalWidth] = useState(() => (block.props.width ? Number(block.props.width) : 0)); + const mediaRef = useRef(null); + const latestWidthRef = useRef(localWidth); + const displayFileName = block.props.fileName ?? deriveFileName(fileUrl); + const captionRef = useRef(null); + const [captionEditing, setCaptionEditing] = useState(false); + const shouldShowCaption = captionEditing || Boolean(block.props.caption); + + const handleChoose = () => { + openPicker({ + defaultTab: fileUrl ? "recent" : "upload", + mediaType: assetType, + onSelect: (selection) => { + editor.updateBlock(block, { + props: { + fileUrl: selection.fileUrl, + thumbnailUrl: selection.thumbnailUrl ?? selection.fileUrl, + assetId: selection.assetId, + assetType: selection.assetType ?? rawAssetType, + fileName: selection.fileName ?? block.props.fileName ?? "", + fileSize: selection.fileSize ?? block.props.fileSize ?? null, + mimeType: selection.mimeType ?? block.props.mimeType ?? "", + ocrStatus: "idle", + }, + }); + }, + }); + }; + + const toggleBorder = () => { + editor.updateBlock(block, { props: { hasBorder: !block.props.hasBorder } }); + }; + + const setAlign = (align: MediaAlign) => { + editor.updateBlock(block, { props: { captionAlign: align } }); + }; + + const handleCaptionChange = (value: string) => { + editor.updateBlock(block, { props: { caption: value } }); + }; + + const enableCaptionEdit = () => { + setCaptionEditing(true); + setTimeout(() => captionRef.current?.focus(), 0); + }; + + useEffect(() => { + if (!shouldShowCaption && captionEditing) { + setCaptionEditing(false); + } + }, [captionEditing, shouldShowCaption]); + useEffect(() => { + if (!dragging) { + setLocalWidth(block.props.width ? Number(block.props.width) : 0); + } + }, [block.props.width, dragging]); + useEffect(() => { + latestWidthRef.current = localWidth; + }, [localWidth]); + + const resolvedWidth = useMemo(() => { + if (!canResize) return 0; + if (localWidth > 0) return clampWidth(localWidth); + if (block.props.width && Number(block.props.width) > 0) { + return clampWidth(Number(block.props.width)); + } + return 0; + }, [block.props.width, canResize, localWidth]); + + const handleResizeStart = (event: ReactMouseEvent, side: "left" | "right") => { + if (!canResize) return; + event.preventDefault(); + event.stopPropagation(); + const canvasWidth = resolvedWidth || mediaRef.current?.offsetWidth || 0; + if (!canvasWidth) { + return; + } + setDragging({ + side, + startX: event.clientX, + startWidth: canvasWidth, + }); + }; + + useEffect(() => { + if (!dragging) { + return undefined; + } + const handleMove = (event: MouseEvent) => { + event.preventDefault(); + const delta = event.clientX - dragging.startX; + const adjusted = dragging.side === "left" ? -delta : delta; + const next = clampWidth(dragging.startWidth + adjusted); + setLocalWidth(next); + }; + const handleUp = () => { + const finalWidth = latestWidthRef.current > 0 ? latestWidthRef.current : dragging.startWidth; + editor.updateBlock(block, { props: { width: clampWidth(finalWidth) } }); + setDragging(null); + }; + window.addEventListener("mousemove", handleMove); + window.addEventListener("mouseup", handleUp); + return () => { + window.removeEventListener("mousemove", handleMove); + window.removeEventListener("mouseup", handleUp); + }; + }, [dragging, editor, block]); + + const handleLink = () => { + const next = window.prompt(`输入${typeLabel}跳转链接`, block.props.linkUrl ?? ""); + if (next === null) return; + editor.updateBlock(block, { props: { linkUrl: next.trim() } }); + }; + + const viewOriginal = () => { + if (!fileUrl) return; + window.open(fileUrl, "_blank", "noopener,noreferrer"); + }; + + const downloadAsset = () => { + if (!fileUrl) return; + const anchor = document.createElement("a"); + anchor.href = fileUrl; + anchor.download = block.props.fileName || block.props.caption || typeLabel; + anchor.click(); + }; + + const triggerOcr = async () => { + if (!block.props.assetId) { + window.alert("请先上传图片后再执行 OCR"); + return; + } + setBusy(true); + try { + const response = await fetch("/api/media/ocr", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ assetId: block.props.assetId }), + }); + if (!response.ok) { + const payload = await response.json().catch(() => null); + throw new Error(payload?.error ?? "触发 OCR 失败"); + } + editor.updateBlock(block, { props: { ocrStatus: "processing" } }); + window.alert("已提交 OCR 任务,稍后可在搜索面板中通过 OCR 结果搜索。"); + } catch (error) { + window.alert((error as Error).message); + } finally { + setBusy(false); + } + }; + + if (!fileUrl) { + return ( +
+ +

支持上传、最近及外链插入

+
+ ); + } + + const renderPreviewContent = () => { + if (assetType === "video") { + return ( + + ); + } + if (assetType === "audio") { + return ( +
+ +

{displayFileName}

+
+ ); + } + if (assetType === "file") { + return ( +
+ + + +
+

{displayFileName}

+ {block.props.fileSize ? ( +

{formatFileSize(block.props.fileSize)}

+ ) : null} +
+
+ ); + } + const inlineStyle = resolvedWidth ? { width: `${resolvedWidth}px` } : undefined; + return {block.props.caption; + }; + + const figure = ( +
+
{renderPreviewContent()}
+ {shouldShowCaption && ( +
+ handleCaptionChange(event.target.value)} + onBlur={() => setCaptionEditing(false)} + placeholder={assetType === "file" ? "添加文件说明..." : "添加说明..."} + className="w-full border-none bg-transparent text-sm text-[#475569] outline-none" + /> +
+ )} +
+ ); + + type QuickAction = { key: string; label: string; icon: JSX.Element; onClick: () => void }; + const quickActions: QuickAction[] = [ + { + key: "replace", + label: `替换${typeLabel}`, + icon: , + onClick: handleChoose, + }, + canToggleBorder + ? { + key: "border", + label: block.props.hasBorder ? "取消边框" : "显示边框", + icon: , + onClick: toggleBorder, + } + : null, + !shouldShowCaption + ? { + key: "caption", + label: "添加说明", + icon: , + onClick: enableCaptionEdit, + } + : null, + { + key: "link", + label: block.props.linkUrl ? "编辑链接" : "添加链接", + icon: , + onClick: handleLink, + }, + { + key: "download", + label: `下载${typeLabel}`, + icon: , + onClick: downloadAsset, + }, + ].filter((action): action is QuickAction => Boolean(action)); + + const dropdownLinkLabel = block.props.linkUrl ? "编辑链接" : "添加链接"; + + return ( +
+
+ {block.props.linkUrl ? ( + + {figure} + + ) : ( + figure + )} +
+ {quickActions.map((action) => ( + + ))} + + + + + + 替换资源 + {!shouldShowCaption && ( + 添加说明 + )} + {canToggleBorder && ( + + {block.props.hasBorder ? "取消边框" : "显示边框"} + + )} + {canAlign && ( + <> + 说明对齐 + setAlign("left")}>左对齐 + setAlign("center")}>居中 + setAlign("right")}>右对齐 + + + )} + {dropdownLinkLabel} + handleCopyLink(fileUrl)}>复制链接 + 查看原文件 + 下载到本地 + {canTriggerOcr && ( + <> + + + {busy ? "OCR 进行中..." : "触发 OCR 识别"} + + + )} + + +
+ {canResize && ( + <> + handleResizeStart(event, "left")} /> + handleResizeStart(event, "right")} /> + + )} +
+
{block.props.ocrStatus === "processing" ? "OCR 处理中..." : ""}
+
+ ); +}; + +export const mediaBlock = createReactBlockSpec( + { + type: "media", + propSchema: { + fileUrl: { default: "", type: "string" }, + thumbnailUrl: { default: "", type: "string" }, + caption: { default: "", type: "string" }, + captionAlign: { default: "left", values: ["left", "center", "right"] as MediaAlign[] }, + hasBorder: { default: true, type: "boolean" }, + linkUrl: { default: "", type: "string" }, + assetId: { default: "", type: "string" }, + assetType: { default: "image", type: "string" }, + fileName: { default: "", type: "string" }, + fileSize: { default: 0, type: "number" }, + mimeType: { default: "", type: "string" }, + width: { default: 0, type: "number" }, + ocrStatus: { default: "idle", type: "string" }, + }, + content: "none", + }, + { + render: (props) => , + }, +)(); +const handleCopyLink = async (targetUrl: string | null) => { + if (!targetUrl) return; + try { + if (navigator?.clipboard?.writeText) { + await navigator.clipboard.writeText(targetUrl); + window.alert("链接已复制"); + } else { + throw new Error("no clipboard"); + } + } catch { + window.prompt("请复制以下链接", targetUrl); + } +}; + +const ResizeHandle = ({ + side, + onMouseDown, + dragging, +}: { + side: "left" | "right"; + dragging: boolean; + onMouseDown: (event: ReactMouseEvent) => void; +}) => ( + +); + +const clampWidth = (value: number) => { + const min = 240; + const max = 960; + if (Number.isNaN(value)) return min; + return Math.max(min, Math.min(max, value)); +}; diff --git a/src/components/editor/blocks/PageReferenceBlock.tsx b/src/components/editor/blocks/PageReferenceBlock.tsx new file mode 100644 index 00000000..a8905fe5 --- /dev/null +++ b/src/components/editor/blocks/PageReferenceBlock.tsx @@ -0,0 +1,114 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { createReactBlockSpec } from "@blocknote/react"; +import { RiFileTextFill } from "react-icons/ri"; +import { useRouter } from "next/navigation"; +import { supabaseBrowser } from "@/lib/supabase/client"; + +const normalizeTitle = (value?: string | null) => { + if (!value || !value.trim()) { + return "未命名页面"; + } + return value; +}; + +const PageReferenceContent = ({ pageId, title }: { pageId: string; title: string }) => { + const router = useRouter(); + const fallbackTitle = normalizeTitle(title); + const [resolvedTitle, setResolvedTitle] = useState(fallbackTitle); + + useEffect(() => { + setResolvedTitle(fallbackTitle); + }, [fallbackTitle]); + + useEffect(() => { + if (!pageId) { + return; + } + let cancelled = false; + const applyTitle = (nextTitle?: string | null) => { + if (!cancelled) { + setResolvedTitle(normalizeTitle(nextTitle)); + } + }; + + const fetchTitle = async () => { + try { + const { data } = await supabaseBrowser + .from("documents") + .select("title") + .eq("id", pageId) + .single(); + if (data) { + applyTitle(data.title); + } + } catch { + // ignore fetch errors,等待后续订阅同步 + } + }; + + void fetchTitle(); + + const channel = supabaseBrowser + .channel(`page-ref-${pageId}`) + .on( + "postgres_changes", + { event: "UPDATE", schema: "public", table: "documents", filter: `id=eq.${pageId}` }, + (payload) => { + const nextTitle = (payload.new as { title?: string } | null)?.title ?? null; + applyTitle(nextTitle); + }, + ) + .subscribe(); + + return () => { + cancelled = true; + supabaseBrowser.removeChannel(channel); + }; + }, [pageId]); + + const navigate = () => { + if (pageId) { + router.push(`/documents/${pageId}`); + } + }; + + return ( +
{ + if ((event.key === "Enter" || event.key === " ") && pageId) { + event.preventDefault(); + navigate(); + } + }} + className="group mt-2 flex items-center gap-3 rounded-[4px] px-4 py-3 text-[#2563eb] hover:bg-[#f5f5f5]" + style={{ fontFamily: "Inter, system-ui, sans-serif" }} + > + + {resolvedTitle} + + 点击进入 → + +
+ ); +}; + +export const pageReferenceBlock = createReactBlockSpec( + { + type: "pageReference", + propSchema: { + pageId: { default: "" }, + title: { default: "未命名页面" }, + }, + content: "none", + }, + () => ({ + render: ({ block }) => ( + + ), + }), +)(); diff --git a/src/components/editor/blocks/ProgressBlock.tsx b/src/components/editor/blocks/ProgressBlock.tsx new file mode 100644 index 00000000..347401b7 --- /dev/null +++ b/src/components/editor/blocks/ProgressBlock.tsx @@ -0,0 +1,50 @@ +"use client"; + +import { createReactBlockSpec } from "@blocknote/react"; + +export const progressBlock = createReactBlockSpec( + { + type: "progressMeter", + propSchema: { + percent: { default: 0, type: "number" }, + auto: { default: true, type: "boolean" }, + summary: { default: "", type: "string" }, + }, + content: "inline", + }, + { + render: ({ block, editor }) => { + const percent = block.props.percent ?? 0; + const handleToggle = () => { + editor.updateBlock(block, { props: { auto: !block.props.auto } }); + }; + + const handleBarClick = () => { + if (block.props.auto) return; + const input = window.prompt("设置进度(0-100)", percent.toString()); + if (!input) return; + const value = Number.parseInt(input, 10); + if (Number.isNaN(value)) return; + editor.updateBlock(block, { + props: { percent: Math.min(100, Math.max(0, value)) }, + }); + }; + + return ( +
+
+ {block.props.summary || "暂无条目"} + +
+
+
+
+ {percent}% +
+
+ ); + }, + }, +)(); diff --git a/src/components/editor/document-content.tsx b/src/components/editor/document-content.tsx new file mode 100644 index 00000000..a4284a29 --- /dev/null +++ b/src/components/editor/document-content.tsx @@ -0,0 +1,259 @@ +"use client"; + +import dynamic from "next/dynamic"; +import { useCallback, useEffect, useMemo, useState, type ChangeEvent, type KeyboardEvent } from "react"; +import type { DocumentStats, PageOptionsState } from "@/types/page-options"; +import { PageOptionsSidebar } from "@/components/editor/page-options-sidebar"; +import { PageBacklinksPanel } from "@/components/editor/page-backlinks-panel"; +import { useEditorBridgeStore } from "@/store/editor-bridge"; +import { usePageLayoutStore } from "@/store/page-layout"; +import type { Json } from "@/types/supabase"; +import { DocumentHistoryDrawer } from "@/components/editor/document-history-drawer"; +import type { DocumentSnapshot } from "@/types/document"; +import { useDebouncedCallback } from "@/hooks/use-debounced-callback"; +import { ImagePickerProvider } from "@/components/media/image-picker-context"; + +const BlockNoteEditor = dynamic( + () => import("@/components/editor/blocknote-editor").then((mod) => mod.BlockNoteEditor), + { + ssr: false, + loading: () => ( +
编辑器加载中...
+ ), + }, +); + +export interface DocumentContentProps { + documentId: string; + workspaceId: string; + title: string | null; + updatedAt: string | null; + initialContent: unknown; + initialOptions: PageOptionsState; + initialStats: DocumentStats | null; +} + +const defaultOptions: PageOptionsState = { + wideLayout: false, + smallText: false, + showHeadingNumbers: true, + showToc: false, + showStructure: false, + protectEditing: false, + showWordCount: true, +}; +const defaultStats: DocumentStats = { wordCount: 0, characterCount: 0, blockCount: 0 }; + +export function DocumentContent({ + documentId, + workspaceId, + title, + updatedAt, + initialContent, + initialOptions, + initialStats, +}: DocumentContentProps) { + const [options, setOptions] = useState(initialOptions ?? defaultOptions); + const [stats, setStats] = useState(initialStats ?? defaultStats); + const [history, setHistory] = useState([]); + const [historyOpen, setHistoryOpen] = useState(false); + const editorBridge = useEditorBridgeStore((state) => state.bridge); + const showInspector = usePageLayoutStore((state) => state.showInspector); + const [pageTitle, setPageTitle] = useState(title ?? "无标题"); + + /* eslint-disable react-hooks/set-state-in-effect */ + useEffect(() => { + setPageTitle(title ?? "无标题"); + }, [title]); + + useEffect(() => { + setOptions(initialOptions ?? defaultOptions); + }, [initialOptions]); + + useEffect(() => { + setStats(initialStats ?? defaultStats); + }, [initialStats]); + /* eslint-enable react-hooks/set-state-in-effect */ + + const persistTitle = useCallback( + async (nextTitle: string) => { + const payload = nextTitle.trim() || "无标题"; + await fetch("/api/documents/title", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ documentId, title: payload }), + }); + }, + [documentId], + ); + + const debouncedPersistTitle = useDebouncedCallback((value: string) => { + void persistTitle(value); + }, 600); + + const handleTitleChange = (event: ChangeEvent) => { + const value = event.target.value; + setPageTitle(value); + debouncedPersistTitle(value); + }; + + const handleTitleBlur = () => { + void persistTitle(pageTitle); + }; + + const handleTitleKeyDown = (event: KeyboardEvent) => { + if (event.key === "Enter") { + event.preventDefault(); + event.currentTarget.blur(); + } + }; + + const persistOptions = useCallback( + async (patch: Partial) => { + try { + const response = await fetch("/api/documents/options", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ documentId, options: patch }), + }); + if (!response.ok) { + const payload = await response.json().catch(() => null); + console.error(payload?.error ?? "更新页面选项失败"); + } + } catch (error) { + console.error(error); + } + }, + [documentId], + ); + + const toggleOption = (key: keyof PageOptionsState) => { + setOptions((prev) => { + const nextValue = !prev[key]; + const next = { ...prev, [key]: nextValue }; + void persistOptions({ [key]: nextValue } as Partial); + return next; + }); + }; + + const formattedUpdatedAt = useMemo(() => { + if (!updatedAt) return ""; + return new Date(updatedAt).toLocaleString(); + }, [updatedAt]); + + const handleExport = useCallback(() => { + const latest = history[0]; + if (!latest) { + window.alert("暂无可导出的内容"); + return; + } + const payload = JSON.stringify(latest.blocks, null, 2); + const blob = new Blob([payload], { type: "application/json" }); + const url = URL.createObjectURL(blob); + const anchor = document.createElement("a"); + anchor.href = url; + anchor.download = `${title ?? "未命名页面"}-${new Date(latest.timestamp).toISOString()}.json`; + anchor.click(); + URL.revokeObjectURL(url); + }, [history, title]); + + const handleSnapshot = useCallback((payload: { blocks: Json; stats: DocumentStats }) => { + setHistory((prev) => { + const now = Date.now(); + if (prev.length > 0 && now - prev[0].timestamp < 4000) { + return prev; + } + const snapshot: DocumentSnapshot = { + id: `${now}-${Math.random().toString(36).slice(2, 8)}`, + timestamp: now, + blocks: payload.blocks, + stats: payload.stats, + }; + return [snapshot, ...prev].slice(0, 15); + }); + }, []); + + const persistStatsRequest = useCallback((next: DocumentStats) => { + void fetch("/api/documents/stats", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ documentId, stats: next }), + }).catch((error) => console.error(error)); + }, [documentId]); + + const persistStats = useDebouncedCallback(persistStatsRequest, 1500); + + const handleStatsChange = useCallback( + (nextStats: DocumentStats) => { + setStats(nextStats); + persistStats(nextStats); + }, + [persistStats], + ); + + const handleRestoreSnapshot = useCallback( + (snapshot: DocumentSnapshot) => { + if (!editorBridge) { + window.alert("编辑器尚未准备好,无法恢复历史版本"); + return; + } + editorBridge.replaceWithSnapshot(snapshot.blocks); + setHistoryOpen(false); + }, + [editorBridge], + ); + + return ( + +
+
+
+
+ +
+ {options.protectEditing && ( +

当前页面已开启编辑保护,关闭后方可修改内容。

+ )} +

最近更新:{formattedUpdatedAt}

+
+
+ + +
+
+ {showInspector && ( + setHistoryOpen(true)} + /> + )} +
+ +
+ ); +} diff --git a/src/components/editor/document-history-drawer.tsx b/src/components/editor/document-history-drawer.tsx new file mode 100644 index 00000000..0c593614 --- /dev/null +++ b/src/components/editor/document-history-drawer.tsx @@ -0,0 +1,51 @@ +"use client"; + +import { Drawer, DrawerContent, DrawerDescription, DrawerHeader, DrawerTitle } from "@/components/ui/drawer"; +import { Button } from "@/components/ui/button"; +import type { DocumentSnapshot } from "@/types/document"; + +interface DocumentHistoryDrawerProps { + open: boolean; + onOpenChange: (open: boolean) => void; + history: DocumentSnapshot[]; + onRestore: (snapshot: DocumentSnapshot) => void; +} + +export function DocumentHistoryDrawer({ open, onOpenChange, history, onRestore }: DocumentHistoryDrawerProps) { + return ( + + + + 页面历史 + 最近保存的 15 个版本,可以一键恢复。 + +
+ {history.length === 0 ? ( +
+ 尚未产生历史快照,编辑后会自动生成。 +
+ ) : ( + history.map((snapshot) => ( +
+
+
+ {new Date(snapshot.timestamp).toLocaleString()} +
+
+ 字数 {snapshot.stats.wordCount} · 字符 {snapshot.stats.characterCount} +
+
+ +
+ )) + )} +
+
+
+ ); +} diff --git a/src/components/editor/document-shell.tsx b/src/components/editor/document-shell.tsx new file mode 100644 index 00000000..fb2021e7 --- /dev/null +++ b/src/components/editor/document-shell.tsx @@ -0,0 +1,37 @@ +"use client"; + +import dynamic from "next/dynamic"; +import { useEffect, useState } from "react"; +import type { DocumentContentProps } from "@/components/editor/document-content"; + +const DocumentContent = dynamic( + () => import("@/components/editor/document-content").then((mod) => mod.DocumentContent), + { + ssr: false, + loading: () => ( +
+ 正在载入编辑器... +
+ ), + }, +); + +export function DocumentShell(props: DocumentContentProps) { + const [mounted, setMounted] = useState(false); + + /* eslint-disable react-hooks/set-state-in-effect */ + useEffect(() => { + setMounted(true); + }, []); + /* eslint-enable react-hooks/set-state-in-effect */ + + if (!mounted) { + return ( +
+ 正在载入编辑器... +
+ ); + } + + return ; +} diff --git a/src/components/editor/document-toc.tsx b/src/components/editor/document-toc.tsx new file mode 100644 index 00000000..cdb96bf7 --- /dev/null +++ b/src/components/editor/document-toc.tsx @@ -0,0 +1,48 @@ +"use client"; + +import { cn } from "@/lib/utils"; + +export interface TocEntry { + id: string; + title: string; + level: number; + numbering: string; +} + +interface DocumentTocProps { + entries: TocEntry[]; + visible: boolean; + onJump: (id: string) => void; +} + +export function DocumentToc({ entries, visible, onJump }: DocumentTocProps) { + if (!visible || entries.length === 0) { + return null; + } + + return ( +
+
+
标题目录
+
    + {entries.map((entry) => ( +
  • + +
  • + ))} +
+
+
+ ); +} diff --git a/src/components/editor/menus/CustomSideMenu.tsx b/src/components/editor/menus/CustomSideMenu.tsx new file mode 100644 index 00000000..45a56b09 --- /dev/null +++ b/src/components/editor/menus/CustomSideMenu.tsx @@ -0,0 +1,335 @@ +'use client'; + +import { useCallback, useMemo } from "react"; +import type { Block, PartialBlock } from "@blocknote/core"; +import { + BlockColorsItem, + SideMenu, + TableColumnHeaderItem, + TableRowHeaderItem, + useBlockNoteEditor, + useComponentsContext, + type DragHandleMenuProps, + type SideMenuProps, +} from "@blocknote/react"; +import { useRouter } from "next/navigation"; +import type { CustomBlockSchema } from "../schema"; + +type InlineNode = { text?: unknown }; +type TableMenuBlock = Parameters< + typeof TableRowHeaderItem +>[0]["block"]; +type DraftBlock = PartialBlock & { id?: string }; +type ConvertOption = { + label: string; + type?: Block["type"]; + props?: Record; + shortcut?: string; + action?: () => void; +}; + +type CustomDragProps = DragHandleMenuProps & { + currentDocumentId: string; +}; + +const extractText = (block: Block) => { + const inlineNodes = block.content as InlineNode[] | undefined; + const maybeText = inlineNodes?.[0]?.text; + if (typeof maybeText === "string" && maybeText.trim().length > 0) { + return maybeText.trim(); + } + return "未命名页面"; +}; + +const CustomDragHandleMenu = ({ block, currentDocumentId }: CustomDragProps) => { + const Components = useComponentsContext()!; + const editor = useBlockNoteEditor(); + const router = useRouter(); + + const duplicateBlock = useCallback(() => { + const blockWithoutId: DraftBlock = { ...block }; + delete blockWithoutId.id; + editor.insertBlocks([blockWithoutId], block, "after"); + }, [block, editor]); + + const removePageReference = useCallback(async () => { + if (block.type === "pageReference") { + const pageId = block.props.pageId; + if (pageId) { + await fetch("/api/documents/delete", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ documentId: pageId }), + }); + } + } + editor.removeBlocks([block.id]); + router.refresh(); + }, [block, editor, router]); + + const handleDeleteBlock = useCallback(() => { + if (block.type === "pageReference") { + void removePageReference(); + return; + } + editor.removeBlocks([block.id]); + }, [block, editor, removePageReference]); + + const turnToPage = useCallback(async () => { + const response = await fetch("/api/documents/create-child", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + parentId: currentDocumentId, + title: extractText(block), + blocks: [block], + }), + }); + + if (!response.ok) { + return; + } + + const { pageId, title } = await response.json(); + + editor.replaceBlocks( + [block.id], + [ + { + type: "pageReference", + props: { pageId, title }, + } as PartialBlock, + ], + ); + router.refresh(); + }, [block, currentDocumentId, editor, router]); + + const moveOrEmbedBlock = useCallback(async () => { + if (typeof window === "undefined") { + return; + } + const targetParent = window.prompt("输入目标页面 ID(将在该页面末尾插入新子页面)", currentDocumentId); + if (!targetParent) { + return; + } + const response = await fetch("/api/documents/create-child", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + parentId: targetParent.trim(), + title: extractText(block), + blocks: [block], + }), + }); + if (!response.ok) { + window.alert("移动失败,请确认页面 ID"); + return; + } + const { pageId, title } = await response.json(); + editor.replaceBlocks( + [block.id], + [ + { + type: "pageReference", + props: { pageId, title }, + } as PartialBlock, + ], + ); + router.refresh(); + }, [block, currentDocumentId, editor, router]); + + const convertOptions = useMemo( + () => [ + { label: "文本", type: "paragraph", shortcut: "Ctrl+Alt+0" }, + { label: "待办列表", type: "checkListItem", shortcut: "Ctrl+Shift+5" }, + { label: "高级待办列表", type: "advancedTodo" }, + { label: "主标题", type: "heading", props: { level: 1 }, shortcut: "Ctrl+Shift+1" }, + { label: "大标题", type: "heading", props: { level: 2 }, shortcut: "Ctrl+Shift+2" }, + { label: "中标题", type: "heading", props: { level: 3 }, shortcut: "Ctrl+Shift+3" }, + { label: "小标题", type: "heading", props: { level: 4 }, shortcut: "Ctrl+Shift+4" }, + { label: "页面", action: turnToPage }, + { label: "列表", type: "bulletListItem", shortcut: "Ctrl+Shift+6" }, + { label: "数字列表", type: "numberedListItem", shortcut: "Ctrl+Shift+7" }, + { label: "折叠列表", type: "toggleListItem", shortcut: "Ctrl+Shift+8" }, + { label: "折叠标题", type: "heading", props: { level: 2, isToggleable: true } }, + { label: "引述文字", type: "blockquote" }, + { label: "代码片段", type: "codeBlock" }, + ], + [turnToPage], + ); + + const convertBlock = useCallback( + (option: ConvertOption) => { + if (option.action) { + option.action(); + return; + } + if (!option.type) return; + editor.updateBlock(block, { + type: option.type, + props: option.props ?? {}, + }); + }, + [block, editor], + ); + + const copyBlockLink = useCallback(async () => { + if (typeof window === "undefined") { + return; + } + const url = `${window.location.origin}/documents/${currentDocumentId}#block-${block.id}`; + try { + await navigator.clipboard.writeText(url); + window.alert("块链接已复制"); + } catch { + window.prompt("复制失败,请手动复制", url); + } + }, [block.id, currentDocumentId]); + + const openOnRight = useCallback(() => { + if (typeof window === "undefined") { + return; + } + const url = `${window.location.origin}/documents/${currentDocumentId}?preview=sidebar&focus=${block.id}`; + window.open(url, "_blank", "noopener,noreferrer"); + }, [block.id, currentDocumentId]); + + const setAdvancedTodoStatus = useCallback( + (status: "todo" | "doing" | "done" | "cancelled") => { + if (block.type !== "advancedTodo") return; + editor.updateBlock(block, { + props: { status }, + }); + }, + [block, editor], + ); + + const toggleProgressMode = useCallback(() => { + if (block.type !== "progressMeter") return; + editor.updateBlock(block, { + props: { auto: !block.props.auto }, + }); + }, [block, editor]); + + const setManualProgress = useCallback(() => { + if (block.type !== "progressMeter") return; + const value = Number.parseInt(window.prompt("手动设置进度(0-100)", String(block.props.percent ?? 0)) ?? "", 10); + if (Number.isNaN(value)) return; + const clamped = Math.min(100, Math.max(0, value)); + editor.updateBlock(block, { + props: { percent: clamped }, + }); + }, [block, editor]); + + return ( + + + 在右侧边栏打开 + + + + + + 转换为 + + + + {convertOptions.map((option) => ( + convertBlock(option)} + > + {option.label} + {option.shortcut && {option.shortcut}} + + ))} + + + + + 拷贝副本 + + + + 复制链接 + + + + 移动/嵌入到... + + + window.alert("块历史功能开发中,敬请期待")} + > + 块历史... + + + window.alert("评论功能暂未开放")} + > + 评论 + + + + 删除 + + + 颜色 + 表头(行) + 表头(列) + + {block.type === "advancedTodo" && ( + <> + + {[ + { label: "设为未开始", status: "todo" as const }, + { label: "设为进行中", status: "doing" as const }, + { label: "设为已完成", status: "done" as const }, + { label: "设为取消", status: "cancelled" as const }, + ].map((item) => ( + setAdvancedTodoStatus(item.status)} + > + {item.label} + + ))} + + )} + + {block.type === "progressMeter" && ( + <> + + + {block.props.auto ? "切换为手动进度" : "切换为自动进度"} + + {!block.props.auto && ( + + 手动设置百分比 + + )} + + )} + + ); +}; + +type CustomSideMenuProps = SideMenuProps & { + currentDocumentId: string; +}; + +export const CustomSideMenu = (props: CustomSideMenuProps) => ( + ( + )} + currentDocumentId={props.currentDocumentId} + /> + )} + /> +); diff --git a/src/components/editor/menus/CustomSlashMenu.tsx b/src/components/editor/menus/CustomSlashMenu.tsx new file mode 100644 index 00000000..7450686d --- /dev/null +++ b/src/components/editor/menus/CustomSlashMenu.tsx @@ -0,0 +1,343 @@ +"use client"; + +import { useCallback, useMemo } from "react"; +import type { JSX } from "react"; +import { + SuggestionMenuController, + getDefaultReactSlashMenuItems, + type DefaultReactSuggestionItem, +} from "@blocknote/react"; +import { filterSuggestionItems, type BlockNoteEditor } from "@blocknote/core"; +import { useRouter } from "next/navigation"; +import { + FileImage, + FilePlus2, + FileVideo, + ListTree, + Music, + Paperclip, + PilcrowSquare, + Play, + Sparkles, + SquareCheckBig, +} from "lucide-react"; +import type { CustomBlockSchema } from "../schema"; +import { useImagePicker } from "@/components/media/image-picker-context"; +import type { MediaKind, MediaSelection } from "@/types/media"; + +type Props = { + editor: BlockNoteEditor; + currentDocumentId: string; +}; + +const matchKeywords = (query: string, aliases: string[]) => { + const lower = query.trim().toLowerCase(); + if (!lower) return true; + return aliases.some((alias) => alias.toLowerCase().includes(lower)); +}; + +const GROUP_TRANSLATIONS: Record = { + "Headings": "标题", + "Subheadings": "副标题", + "Basic blocks": "基础块", + "Advanced": "高级", + "Media": "媒体", + "Others": "其他", +}; + +const DEFAULT_ITEM_TRANSLATIONS: Record< + string, + { title?: string; subtext?: string; group?: string; aliases?: string[] } +> = { + "Paragraph": { title: "正文", group: "基础块", aliases: ["zw", "paragraph", "body"] }, + "Heading 1": { title: "主标题", group: "标题", aliases: ["biaoti", "bt1"] }, + "Heading 2": { title: "大标题", group: "标题", aliases: ["biaoti", "bt2"] }, + "Heading 3": { title: "中标题", group: "标题", aliases: ["biaoti", "bt3"] }, + "Heading 4": { title: "小标题", group: "标题", aliases: ["biaoti", "bt4"] }, + "Heading 5": { title: "极小标题", group: "标题", aliases: ["biaoti", "bt5"] }, + "Heading 6": { title: "最小标题", group: "标题", aliases: ["biaoti", "bt6"] }, + "Toggle Heading 1": { title: "可折叠主标题", group: "标题", aliases: ["toggle", "zd1"] }, + "Toggle Heading 2": { title: "可折叠大标题", group: "标题", aliases: ["toggle", "zd2"] }, + "Toggle Heading 3": { title: "可折叠中标题", group: "标题", aliases: ["toggle", "zd3"] }, + "Quote": { title: "引用", group: "基础块", aliases: ["quote", "引用"] }, + "Toggle List": { title: "折叠列表", group: "基础块", aliases: ["toggle list", "zd"] }, + "Numbered List": { title: "数字列表", group: "基础块", aliases: ["ordered", "ol"] }, + "Bullet List": { title: "符号列表", group: "基础块", aliases: ["ul", "list"] }, + "Check List": { title: "任务列表", group: "基础块", aliases: ["todo", "checkbox"] }, + "Code Block": { title: "代码块", group: "基础块", aliases: ["code", "pre"] }, + "Table": { title: "表格", group: "高级", aliases: ["table", "biaoge"] }, + "Image": { title: "插入图片", group: "媒体", subtext: "上传或引用图片资源", aliases: ["tupian", "image", "tp"] }, + "Video": { title: "插入视频", group: "媒体", subtext: "上传或引用视频", aliases: ["shipin", "video", "sp"] }, + "Audio": { title: "插入音频", group: "媒体", subtext: "上传或引用音频", aliases: ["yinpin", "audio", "yp"] }, + "File": { title: "插入文件", group: "媒体", subtext: "上传附件并生成卡片", aliases: ["wenjian", "file", "wj"] }, + "Emoji": { title: "插入表情", group: "其他", aliases: ["emoji", "biaoqing"] }, + "Divider": { title: "分割线", group: "基础块", aliases: ["divider", "hr"] }, + "Page Break": { title: "分页符", group: "基础块", aliases: ["page", "break"] }, +}; + +const MEDIA_ICONS: Record = { + image: , + video: , + audio: , + file: , +}; + +const HEADING_PRESETS = [ + { + level: 1, + title: "主标题", + subtext: "适合页面名称/顶层章节", + aliases: ["biaoti1", "h1", "level1"], + }, + { + level: 2, + title: "大标题", + subtext: "用于章节逻辑层", + aliases: ["biaoti2", "h2", "level2"], + }, + { + level: 3, + title: "中标题", + subtext: "用于小节和段落", + aliases: ["biaoti3", "h3", "level3"], + }, + { + level: 4, + title: "小标题", + subtext: "更细的结构说明", + aliases: ["biaoti4", "h4", "level4"], + }, + { + level: 5, + title: "极小标题", + subtext: "适合脚注/补充说明", + aliases: ["biaoti5", "h5", "level5"], + }, +]; + +const isHeadingDefaultItem = (item: DefaultReactSuggestionItem) => { + const maybeKey = (item as { key?: string }).key ?? ""; + if (maybeKey && (maybeKey === "heading" || maybeKey.startsWith("heading_") || maybeKey.startsWith("toggle_heading"))) { + return true; + } + const title = item.title ?? ""; + return title.includes("标题"); +}; + +export const CustomSlashMenu = ({ editor, currentDocumentId }: Props) => { + const defaultItems = useMemo(() => getDefaultReactSlashMenuItems(editor), [editor]); + const router = useRouter(); + const { openPicker } = useImagePicker(); + + const getItems = useCallback( + async (query: string) => { + const cursor = editor.getTextCursorPosition(); + const referenceBlock = cursor?.block ?? editor.topLevelBlocks[0]; + + const createPageItem: DefaultReactSuggestionItem = { + title: "嵌入页面", + group: "嵌入", + aliases: ["page", "ym", "子页面", "嵌入页面块"], + icon: , + onItemClick: async () => { + const response = await fetch("/api/documents/create-child", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + parentId: currentDocumentId, + title: cursor?.block?.content?.[0]?.text ?? "未命名页面", + blocks: cursor ? [cursor.block] : [], + }), + }); + + if (!response.ok) return; + const { pageId, title } = await response.json(); + editor.insertBlocks( + [ + { + type: "pageReference", + props: { pageId, title }, + }, + ], + referenceBlock, + "after", + ); + router.refresh(); + }, + }; + + const headingItems: DefaultReactSuggestionItem[] = HEADING_PRESETS.map((preset) => ({ + title: preset.title, + group: "标题", + subtext: preset.subtext, + aliases: preset.aliases, + icon: , + onItemClick: () => { + editor.insertBlocks( + [ + { + type: "heading", + props: { level: preset.level }, + }, + ], + referenceBlock, + "after", + ); + }, + })); + + const foldHeading: DefaultReactSuggestionItem = { + title: "折叠标题", + group: "标题", + aliases: ["toggle", "zd", "fold"], + icon: , + onItemClick: () => { + editor.insertBlocks( + [ + { + type: "heading", + props: { level: 2, isToggleable: true }, + }, + ], + referenceBlock, + "after", + ); + }, + }; + + const advancedTodo: DefaultReactSuggestionItem = { + title: "高级待办", + group: "待办", + subtext: "四态状态 · Alt 直接取消", + aliases: ["gjdblb", "todopro", "gaoji", "todo+"], + icon: , + onItemClick: () => { + editor.insertBlocks( + [ + { + type: "advancedTodo", + props: { status: "todo" }, + }, + ], + referenceBlock, + "after", + ); + }, + }; + + const progressMeter: DefaultReactSuggestionItem = { + title: "进度条", + group: "进度", + subtext: "自动读取下方待办完成度", + aliases: ["jdt", "progress", "jindu"], + icon: , + onItemClick: () => { + editor.insertBlocks( + [ + { + type: "progressMeter", + props: { percent: 0, auto: true }, + }, + ], + referenceBlock, + "after", + ); + }, + }; + + const foldAdvancedTodo: DefaultReactSuggestionItem = { + title: "折叠高级待办", + group: "待办", + aliases: ["zdgjdb", "foldtodo"], + icon: , + onItemClick: () => { + editor.insertBlocks( + [ + { + type: "advancedTodo", + props: { status: "todo" }, + }, + ], + referenceBlock, + "after", + ); + }, + }; + + const customItems = [ + ...headingItems, + foldHeading, + createPageItem, + advancedTodo, + foldAdvancedTodo, + progressMeter, + ].filter((item) => matchKeywords(query, item.aliases ?? [])); + + const insertMediaSelection = (selection: MediaSelection) => { + editor.insertBlocks( + [ + { + type: "media", + props: { + fileUrl: selection.fileUrl, + thumbnailUrl: selection.thumbnailUrl ?? selection.fileUrl, + assetId: selection.assetId, + assetType: selection.assetType ?? "image", + fileName: selection.fileName ?? "", + fileSize: selection.fileSize ?? null, + mimeType: selection.mimeType ?? "", + ocrStatus: "idle", + }, + }, + ], + referenceBlock, + "after", + ); + }; + + const handleMediaPick = (mediaType: MediaKind) => { + openPicker({ + mediaType, + onSelect: (selection) => { + insertMediaSelection({ + ...selection, + assetType: selection.assetType ?? mediaType, + }); + }, + }); + }; + + const localizedDefaults = defaultItems.map((item) => { + const translation = DEFAULT_ITEM_TRANSLATIONS[item.title]; + const next: DefaultReactSuggestionItem = { ...item }; + if (translation?.title) next.title = translation.title; + if (translation?.subtext) next.subtext = translation.subtext; + if (translation?.aliases) next.aliases = translation.aliases; + if (translation?.group) { + next.group = translation.group; + } else if (item.group && GROUP_TRANSLATIONS[item.group]) { + next.group = GROUP_TRANSLATIONS[item.group]; + } + + if (["Image", "Video", "Audio", "File"].includes(item.title)) { + const mediaType = item.title.toLowerCase() as MediaKind; + next.icon = MEDIA_ICONS[mediaType]; + next.group = translation?.group ?? "媒体"; + next.subtext = translation?.subtext ?? next.subtext; + next.aliases = translation?.aliases ?? next.aliases; + next.onItemClick = () => handleMediaPick(mediaType); + } + return next; + }); + + const sanitizedDefaults = localizedDefaults.filter( + (item) => !isHeadingDefaultItem(item) && item.title !== "表格", + ); + const merged = [...customItems, ...sanitizedDefaults]; + return filterSuggestionItems(merged, query); + }, + [currentDocumentId, defaultItems, editor, openPicker, router], + ); + + return ; +} diff --git a/src/components/editor/page-backlinks-panel.tsx b/src/components/editor/page-backlinks-panel.tsx new file mode 100644 index 00000000..3a5be0b8 --- /dev/null +++ b/src/components/editor/page-backlinks-panel.tsx @@ -0,0 +1,77 @@ +"use client"; + +import { useMemo } from "react"; +import { Button } from "@/components/ui/button"; +import { useBacklinks } from "@/hooks/use-backlinks"; +import type { BacklinkRecord } from "@/types/references"; +import { cn } from "@/lib/utils"; + +interface PageBacklinksPanelProps { + workspaceId: string; + documentId: string; + className?: string; +} + +const formatRelative = (value: string) => { + if (!value) return ""; + const date = new Date(value); + return date.toLocaleString(); +}; + +const EmptyState = () => ( +
+ 暂无反向引用。试试输入 [[#{" "} + 来引用其他页面。 +
+); + +const BacklinkItem = ({ record }: { record: BacklinkRecord }) => ( +
+
+ {record.alias || record.sourceTitle || "无标题"} + {record.displayMode === "embed" ? "嵌入块" : "行内引用"} +
+
+ 来自页面:{record.sourceTitle || "无标题"} · 更新:{formatRelative(record.updatedAt)} +
+
+); + +export function PageBacklinksPanel({ workspaceId, documentId, className }: PageBacklinksPanelProps) { + const { data, isLoading, error, refetch, isFetching } = useBacklinks({ + workspaceId, + documentId, + }); + + const records = useMemo(() => data ?? [], [data]); + if (!isLoading && !error && records.length === 0) { + return null; + } + + return ( +
+
+
+

反向引用

+

展示指向当前页面的所有页面或块引用

+
+ +
+ {isLoading ? ( +
加载引用中...
+ ) : error ? ( +
{(error as Error).message}
+ ) : records.length === 0 ? ( + + ) : ( +
+ {records.map((record) => ( + + ))} +
+ )} +
+ ); +} diff --git a/src/components/editor/page-options-sidebar.tsx b/src/components/editor/page-options-sidebar.tsx new file mode 100644 index 00000000..ff400f97 --- /dev/null +++ b/src/components/editor/page-options-sidebar.tsx @@ -0,0 +1,228 @@ +"use client"; + +import { useState, type ComponentType } from "react"; +import { BookOpenCheck, Focus, ListOrdered, ListTree, Maximize2, ShieldCheck, Type } from "lucide-react"; +import { cn } from "@/lib/utils"; +import type { DocumentStats, PageOptionsState } from "@/types/page-options"; +import { DocumentTaskPanel } from "@/components/document-task-panel"; +import { Button } from "@/components/ui/button"; + +type TabId = "page" | "custom" | "global"; + +const TABS: Array<{ id: TabId; label: string }> = [ + { id: "page", label: "页面选项" }, + { id: "custom", label: "自定义页面" }, + { id: "global", label: "全局选项" }, +]; + +const OPTION_META: Record< + keyof PageOptionsState, + { label: string; description: string; icon: ComponentType<{ className?: string }> } +> = { + wideLayout: { + label: "自适应宽度", + description: "让编辑区域根据屏幕自动铺满", + icon: Maximize2, + }, + smallText: { + label: "小字体", + description: "使用更紧凑的字号排版", + icon: Type, + }, + showHeadingNumbers: { + label: "标题编号", + description: "自动为标题添加编号", + icon: ListOrdered, + }, + showToc: { + label: "显示目录", + description: "在右侧展示目录导航", + icon: ListTree, + }, + showStructure: { + label: "块结构线框", + description: "显示块级元素的结构边界", + icon: Focus, + }, + protectEditing: { + label: "编辑保护", + description: "保护内容避免误触修改", + icon: ShieldCheck, + }, + showWordCount: { + label: "字数提示", + description: "实时展示字数和块统计", + icon: BookOpenCheck, + }, +}; + +const CUSTOM_LAYOUT_OPTIONS: (keyof PageOptionsState)[] = ["wideLayout", "smallText"]; +const CUSTOM_STRUCTURE_OPTIONS: (keyof PageOptionsState)[] = ["showHeadingNumbers", "showToc"]; +const GLOBAL_OPTIONS: (keyof PageOptionsState)[] = ["showStructure", "protectEditing", "showWordCount"]; + +interface PageOptionsSidebarProps { + documentId: string; + options: PageOptionsState; + stats?: DocumentStats; + onToggle: (key: keyof PageOptionsState) => void; + onExport: () => void; + onOpenHistory: () => void; +} + +export function PageOptionsSidebar({ + documentId, + options, + stats, + onToggle, + onExport, + onOpenHistory, +}: PageOptionsSidebarProps) { + const [activeTab, setActiveTab] = useState("page"); + + return ( + + ); +} + +function OptionToggleGroup({ + title, + optionKeys, + options, + onToggle, +}: { + title: string; + optionKeys: (keyof PageOptionsState)[]; + options: PageOptionsState; + onToggle: (key: keyof PageOptionsState) => void; +}) { + return ( +
+
{title}
+
+ {optionKeys.map((key) => ( + + ))} +
+
+ ); +} + +function OptionToggle({ + optionKey, + options, + onToggle, +}: { + optionKey: keyof PageOptionsState; + options: PageOptionsState; + onToggle: (key: keyof PageOptionsState) => void; +}) { + const meta = OPTION_META[optionKey]; + const Icon = meta.icon; + const active = options[optionKey]; + + return ( + + ); +} + +function StatsCell({ label, value }: { label: string; value: number }) { + return ( +
+
{label}
+
{value}
+
+ ); +} diff --git a/src/components/editor/schema.ts b/src/components/editor/schema.ts new file mode 100644 index 00000000..113ce72a --- /dev/null +++ b/src/components/editor/schema.ts @@ -0,0 +1,36 @@ +"use client"; + +import { + BlockNoteSchema, + createHeadingBlockSpec, + defaultBlockSpecs, + defaultInlineContentSpecs, + defaultStyleSpecs, +} from "@blocknote/core"; +import { pageReferenceBlock } from "./blocks/PageReferenceBlock"; +import { advancedTodoBlock } from "./blocks/AdvancedTodoBlock"; +import { progressBlock } from "./blocks/ProgressBlock"; +import { mediaBlock } from "./blocks/MediaBlock"; + +const headingSpec = + typeof window === "undefined" + ? defaultBlockSpecs.heading + : createHeadingBlockSpec({ + levels: [1, 2, 3, 4, 5], + allowToggleHeadings: true, + }); + +export const customBlockSchema = BlockNoteSchema.create({ + blockSpecs: { + ...defaultBlockSpecs, + heading: headingSpec, + pageReference: pageReferenceBlock, + advancedTodo: advancedTodoBlock, + progressMeter: progressBlock, + media: mediaBlock, + }, + inlineContentSpecs: defaultInlineContentSpecs, + styleSpecs: defaultStyleSpecs, +}); + +export type CustomBlockSchema = typeof customBlockSchema.blockSchema; diff --git a/src/components/media/image-picker-context.tsx b/src/components/media/image-picker-context.tsx new file mode 100644 index 00000000..f511d2d0 --- /dev/null +++ b/src/components/media/image-picker-context.tsx @@ -0,0 +1,78 @@ +"use client"; + +import { createContext, useContext, useMemo, useState, type ReactNode } from "react"; +import { ImagePickerDialog, type PickerTab } from "@/components/media/image-picker-dialog"; +import type { MediaKind, MediaSelection } from "@/types/media"; + +interface PickerState { + open: boolean; + defaultTab: PickerTab; + mediaType: MediaKind; + onSelect?: (selection: MediaSelection) => void; +} + +interface ImagePickerContextValue { + openPicker: (options: { onSelect: (selection: MediaSelection) => void; defaultTab?: PickerTab; mediaType?: MediaKind }) => void; +} + +const ImagePickerContext = createContext(undefined); + +interface ImagePickerProviderProps { + documentId: string; + workspaceId: string; + children: ReactNode; +} + +export function ImagePickerProvider({ documentId, workspaceId, children }: ImagePickerProviderProps) { + const [state, setState] = useState({ + open: false, + defaultTab: "upload", + mediaType: "image", + }); + + const contextValue = useMemo( + () => ({ + openPicker: ({ onSelect, defaultTab = "upload", mediaType = "image" }) => { + setState({ + open: true, + onSelect, + defaultTab, + mediaType, + }); + }, + }), + [], + ); + + const handleClose = () => { + setState((prev) => ({ ...prev, open: false })); + }; + + const handleSelect = (selection: MediaSelection) => { + state.onSelect?.(selection); + setState((prev) => ({ ...prev, open: false })); + }; + + return ( + + {children} + + + ); +} + +export function useImagePicker() { + const context = useContext(ImagePickerContext); + if (!context) { + throw new Error("useImagePicker 必须在 ImagePickerProvider 中使用"); + } + return context; +} diff --git a/src/components/media/image-picker-dialog.tsx b/src/components/media/image-picker-dialog.tsx new file mode 100644 index 00000000..6fff4888 --- /dev/null +++ b/src/components/media/image-picker-dialog.tsx @@ -0,0 +1,392 @@ +"use client"; + +import { useCallback, useEffect, useMemo, useState } from "react"; +import { useDropzone } from "react-dropzone"; +import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { cn } from "@/lib/utils"; +import type { MediaAsset, MediaKind, MediaSelection } from "@/types/media"; + +export type PickerTab = "upload" | "recent" | "link" | "icon"; + +interface ImagePickerDialogProps { + open: boolean; + defaultTab: PickerTab; + mediaType: MediaKind; + documentId: string; + workspaceId: string; + onClose: () => void; + onSelect: (selection: MediaSelection) => void; +} + +const ICON_PRESETS: Array<{ id: string; label: string; url: string }> = [ + { id: "progress", label: "进度波纹", url: "https://media.giphy.com/media/QBd2kLB5qDmysEXre9/giphy.gif" }, + { id: "heartbeat", label: "心跳", url: "https://media.giphy.com/media/HEPwfdu6T6svpPE1eN/giphy.gif" }, + { id: "sparkles", label: "闪光", url: "https://media.giphy.com/media/f9jQLaPmuFKfa7E55Q/giphy.gif" }, + { id: "loading", label: "加载", url: "https://media.giphy.com/media/52qtwCtj9OLTi/giphy.gif" }, +]; + +const MEDIA_TABS: Record = { + image: ["upload", "recent", "link", "icon"], + video: ["upload", "recent", "link"], + audio: ["upload", "recent", "link"], + file: ["upload", "recent", "link"], +}; + +const TAB_LABELS: Record = { + upload: "上传", + recent: "最近", + link: "链接", + icon: "动态图标", +}; + +const MEDIA_ACCEPTS: Record | null> = { + image: { "image/*": [] }, + video: { "video/*": [] }, + audio: { "audio/*": [] }, + file: null, +}; + +const MEDIA_TITLES: Record = { + image: "选择图片", + video: "选择视频", + audio: "选择音频", + file: "选择文件", +}; + +const formatBytes = (size?: number | null) => { + if (!size || size <= 0) { + return ""; + } + const units = ["B", "KB", "MB", "GB", "TB"]; + let value = size; + let index = 0; + while (value >= 1024 && index < units.length - 1) { + value /= 1024; + index += 1; + } + return `${value.toFixed(value >= 10 ? 0 : 1)} ${units[index]}`; +}; + +const guessFileNameFromUrl = (url: string) => { + try { + const parsed = new URL(url); + const name = parsed.pathname.split("/").filter(Boolean).pop(); + if (name) { + return decodeURIComponent(name); + } + } catch { + const segments = url.split("?")[0]?.split("/") ?? []; + const name = segments.pop(); + if (name) { + return decodeURIComponent(name); + } + } + return "未命名资源"; +}; + +export function ImagePickerDialog({ + open, + defaultTab, + mediaType, + documentId, + workspaceId, + onClose, + onSelect, +}: ImagePickerDialogProps) { + const [tab, setTab] = useState(defaultTab); + const availableTabs = MEDIA_TABS[mediaType]; + const [recent, setRecent] = useState([]); + const [recentLoading, setRecentLoading] = useState(false); + const [linkUrl, setLinkUrl] = useState(""); + const [uploading, setUploading] = useState(false); + const [error, setError] = useState(null); + + useEffect(() => { + if (open) { + const availableTabs = MEDIA_TABS[mediaType]; + const fallback = availableTabs.includes(defaultTab) ? defaultTab : availableTabs[0]; + setTab(fallback); + } + }, [defaultTab, mediaType, open]); + + const fetchRecent = useCallback(async () => { + if (!workspaceId) { + return; + } + setRecentLoading(true); + try { + const response = await fetch( + `/api/media/assets?workspaceId=${workspaceId}&assetType=${mediaType}`, + ); + if (!response.ok) { + throw new Error("无法获取最近上传"); + } + const payload = (await response.json()) as { items: MediaAsset[] }; + setRecent(payload.items ?? []); + } catch (err) { + console.error(err); + setRecent([]); + } finally { + setRecentLoading(false); + } + }, [workspaceId, mediaType]); + + useEffect(() => { + if (open && tab === "recent") { + void fetchRecent(); + } + }, [open, tab, fetchRecent]); + + const handleUpload = useCallback( + async (file: File) => { + if (!workspaceId || !documentId) { + setError("缺少必要参数"); + return; + } + setUploading(true); + setError(null); + try { + const form = new FormData(); + form.append("file", file); + form.append("workspaceId", workspaceId); + form.append("documentId", documentId); + const response = await fetch("/api/media/upload", { + method: "POST", + body: form, + }); + if (!response.ok) { + const payload = await response.json().catch(() => null); + throw new Error(payload?.error ?? "上传失败"); + } + const payload = (await response.json()) as { asset: MediaAsset }; + if (!payload.asset?.file_url) { + throw new Error("返回数据缺少文件地址"); + } + onSelect({ + assetId: payload.asset.id, + fileUrl: payload.asset.file_url, + thumbnailUrl: payload.asset.thumbnail_url, + assetType: payload.asset.asset_type, + fileName: payload.asset.file_name, + fileSize: payload.asset.file_size, + mimeType: payload.asset.mime_type, + }); + onClose(); + } catch (err) { + setError((err as Error).message); + } finally { + setUploading(false); + } + }, + [documentId, onClose, onSelect, workspaceId], + ); + + const onDrop = useCallback( + (acceptedFiles: File[]) => { + if (acceptedFiles.length === 0) return; + void handleUpload(acceptedFiles[0]); + }, + [handleUpload], + ); + + const acceptConfig = MEDIA_ACCEPTS[mediaType]; + + const { getRootProps, getInputProps, isDragActive } = useDropzone({ + onDrop, + accept: acceptConfig ?? undefined, + maxFiles: 1, + }); + + const handleLinkSubmit = async () => { + if (!linkUrl.trim()) return; + try { + const targetUrl = linkUrl.trim(); + const response = await fetch("/api/media/assets", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + workspaceId, + documentId, + fileUrl: targetUrl, + thumbnailUrl: targetUrl, + assetType: mediaType, + fileName: guessFileNameFromUrl(targetUrl), + }), + }); + if (!response.ok) { + const payload = await response.json().catch(() => null); + throw new Error(payload?.error ?? "保存失败"); + } + const payload = (await response.json()) as { asset: MediaAsset }; + if (!payload.asset?.file_url) { + throw new Error("缺少图片地址"); + } + onSelect({ + assetId: payload.asset.id, + fileUrl: payload.asset.file_url, + thumbnailUrl: payload.asset.thumbnail_url, + assetType: payload.asset.asset_type, + fileName: payload.asset.file_name, + fileSize: payload.asset.file_size, + mimeType: payload.asset.mime_type, + }); + setLinkUrl(""); + onClose(); + } catch (err) { + setError((err as Error).message); + } + }; + + const renderRecent = () => { + if (recentLoading) { + return
加载最近上传中...
; + } + if (recent.length === 0) { + return
暂无最近上传的{MEDIA_TITLES[mediaType].replace("选择", "")}
; + } + return ( +
+ {recent.map((asset) => ( + + ))} +
+ ); + }; + + const renderIcons = () => ( +
+ {ICON_PRESETS.map((icon) => ( + + ))} +
+ ); + + const helperText = useMemo(() => { + const label = MEDIA_TITLES[mediaType].replace("选择", ""); + switch (tab) { + case "upload": + return `支持拖拽或点击上传${label},单个文件不超过 200MB`; + case "recent": + return `最近 12 个${label},支持一键选取`; + case "link": + return `输入${label}直链或 CDN 地址`; + case "icon": + return "选择 Wolai 常用动态图标"; + default: + return ""; + } + }, [mediaType, tab]); + + return ( + !next && onClose()}> + + + {MEDIA_TITLES[mediaType]} + {helperText} + + {error &&
{error}
} + setTab(value as PickerTab)}> + + {availableTabs.map((tabKey) => ( + + {TAB_LABELS[tabKey]} + + ))} + + {availableTabs.includes("upload") && ( + +
+ + {uploading ? "上传中..." : "拖拽文件到此处,或点击选择文件"} +
+
+ )} + {availableTabs.includes("recent") && ( + +
+ 最近上传的{MEDIA_TITLES[mediaType].replace("选择", "")} + +
+ {renderRecent()} +
+ )} + {availableTabs.includes("link") && ( + +
+ setLinkUrl(event.target.value)} + placeholder={`输入${MEDIA_TITLES[mediaType].replace("选择", "")}链接,例如 https://example.com/file`} + /> + +
+
+ )} + {availableTabs.includes("icon") && {renderIcons()}} +
+
+
+ ); +} diff --git a/src/components/mindmap/.gitkeep b/src/components/mindmap/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/src/components/mobile-sidebar-trigger.tsx b/src/components/mobile-sidebar-trigger.tsx new file mode 100644 index 00000000..35e41663 --- /dev/null +++ b/src/components/mobile-sidebar-trigger.tsx @@ -0,0 +1,19 @@ +"use client"; + +import { Menu } from "lucide-react"; +import { useSidebarStore } from "@/store/sidebar"; + +export function MobileSidebarTrigger() { + const setOpen = useSidebarStore((state) => state.setOpen); + + return ( + + ); +} diff --git a/src/components/providers/query-provider.tsx b/src/components/providers/query-provider.tsx new file mode 100644 index 00000000..4f4fa61d --- /dev/null +++ b/src/components/providers/query-provider.tsx @@ -0,0 +1,31 @@ +"use client"; + +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; +import { useState, type ReactNode } from "react"; + +interface QueryProviderProps { + children: ReactNode; +} + +export function QueryProvider({ children }: QueryProviderProps) { + const [client] = useState( + () => + new QueryClient({ + defaultOptions: { + queries: { + refetchOnWindowFocus: false, + refetchOnReconnect: true, + retry: 1, + }, + }, + }), + ); + + return ( + + {children} + + + ); +} diff --git a/src/components/providers/supabase-provider.tsx b/src/components/providers/supabase-provider.tsx new file mode 100644 index 00000000..9b2d1199 --- /dev/null +++ b/src/components/providers/supabase-provider.tsx @@ -0,0 +1,18 @@ +"use client"; + +import type { Session } from "@supabase/supabase-js"; +import { SessionContextProvider } from "@supabase/auth-helpers-react"; +import { supabaseBrowser } from "@/lib/supabase/client"; + +interface SupabaseProviderProps { + session: Session | null; + children: React.ReactNode; +} + +export function SupabaseProvider({ session, children }: SupabaseProviderProps) { + return ( + + {children} + + ); +} diff --git a/src/components/reference/page-hover-card.tsx b/src/components/reference/page-hover-card.tsx new file mode 100644 index 00000000..dc153985 --- /dev/null +++ b/src/components/reference/page-hover-card.tsx @@ -0,0 +1,39 @@ +import { Button } from "@/components/ui/button"; +import type { DocumentSearchResult } from "@/types/search"; + +interface PageHoverCardProps { + result: DocumentSearchResult; + onPreview?: () => void; +} + +const formatRelative = (value: string | null) => { + if (!value) return "未知时间"; + const date = new Date(value); + return date.toLocaleString(); +}; + +export function PageHoverCard({ result, onPreview }: PageHoverCardProps) { + return ( +
+
+

{result.title || "无标题"}

+

+ 最近更新:{formatRelative(result.updatedAt)} · 创建:{formatRelative(result.createdAt)} +

+
+
+ +
+ ); +} diff --git a/src/components/search/search-palette.tsx b/src/components/search/search-palette.tsx new file mode 100644 index 00000000..d45d74bc --- /dev/null +++ b/src/components/search/search-palette.tsx @@ -0,0 +1,654 @@ +"use client"; + +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import type { DragEvent as ReactDragEvent } from "react"; +import { useRouter, useSelectedLayoutSegments } from "next/navigation"; +import { CalendarClock, ChevronsUpDown, Copy, Layers, Search } from "lucide-react"; +import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { HoverCard, HoverCardContent, HoverCardTrigger } from "@/components/ui/hover-card"; +import { cn } from "@/lib/utils"; +import { useSearchPaletteStore, type ReferenceInsertMode, type SearchPaletteMode } from "@/store/search-palette"; +import type { DocumentSearchResult, DocumentSearchRequest, DocumentSearchTimeRange } from "@/types/search"; +import { useDocumentSearch } from "@/hooks/use-document-search"; +import { useReferenceComposer } from "@/hooks/use-reference-composer"; +import { recordRecentPage } from "@/lib/search/record-recent"; +import { PageHoverCard } from "@/components/reference/page-hover-card"; +import { buildSearchRequest } from "@/lib/search/request"; +import { resolveSearchOpenMode } from "@/lib/search/shortcuts"; + +interface SearchPaletteProps { + workspaceId: string | null; +} + +const TIME_RANGE_LABEL: Record = { + any: "全部时间", + "7d": "最近 7 天", + "30d": "最近 30 天", +}; + +const formatDate = (value: string | null) => { + if (!value) return "未知时间"; + const date = new Date(value); + return date.toLocaleString(); +}; + +export function SearchPalette({ workspaceId }: SearchPaletteProps) { + const router = useRouter(); + const segments = useSelectedLayoutSegments(); + const activeDocumentId = segments?.[1] ?? null; + const inputRef = useRef(null); + const { + open, + mode, + query, + filters, + timeRange, + referenceMode, + alias, + recent, + setRecent, + openSearch, + openReference, + close, + setQuery, + toggleFilter, + setTimeRange, + setTimeField, + setCustomRange, + setReferenceMode, + setAlias, + rememberResult, + } = useSearchPaletteStore(); + const [highlightedIndex, setHighlightedIndex] = useState(0); + const { insertReference } = useReferenceComposer({ + workspaceId, + sourcePageId: activeDocumentId ?? null, + }); + const [resultTab, setResultTab] = useState<"all" | "recent">(recent.length > 0 ? "recent" : "all"); + const [referenceFiltersOpen, setReferenceFiltersOpen] = useState(mode === "reference"); + const updateCustomRange = useCallback( + (patch: { from?: string; to?: string }) => { + const next = { ...(filters.customRange ?? {}), ...patch }; + if (!next.from && !next.to) { + setCustomRange(null); + } else { + setCustomRange(next); + } + }, + [filters.customRange, setCustomRange], + ); + const clearCustomRange = useCallback(() => setCustomRange(null), [setCustomRange]); + + const requestPayload = useMemo( + () => + buildSearchRequest({ + workspaceId, + activeDocumentId, + query, + filters, + timeRange, + }), + [workspaceId, activeDocumentId, query, filters, timeRange], + ); + + const searchQueryEnabled = Boolean(open && workspaceId); + const { data, isLoading, isFetching, error } = useDocumentSearch(requestPayload, searchQueryEnabled); + + useEffect(() => { + if (data?.recent) { + setRecent(data.recent); + } + }, [data?.recent, setRecent]); + /* eslint-disable react-hooks/set-state-in-effect */ + useEffect(() => { + if (mode === "reference") { + setReferenceFiltersOpen(true); + } else { + setReferenceFiltersOpen(false); + } + }, [mode]); + /* eslint-enable react-hooks/set-state-in-effect */ + const searchResults = data?.results; + const remoteResults = useMemo(() => searchResults ?? [], [searchResults]); + const showRecent = !query.trim() && recent.length > 0; + const enableRecentTab = showRecent; + const effectiveTab = enableRecentTab ? resultTab : "all"; + const activeResults = effectiveTab === "recent" ? recent : remoteResults; + + useEffect(() => { + if (!open) { + return undefined; + } + inputRef.current?.focus(); + const frame = requestAnimationFrame(() => setHighlightedIndex(0)); + return () => cancelAnimationFrame(frame); + }, [open]); + + useEffect(() => { + if (highlightedIndex >= activeResults.length) { + const frame = requestAnimationFrame(() => + setHighlightedIndex(activeResults.length > 0 ? activeResults.length - 1 : 0), + ); + return () => cancelAnimationFrame(frame); + } + return undefined; + }, [activeResults.length, highlightedIndex]); + + useEffect(() => { + const handleGlobalHotkey = (event: KeyboardEvent) => { + if ((event.metaKey || event.ctrlKey) && !event.shiftKey && event.key.toLowerCase() === "p") { + event.preventDefault(); + openSearch(); + } + if ((event.metaKey || event.ctrlKey) && event.shiftKey && event.key.toLowerCase() === "r") { + event.preventDefault(); + openReference(); + } + }; + window.addEventListener("keydown", handleGlobalHotkey); + return () => window.removeEventListener("keydown", handleGlobalHotkey); + }, [openSearch, openReference]); + + const handleOpenResult = useCallback( + (result: DocumentSearchResult, openMode: "main" | "new-window" | "sidebar") => { + if (openMode === "new-window") { + window.open(result.publicPath, "_blank", "noopener,noreferrer"); + } else if (openMode === "sidebar") { + window.open(`${result.publicPath}?preview=sidebar`, "_blank", "noopener,noreferrer"); + } else { + router.push(result.publicPath); + } + void recordRecentPage(workspaceId, result.id); + rememberResult(result); + close(); + }, + [router, workspaceId, rememberResult, close], + ); + + const handleInsertReference = useCallback( + (result: DocumentSearchResult, overrideMode?: ReferenceInsertMode) => { + const run = async () => { + try { + const effectiveMode = overrideMode ?? referenceMode; + await insertReference(result, { + mode: effectiveMode, + alias: effectiveMode === "inline" ? alias : undefined, + }); + rememberResult(result); + close(); + } catch (error) { + console.error(error); + } + }; + void run(); + }, + [alias, close, insertReference, referenceMode, rememberResult], + ); + + const handleCopyReference = useCallback((result: DocumentSearchResult) => { + if (typeof window === "undefined") { + return; + } + const payload = `((${result.id}))`; + if (navigator?.clipboard) { + navigator.clipboard + .writeText(payload) + .then(() => window.alert("块引用已复制")) + .catch(() => { + window.prompt("复制失败,请手动复制引用内容", payload); + }); + } else { + window.prompt("复制块引用", payload); + } + }, []); + + useEffect(() => { + if (!open) { + return; + } + const handleKey = (event: KeyboardEvent) => { + if (event.key === "ArrowDown") { + event.preventDefault(); + setHighlightedIndex((prev) => Math.min(activeResults.length - 1, prev + 1)); + } else if (event.key === "ArrowUp") { + event.preventDefault(); + setHighlightedIndex((prev) => Math.max(0, prev - 1)); + } else if (event.key === "Enter") { + event.preventDefault(); + if (activeResults.length === 0) { + return; + } + const result = activeResults[highlightedIndex] ?? activeResults[0]; + if (!result) return; + if (mode === "search") { + const openMode = resolveSearchOpenMode(event); + handleOpenResult(result, openMode); + } else { + handleInsertReference(result); + } + } else if (event.key === "Escape") { + event.preventDefault(); + close(); + } + }; + window.addEventListener("keydown", handleKey); + return () => window.removeEventListener("keydown", handleKey); + }, [open, activeResults, highlightedIndex, mode, handleOpenResult, handleInsertReference, close]); + + const pending = isLoading || isFetching; + const dialogTitle = mode === "search" ? "页面搜索面板" : "引用选择面板"; + + return ( + !next && close()}> + + {dialogTitle} +
+
+
+
+ + { + const nextValue = event.target.value; + if (nextValue.trim().length > 0 && resultTab !== "all") { + setResultTab("all"); + } + setQuery(nextValue); + }} + placeholder={mode === "search" ? "搜索页面标题、正文或 OCR 内容..." : "选择要引用的页面"} + className="h-11 w-full rounded-xl border border-[#e2e8f0] bg-white pl-9 text-sm" + /> +
+ + + + + + {(Object.keys(TIME_RANGE_LABEL) as DocumentSearchTimeRange[]).map((range) => ( + setTimeRange(range)} + className={cn(range === timeRange && "bg-[#eef2ff] text-[#2563eb]")} + > + {TIME_RANGE_LABEL[range]} + + ))} + + + + + + + + setTimeField("updated")} + className={cn(filters.timeField === "updated" && "bg-[#eef2ff] text-[#2563eb]")} + > + 按编辑时间 + + setTimeField("created")} + className={cn(filters.timeField === "created" && "bg-[#eef2ff] text-[#2563eb]")} + > + 按创建时间 + + + +
+
+ toggleFilter("titleOnly")} /> + toggleFilter("exact")} /> + toggleFilter("onlyCurrentPage")} + /> + toggleFilter("includeOcr")} + /> + {mode === "reference" && ( + + )} +
+ {mode === "reference" && referenceFiltersOpen && ( +
+ setReferenceMode("inline")} + /> + setReferenceMode("embed")} + /> + {referenceMode === "inline" && ( + setAlias(event.target.value)} + placeholder="引用别名" + className="h-8 w-40 text-xs" + /> + )} + 引用模式将同步到 [[ / # 快捷键 +
+ )} +
+ 自定义时间 + updateCustomRange({ from: event.target.value || undefined })} + className="h-8 w-36 text-xs" + /> + updateCustomRange({ to: event.target.value || undefined })} + className="h-8 w-36 text-xs" + /> + +
+
+
+ {enableRecentTab && ( +
+ setResultTab("recent")} + disabled={!enableRecentTab} + /> + setResultTab("all")} /> +
+ )} + {pending ? ( +
+ 检索中,请稍候... +
+ ) : error ? ( +
+ {(error as Error).message} +
+ ) : activeResults.length === 0 ? ( +
+ {effectiveTab === "recent" ? "暂无最近访问记录" : "暂无匹配结果"} +
+ ) : ( +
+ {activeResults.map((result, index) => ( + handleOpenResult(result, "main")} + onReference={() => handleInsertReference(result)} + onEmbed={() => handleInsertReference(result, "embed")} + onCopy={() => handleCopyReference(result)} + /> + ))} +
+ )} +
+
+ {mode === "search" ? ( +
+ Enter 打开 · Ctrl/Cmd+Enter 新窗口 · Alt+Enter 右侧预览 + Esc 关闭 +
+ ) : ( +
+ + {referenceMode === "inline" ? "插入行内引用(链接高亮)" : "插入一个嵌入的页面块"},可先输入别名 + + Esc 关闭 +
+ )} +
+
+
+
+ ); +} + +function ResultRow({ + result, + highlighted, + mode, + onOpen, + onReference, + onEmbed, + onCopy, +}: { + result: DocumentSearchResult; + highlighted: boolean; + mode: SearchPaletteMode; + onOpen: () => void; + onReference: () => void; + onEmbed: () => void; + onCopy: () => void; +}) { + const handleActivate = () => { + if (mode === "search") { + onOpen(); + } else { + onReference(); + } + }; + + const handleDragStart = (event: ReactDragEvent) => { + if (mode !== "reference") return; + if (event.dataTransfer) { + event.dataTransfer.setData("text/plain", `((${result.id}))`); + event.dataTransfer.effectAllowed = "copy"; + } + }; + + const content = ( +
{ + if (event.key === "Enter") { + event.preventDefault(); + handleActivate(); + } + }} + className={cn( + "cursor-pointer px-4 py-3 transition-colors hover:bg-[#eef2ff]", + highlighted && "bg-[#e3ecff]", + )} + draggable={mode === "reference"} + onDragStart={handleDragStart} + > +
+ {result.title || "无标题"} + {result.matchField !== "recent" && ( + + {result.matchField === "title" ? "标题匹配" : "正文匹配"} + + )} + {result.hasOcr && ( + + OCR + + )} +
+
+
+ 最近编辑:{formatDate(result.updatedAt)} · 创建时间:{formatDate(result.createdAt)} +
+ {mode === "reference" && ( +
+ + + + 也可直接拖拽引用到编辑器 +
+ )} +
+ ); + + return ( + + {content} + + + + + ); +} + +interface FilterToggleProps { + label: string; + active: boolean; + onClick: () => void; + disabled?: boolean; +} + +function FilterToggle({ label, active, onClick, disabled }: FilterToggleProps) { + return ( + + ); +} + +interface TabButtonProps { + label: string; + active: boolean; + onClick: () => void; + disabled?: boolean; +} + +function TabButton({ label, active, onClick, disabled }: TabButtonProps) { + return ( + + ); +} + +interface ReferenceModeToggleProps { + label: string; + active: boolean; + onClick: () => void; +} + +function ReferenceModeToggle({ label, active, onClick }: ReferenceModeToggleProps) { + return ( + + ); +} diff --git a/src/components/sidebar/.gitkeep b/src/components/sidebar/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/src/components/sidebar/private-tree.tsx b/src/components/sidebar/private-tree.tsx new file mode 100644 index 00000000..cedd615b --- /dev/null +++ b/src/components/sidebar/private-tree.tsx @@ -0,0 +1,257 @@ +"use client"; + +import Link from "next/link"; +import { useCallback, useMemo, useRef, useState } from "react"; +import { + DndContext, + PointerSensor, + closestCenter, + useSensor, + useSensors, + type DragEndEvent, + type DragStartEvent, +} from "@dnd-kit/core"; +import { SortableContext, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable"; +import { CSS } from "@dnd-kit/utilities"; +import { useVirtualizer, type VirtualItem } from "@tanstack/react-virtual"; +import { ChevronRight, GripVertical, MoreHorizontal, Plus } from "lucide-react"; +import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"; +import type { DocumentNode } from "@/lib/documents"; +import { cn } from "@/lib/utils"; +import { flattenDocumentTree } from "@/lib/sidebar-tree"; + +interface PrivateTreeProps { + nodes: DocumentNode[]; + expanded: Set; + activeId: string; + onToggleExpand: (id: string) => void; + onMove: (nodeId: string, parentId: string | null, index: number) => void; + onCreateChild: (parentId: string | null) => void; + onContextMenu: (event: React.MouseEvent, node: DocumentNode) => void; +} + +const ROW_HEIGHT = 36; + +export function PrivateTree({ + nodes, + expanded, + activeId, + onToggleExpand, + onMove, + onCreateChild, + onContextMenu, +}: PrivateTreeProps) { + const scrollAreaRef = useRef(null); + const [activeDragId, setActiveDragId] = useState(null); + + const flatNodes = useMemo(() => flattenDocumentTree(nodes, expanded), [nodes, expanded]); + + const sensors = useSensors( + useSensor(PointerSensor, { + activationConstraint: { distance: 5 }, + }), + ); + + // eslint-disable-next-line react-hooks/incompatible-library + const virtualizer = useVirtualizer({ + count: flatNodes.length, + getScrollElement: () => scrollAreaRef.current, + estimateSize: () => ROW_HEIGHT, + overscan: 10, + }); + + const handleDragStart = useCallback((event: DragStartEvent) => { + setActiveDragId(event.active.id as string); + }, []); + + const handleDragEnd = useCallback( + (event: DragEndEvent) => { + const activeId = event.active.id as string; + const overId = event.over?.id as string | undefined; + setActiveDragId(null); + if (!overId || activeId === overId) { + return; + } + const activeIndex = flatNodes.findIndex((item) => item.node.id === activeId); + const overIndex = flatNodes.findIndex((item) => item.node.id === overId); + if (activeIndex === -1 || overIndex === -1) { + return; + } + const targetParent = flatNodes[overIndex].parentId; + const siblingList = flatNodes.filter((item) => item.parentId === targetParent); + const siblingIndex = siblingList.findIndex((item) => item.node.id === overId); + const position = siblingIndex === -1 ? siblingList.length : siblingIndex; + onMove(activeId, targetParent, position); + }, + [flatNodes, onMove], + ); + + const handleDragCancel = useCallback(() => { + setActiveDragId(null); + }, []); + + if (flatNodes.length === 0) { + return
暂无页面,点击下方按钮创建
; + } + + return ( + + + + item.node.id)} strategy={verticalListSortingStrategy}> +
+
+ {virtualizer.getVirtualItems().map((virtualRow) => { + const item = flatNodes[virtualRow.index]; + return ( + + 0} + activeId={activeId} + isDragging={activeDragId === item.node.id} + onToggleExpand={() => onToggleExpand(item.node.id)} + onCreateChild={() => onCreateChild(item.node.id)} + onContextMenu={(event) => onContextMenu(event, item.node)} + /> + + ); + })} +
+
+
+
+
+ + + + +
+ ); +} + +function VirtualRow({ + children, + virtualRow, +}: { + children: React.ReactNode; + virtualRow: VirtualItem; +}) { + return ( +
+ {children} +
+ ); +} + +interface SortableTreeRowProps { + node: DocumentNode; + depth: number; + expanded: boolean; + hasChildren: boolean; + activeId: string; + isDragging: boolean; + onToggleExpand: () => void; + onCreateChild: () => void; + onContextMenu: (event: React.MouseEvent) => void; +} + +function SortableTreeRow({ + node, + depth, + expanded, + hasChildren, + activeId, + isDragging, + onToggleExpand, + onCreateChild, + onContextMenu, +}: SortableTreeRowProps) { + const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ + id: node.id, + }); + + const style = { + transform: CSS.Translate.toString(transform), + transition, + }; + + return ( +
+ +
+ {hasChildren ? ( + + ) : ( + + )} + + {node.title || "无标题"} + +
+ + +
+ ); +} diff --git a/src/components/sidebar/sidebar.tsx b/src/components/sidebar/sidebar.tsx new file mode 100644 index 00000000..eb8b42fb --- /dev/null +++ b/src/components/sidebar/sidebar.tsx @@ -0,0 +1,1077 @@ +"use client"; + +import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react"; +import Link from "next/link"; +import { useRouter, useSelectedLayoutSegments } from "next/navigation"; +import { + ArrowRightLeft, + ArrowUpRight, + Bell, + ChevronRight, + Copy, + Dice5, + Edit3, + GitMerge, + Globe, + Hash, + LayoutGrid, + Library, + Link as LinkIcon, + MoreHorizontal, + PanelRightOpen, + PenSquare, + Plus, + Search as SearchIcon, + Share2, + Shield, + Star, + Trash2, + Upload, + Users, +} from "lucide-react"; +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; +import { Drawer, DrawerContent, DrawerHeader, DrawerTitle } from "@/components/ui/drawer"; +import { cn } from "@/lib/utils"; +import type { DocumentNode } from "@/lib/documents"; +import { buildDocumentTree } from "@/lib/documents"; +import { useSidebarStore } from "@/store/sidebar"; +import type { SidebarInitialData, SidebarSectionId } from "@/components/sidebar/types"; +import { useSidebarData } from "@/hooks/use-sidebar-data"; +import { PrivateTree } from "@/components/sidebar/private-tree"; +import { buildSidebarSectionsFromTree, flattenDocumentTree } from "@/lib/sidebar-tree"; +import { supabaseBrowser } from "@/lib/supabase/client"; +import { useSearchPaletteStore } from "@/store/search-palette"; + +const TOP_BUTTONS = [ + { id: "search", icon: SearchIcon, label: "搜索" }, + { id: "graph", icon: Share2, label: "关系图" }, + { id: "import", icon: Upload, label: "导入" }, + { id: "members", icon: Users, label: "成员" }, + { id: "inbox", icon: Bell, label: "消息箱" }, + { id: "quick-note", icon: PenSquare, label: "今日速记" }, + { id: "lucky", icon: Dice5, label: "手气不错" }, + { id: "more", icon: MoreHorizontal, label: "更多" }, +] as const; + +const SECTION_ICONS: Record, React.ReactNode> = { + starred: , + public: , + shared: , + templates: , +}; + +interface SidebarProps { + initialData: SidebarInitialData; +} + +interface ContextMenuState { + node: DocumentNode; + x: number; + y: number; +} + +export function Sidebar({ initialData }: SidebarProps) { + const { open, setOpen, width, setWidth, collapsedSections, toggleSection, trashConfirm, setTrashConfirm } = + useSidebarStore(); + const openSearchPalette = useSearchPaletteStore((state) => state.openSearch); + const sidebarQuery = useSidebarData(initialData); + const sidebarData = sidebarQuery.data ?? initialData; + const segments = useSelectedLayoutSegments(); + const router = useRouter(); + const activeId = segments?.[1] ?? ""; + + const [tree, setTree] = useState(() => buildDocumentTree(sidebarData.documents)); + const [filter, setFilter] = useState(""); + const [expanded, setExpanded] = useState>(() => collectNodeIds(tree)); + const [contextMenu, setContextMenu] = useState(null); + const [workspaceMenuOpen, setWorkspaceMenuOpen] = useState(false); + const [trashOpen, setTrashOpen] = useState(false); + const [trashSearch, setTrashSearch] = useState(""); + const [emptyingTrash, setEmptyingTrash] = useState(false); + + const workspaceMenuRef = useRef(null); + + useEffect(() => { + setTree(() => { + const nextTree = buildDocumentTree(sidebarData.documents); + setExpanded((expandedPrev) => collectNodeIds(nextTree, new Set(expandedPrev))); + return nextTree; + }); + }, [sidebarData.documents]); + + useEffect(() => { + setOpen(false); + }, [activeId, setOpen]); + + const refreshTree = useCallback(async () => { + await sidebarQuery.refetch(); + }, [sidebarQuery]); + + useEffect(() => { + const channel = supabaseBrowser + .channel("documents-feed") + .on( + "postgres_changes", + { event: "*", schema: "public", table: "documents" }, + () => { + void refreshTree(); + }, + ) + .subscribe(); + return () => { + supabaseBrowser.removeChannel(channel); + }; + }, [refreshTree]); + + const sections = useMemo(() => buildSidebarSectionsFromTree(tree), [tree]); + const starredNodes = useMemo(() => sections.find((section) => section.id === "starred")?.nodes ?? [], [sections]); + const publicNodes = useMemo(() => sections.find((section) => section.id === "public")?.nodes ?? [], [sections]); + const sharedNodes = useMemo(() => sections.find((section) => section.id === "shared")?.nodes ?? [], [sections]); + const templateNodes = useMemo(() => sections.find((section) => section.id === "templates")?.nodes ?? [], [sections]); + const privateTree = useMemo(() => sections.find((section) => section.id === "private")?.nodes ?? [], [sections]); + const filteredPrivateTree = useMemo( + () => (filter ? filterTree(privateTree, filter.toLowerCase()) : privateTree), + [filter, privateTree], + ); + const flattenedPrivate = useMemo( + () => flattenDocumentTree(privateTree, expanded), + [privateTree, expanded], + ); + const filteredTrash = useMemo(() => { + const keyword = trashSearch.trim().toLowerCase(); + if (!keyword) { + return sidebarData.trashedDocuments; + } + return sidebarData.trashedDocuments.filter((item) => + (item.title ?? "无标题").toLowerCase().includes(keyword), + ); + }, [sidebarData.trashedDocuments, trashSearch]); + + const activeWorkspace = + sidebarData.workspaces.find((workspace) => workspace.id === sidebarData.activeWorkspaceId) ?? + sidebarData.workspaces[0]; + + const handleOpenDocument = useCallback( + (documentId: string, mode: "main" | "sidebar") => { + const targetPath = `/documents/${documentId}`; + if (mode === "main") { + router.push(targetPath); + setOpen(false); + return; + } + if (typeof window !== "undefined") { + const sidebarUrl = `${buildDocumentUrl(documentId)}?preview=sidebar`; + window.open(sidebarUrl, "_blank", "noopener,noreferrer"); + } + }, + [router, setOpen], + ); + + const handleCopyLink = useCallback(async (node: DocumentNode, includeTitle = false) => { + const url = buildDocumentUrl(node.id); + const payload = includeTitle ? `${node.title ?? "无标题"}\n${url}` : url; + await copyText(payload, includeTitle ? "标题 + 链接已复制" : "页面链接已复制"); + }, []); + + const handleCopyReference = useCallback(async (node: DocumentNode, mode: "inline" | "embed") => { + const template = mode === "inline" ? `((${node.id}))` : `{{${node.id}}}`; + await copyText(template, mode === "inline" ? "行内引用已复制" : "嵌入引用已复制"); + }, []); + + const handleCopyId = useCallback(async (node: DocumentNode) => { + await copyText(node.id, "页面 ID 已复制"); + }, []); + + const handleDuplicateDocument = useCallback( + async (node: DocumentNode) => { + const response = await fetch("/api/documents/duplicate", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ documentId: node.id }), + }); + if (!response.ok) { + window.alert("复制失败,请稍后再试"); + return; + } + await refreshTree(); + }, + [refreshTree], + ); + + const handleEmbedPrompt = useCallback(async (node: DocumentNode) => { + if (typeof window === "undefined") { + return; + } + const target = window.prompt("输入希望嵌入到的页面 ID"); + if (!target) { + return; + } + const response = await fetch("/api/documents/embed", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ sourceId: node.id, targetId: target.trim() }), + }); + if (!response.ok) { + window.alert("嵌入失败,请检查目标页面 ID"); + return; + } + window.alert("已在目标页面末尾插入引用块"); + }, []); + + const toggleExpand = useCallback((id: string) => { + setExpanded((prev) => { + const next = new Set(prev); + if (next.has(id)) { + next.delete(id); + } else { + next.add(id); + } + return next; + }); + }, []); + + const handleResizeStart = useCallback( + (event: React.MouseEvent) => { + event.preventDefault(); + const startX = event.clientX; + const startWidth = width; + const onMove = (moveEvent: MouseEvent) => { + const delta = moveEvent.clientX - startX; + const targetWidth = Math.min(420, Math.max(240, startWidth + delta)); + setWidth(targetWidth); + }; + const onUp = () => { + document.removeEventListener("mousemove", onMove); + document.removeEventListener("mouseup", onUp); + }; + document.addEventListener("mousemove", onMove); + document.addEventListener("mouseup", onUp); + }, + [setWidth, width], + ); + + const handleCreate = useCallback( + async (parentId: string | null) => { + const response = await fetch("/api/documents/create", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ parentId }), + }); + + if (!response.ok) { + const payload = await response.json().catch(() => ({})); + window.alert(payload?.error ?? "新建页面失败,请稍后再试"); + return; + } + + const payload = (await response.json()) as DocumentNode; + const nextNode: DocumentNode = { + ...payload, + access_scope: payload.access_scope ?? "private", + is_template: payload.is_template ?? false, + updated_at: payload.updated_at ?? payload.created_at, + title: payload.title ?? "无标题", + children: [], + }; + + setTree((prev) => insertNode(prev, parentId, Number.MAX_SAFE_INTEGER, nextNode)); + setExpanded((prev) => { + const next = new Set(prev); + if (parentId) { + next.add(parentId); + } + if (!parentId) { + next.add(nextNode.id); + } + return next; + }); + + await refreshTree(); + router.push(`/documents/${nextNode.id}`); + }, + [refreshTree, router], + ); + + const handleRename = useCallback( + async (documentId: string, currentTitle: string | null) => { + const title = window.prompt("输入新的标题", currentTitle ?? "无标题") ?? ""; + if (!title.trim()) { + return; + } + await fetch("/api/documents/title", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ documentId, title: title.trim() }), + }); + await refreshTree(); + }, + [refreshTree], + ); + + const moveLocalNode = useCallback((currentTree: DocumentNode[], nodeId: string, parentId: string | null, index: number) => { + const cloned = cloneNodes(currentTree); + const { removed, tree: withoutTarget } = removeNode(cloned, nodeId); + if (!removed) { + return currentTree; + } + const next = insertNode(withoutTarget, parentId, index, removed); + return next; + }, []); + + const handleMove = useCallback( + async (documentId: string, parentId: string | null, index: number) => { + setTree((prev) => moveLocalNode(prev, documentId, parentId, index)); + if (parentId) { + setExpanded((prev) => new Set(prev).add(parentId)); + } + await fetch("/api/documents/move", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + documentId, + parentId, + position: index, + }), + }); + await refreshTree(); + }, + [moveLocalNode, refreshTree, setExpanded], + ); + + const handleMovePrompt = useCallback( + async (node: DocumentNode) => { + if (typeof window === "undefined") { + return; + } + const target = window.prompt("输入目标父页面 ID(留空表示移动到根目录)", node.parent_id ?? ""); + if (target === null) { + return; + } + const trimmed = target.trim(); + await handleMove(node.id, trimmed.length > 0 ? trimmed : null, 0); + }, + [handleMove], + ); + + const handleDelete = useCallback( + async (documentId: string) => { + await fetch("/api/documents/delete", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ documentId }), + }); + await refreshTree(); + }, + [refreshTree], + ); + + const handleConvertToChild = useCallback( + async (documentId: string) => { + const flat = flattenedPrivate; + const currentIndex = flat.findIndex((item) => item.node.id === documentId); + if (currentIndex <= 0) { + return; + } + const targetParentId = flat[currentIndex - 1].node.id; + await handleMove(documentId, targetParentId, flat[currentIndex - 1].node.children.length); + }, + [flattenedPrivate, handleMove], + ); + + const confirmTrashAction = useCallback( + (message: string) => { + if (!trashConfirm) { + return true; + } + return window.confirm(message); + }, + [trashConfirm], + ); + + const handleRestoreFromTrash = useCallback( + async (documentId: string) => { + if (!confirmTrashAction("确认恢复该页面吗?")) { + return; + } + await fetch("/api/documents/restore", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ documentId }), + }); + await Promise.all([sidebarQuery.refetch(), refreshTree()]); + }, + [confirmTrashAction, refreshTree, sidebarQuery], + ); + + const handlePurgeFromTrash = useCallback( + async (documentId: string) => { + if (!confirmTrashAction("彻底删除后将无法找回,是否继续?")) { + return; + } + await fetch("/api/documents/purge", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ documentId }), + }); + await Promise.all([sidebarQuery.refetch(), refreshTree()]); + }, + [confirmTrashAction, refreshTree, sidebarQuery], + ); + + const handleEmptyTrash = useCallback(async () => { + if (!sidebarData.activeWorkspaceId) { + window.alert("暂无可清空的工作空间"); + return; + } + if (!confirmTrashAction("清空垃圾桶后将无法恢复,是否继续?")) { + return; + } + setEmptyingTrash(true); + try { + const response = await fetch("/api/documents/empty-trash", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ workspaceId: sidebarData.activeWorkspaceId }), + }); + if (!response.ok) { + const payload = await response.json().catch(() => ({})); + window.alert(payload?.error ?? "清空垃圾桶失败,请稍后再试"); + return; + } + await Promise.all([sidebarQuery.refetch(), refreshTree()]); + } finally { + setEmptyingTrash(false); + } + }, [confirmTrashAction, refreshTree, sidebarData.activeWorkspaceId, sidebarQuery]); + + const handleWorkspaceSwitch = useCallback( + async (workspaceId: string) => { + if (workspaceId === sidebarData.activeWorkspaceId) { + setWorkspaceMenuOpen(false); + return; + } + await fetch("/api/workspaces/switch", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ workspaceId }), + }); + setWorkspaceMenuOpen(false); + await sidebarQuery.refetch(); + }, + [sidebarData.activeWorkspaceId, sidebarQuery], + ); + + const openContextMenu = useCallback((event: React.MouseEvent, node: DocumentNode) => { + event.preventDefault(); + event.stopPropagation(); + setContextMenu({ + node, + x: event.clientX, + y: event.clientY, + }); + }, []); + + const handleTopButtonClick = useCallback( + (buttonId: (typeof TOP_BUTTONS)[number]["id"]) => { + if (buttonId === "search") { + openSearchPalette(); + return; + } + window.alert("该功能即将上线,敬请期待"); + }, + [openSearchPalette], + ); + + useEffect(() => { + if (!contextMenu) { + return; + } + const closeMenu = () => setContextMenu(null); + window.addEventListener("click", closeMenu); + return () => window.removeEventListener("click", closeMenu); + }, [contextMenu]); + + useEffect(() => { + if (!workspaceMenuOpen) { + return; + } + const handleClickOutside = (event: MouseEvent) => { + if ( + workspaceMenuRef.current && + !workspaceMenuRef.current.contains(event.target as Node) + ) { + setWorkspaceMenuOpen(false); + } + }; + window.addEventListener("click", handleClickOutside); + return () => window.removeEventListener("click", handleClickOutside); + }, [workspaceMenuOpen]); + + const sidebarBody = ( +
+
+
当前工作空间
+
+
+
{activeWorkspace?.name ?? "我的空间"}
+
{activeWorkspace?.type === "team" ? "团队空间" : "个人空间"}
+
+
+ + {workspaceMenuOpen && ( +
+ {sidebarData.workspaces.map((workspace) => ( + + ))} +
+ )} +
+
+
+ +
+ {TOP_BUTTONS.map((button) => ( + + ))} +
+ +
+
+
+
+ + 页面树 +
+ setFilter(event.target.value)} + /> +
+ + toggleSection("starred")} + /> + toggleSection("public")} + /> + toggleSection("shared")} + /> + toggleSection("templates")} + /> + +
+ + {!collapsedSections.private ? ( +
+
+ +
+
+ ) : ( +
已折叠
+ )} +
+ +
+ + +
+
+
+
+ ); + + return ( + <> + + + + + 页面目录 + +
{sidebarBody}
+
+
+ {contextMenu && ( + setContextMenu(null)} + onOpenRight={(node) => handleOpenDocument(node.id, "sidebar")} + onMove={handleMovePrompt} + onEmbed={handleEmbedPrompt} + onCopyLink={handleCopyLink} + onCopyReference={handleCopyReference} + onCopyId={handleCopyId} + onDuplicate={handleDuplicateDocument} + onRename={() => void handleRename(contextMenu.node.id, contextMenu.node.title)} + onCreateChild={() => void handleCreate(contextMenu.node.id)} + onConvertChild={() => void handleConvertToChild(contextMenu.node.id)} + onDelete={() => void handleDelete(contextMenu.node.id)} + /> + )} + + + + 垃圾桶 +

180 天内的记录都可以在这里恢复。

+
+
+ setTrashSearch(event.target.value)} + /> +
+ + +
+
+ {filteredTrash.length === 0 ? ( +
暂无已删除页面
+ ) : ( + filteredTrash.map((item) => ( +
+
+
{item.title || "无标题"}
+
+ 删除时间:{new Date(item.deleted_at).toLocaleString()} +
+
+
+ + +
+
+ )) + )} +
+
+
+
+ + ); +} + +interface SectionListProps { + label: string; + icon: React.ReactNode; + nodes: DocumentNode[]; + collapsed: boolean; + onToggle: () => void; +} + +function SectionList({ label, icon, nodes, collapsed, onToggle }: SectionListProps) { + return ( +
+ + {!collapsed && ( +
+ {nodes.length === 0 ? ( +
暂无内容
+ ) : ( + nodes.map((node) => ( + + {node.title || "无标题"} + + )) + )} +
+ )} +
+ ); +} + +interface ContextMenuProps { + contextMenu: ContextMenuState; + onClose: () => void; + onOpenRight: (node: DocumentNode) => void; + onMove: (node: DocumentNode) => void; + onEmbed: (node: DocumentNode) => void; + onCopyLink: (node: DocumentNode, withTitle?: boolean) => void; + onCopyReference: (node: DocumentNode, mode: "inline" | "embed") => void; + onCopyId: (node: DocumentNode) => void; + onDuplicate: (node: DocumentNode) => void; + onRename: () => void; + onCreateChild: () => void; + onConvertChild: () => void; + onDelete: () => void; +} + +function ContextMenu({ + contextMenu, + onClose, + onOpenRight, + onMove, + onEmbed, + onCopyLink, + onCopyReference, + onCopyId, + onDuplicate, + onRename, + onCreateChild, + onConvertChild, + onDelete, +}: ContextMenuProps) { + const { node } = contextMenu; + const menuRef = useRef(null); + const [position, setPosition] = useState({ top: contextMenu.y, left: contextMenu.x }); + + useLayoutEffect(() => { + const clampPosition = () => { + const element = menuRef.current; + if (!element) { + setPosition({ top: contextMenu.y, left: contextMenu.x }); + return; + } + const rect = element.getBoundingClientRect(); + const padding = 12; + const maxLeft = Math.max(padding, window.innerWidth - rect.width - padding); + const maxTop = Math.max(padding, window.innerHeight - rect.height - padding); + const left = Math.min(Math.max(padding, contextMenu.x), maxLeft); + const top = Math.min(Math.max(padding, contextMenu.y), maxTop); + setPosition({ top, left }); + }; + clampPosition(); + window.addEventListener("resize", clampPosition); + return () => window.removeEventListener("resize", clampPosition); + }, [contextMenu]); + + const handleAction = (action: () => void) => { + action(); + onClose(); + }; + + const buttonClass = + "flex w-full items-center gap-2 rounded-sm px-3 py-2 text-left text-sm text-gray-700 hover:bg-[#f5f7fb]"; + + return ( +
+ + + +
+ + +
+
+ +
+ + +
+
+ + +
+ + + + +
+ ); +} + +const cloneNodes = (nodes: DocumentNode[]): DocumentNode[] => + nodes.map((node) => ({ + ...node, + children: cloneNodes(node.children), + })); + +const removeNode = ( + nodes: DocumentNode[], + targetId: string, +): { removed: DocumentNode | null; tree: DocumentNode[] } => { + let removed: DocumentNode | null = null; + const nextTree = nodes + .map((node) => { + if (removed) return node; + if (node.id === targetId) { + removed = node; + return null; + } + const { removed: childRemoved, tree: childTree } = removeNode(node.children, targetId); + if (childRemoved) { + removed = childRemoved; + return { ...node, children: childTree }; + } + return node; + }) + .filter(Boolean) as DocumentNode[]; + return { removed, tree: nextTree }; +}; + +const insertNode = (nodes: DocumentNode[], parentId: string | null, index: number, newNode: DocumentNode): DocumentNode[] => { + if (!parentId) { + const next = [...nodes]; + next.splice(Math.min(index, next.length), 0, newNode); + return next; + } + return nodes.map((node) => { + if (node.id === parentId) { + const children = [...node.children]; + const targetIndex = Math.min(index, children.length); + children.splice(targetIndex, 0, newNode); + return { ...node, children }; + } + return { ...node, children: insertNode(node.children, parentId, index, newNode) }; + }); +}; + +const filterTree = (nodes: DocumentNode[], keyword: string): DocumentNode[] => { + if (!keyword) { + return nodes; + } + const filtered: DocumentNode[] = []; + nodes.forEach((node) => { + const childMatches = filterTree(node.children, keyword); + const title = (node.title ?? "").toLowerCase(); + if (title.includes(keyword) || childMatches.length > 0) { + filtered.push({ ...node, children: childMatches }); + } + }); + return filtered; +}; + +function collectNodeIds(nodes: DocumentNode[], bag: Set = new Set()): Set { + nodes.forEach((node) => { + bag.add(node.id); + collectNodeIds(node.children, bag); + }); + return bag; +} + +const buildDocumentUrl = (documentId: string): string => { + if (typeof window === "undefined" || !window.location) { + return `/documents/${documentId}`; + } + return `${window.location.origin}/documents/${documentId}`; +}; + +const copyText = async (text: string, successMessage: string) => { + if (typeof navigator !== "undefined" && navigator.clipboard) { + try { + await navigator.clipboard.writeText(text); + window.alert(successMessage); + return; + } catch { + // ignore and fallback + } + } + window.prompt("复制失败,请手动复制内容", text); +}; diff --git a/src/components/sidebar/types.ts b/src/components/sidebar/types.ts new file mode 100644 index 00000000..ba5ae566 --- /dev/null +++ b/src/components/sidebar/types.ts @@ -0,0 +1,19 @@ +import type { DocumentRecord } from "@/lib/documents"; +import type { WorkspaceSummary } from "@/lib/workspaces"; + +export type SidebarSectionId = "starred" | "public" | "shared" | "private" | "templates"; + +export interface TrashRecord { + id: string; + title: string | null; + deleted_at: string; + parent_id: string | null; + access_scope: DocumentRecord["access_scope"]; +} + +export interface SidebarInitialData { + activeWorkspaceId: string; + workspaces: WorkspaceSummary[]; + documents: DocumentRecord[]; + trashedDocuments: TrashRecord[]; +} diff --git a/src/components/ui/avatar.tsx b/src/components/ui/avatar.tsx new file mode 100644 index 00000000..71e428b4 --- /dev/null +++ b/src/components/ui/avatar.tsx @@ -0,0 +1,53 @@ +"use client" + +import * as React from "react" +import * as AvatarPrimitive from "@radix-ui/react-avatar" + +import { cn } from "@/lib/utils" + +function Avatar({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AvatarImage({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AvatarFallback({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { Avatar, AvatarImage, AvatarFallback } diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx new file mode 100644 index 00000000..fd3a406b --- /dev/null +++ b/src/components/ui/badge.tsx @@ -0,0 +1,46 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const badgeVariants = cva( + "inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden", + { + variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90", + secondary: + "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90", + destructive: + "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", + outline: + "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +function Badge({ + className, + variant, + asChild = false, + ...props +}: React.ComponentProps<"span"> & + VariantProps & { asChild?: boolean }) { + const Comp = asChild ? Slot : "span" + + return ( + + ) +} + +export { Badge, badgeVariants } diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx new file mode 100644 index 00000000..21409a06 --- /dev/null +++ b/src/components/ui/button.tsx @@ -0,0 +1,60 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const buttonVariants = cva( + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + destructive: + "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", + outline: + "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: + "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-9 px-4 py-2 has-[>svg]:px-3", + sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", + lg: "h-10 rounded-md px-6 has-[>svg]:px-4", + icon: "size-9", + "icon-sm": "size-8", + "icon-lg": "size-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +function Button({ + className, + variant, + size, + asChild = false, + ...props +}: React.ComponentProps<"button"> & + VariantProps & { + asChild?: boolean + }) { + const Comp = asChild ? Slot : "button" + + return ( + + ) +} + +export { Button, buttonVariants } diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx new file mode 100644 index 00000000..681ad980 --- /dev/null +++ b/src/components/ui/card.tsx @@ -0,0 +1,92 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +function Card({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardTitle({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardDescription({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardAction({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardContent({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +export { + Card, + CardHeader, + CardFooter, + CardTitle, + CardAction, + CardDescription, + CardContent, +} diff --git a/src/components/ui/command.tsx b/src/components/ui/command.tsx new file mode 100644 index 00000000..8cb4ca7a --- /dev/null +++ b/src/components/ui/command.tsx @@ -0,0 +1,184 @@ +"use client" + +import * as React from "react" +import { Command as CommandPrimitive } from "cmdk" +import { SearchIcon } from "lucide-react" + +import { cn } from "@/lib/utils" +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog" + +function Command({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CommandDialog({ + title = "Command Palette", + description = "Search for a command to run...", + children, + className, + showCloseButton = true, + ...props +}: React.ComponentProps & { + title?: string + description?: string + className?: string + showCloseButton?: boolean +}) { + return ( + + + {title} + {description} + + + + {children} + + + + ) +} + +function CommandInput({ + className, + ...props +}: React.ComponentProps) { + return ( +
+ + +
+ ) +} + +function CommandList({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CommandEmpty({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CommandGroup({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CommandSeparator({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CommandItem({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CommandShortcut({ + className, + ...props +}: React.ComponentProps<"span">) { + return ( + + ) +} + +export { + Command, + CommandDialog, + CommandInput, + CommandList, + CommandEmpty, + CommandGroup, + CommandItem, + CommandShortcut, + CommandSeparator, +} diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx new file mode 100644 index 00000000..ffbb9d69 --- /dev/null +++ b/src/components/ui/dialog.tsx @@ -0,0 +1,152 @@ +"use client" + +import * as React from "react" +import * as DialogPrimitive from "@radix-ui/react-dialog" +import { XIcon } from "lucide-react" + +import { cn } from "@/lib/utils" + +function Dialog({ + ...props +}: React.ComponentProps) { + return +} + +function DialogTrigger({ + ...props +}: React.ComponentProps) { + return +} + +function DialogPortal({ + ...props +}: React.ComponentProps) { + return +} + +function DialogClose({ + ...props +}: React.ComponentProps) { + return +} + +function DialogOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DialogContent({ + className, + children, + showCloseButton = true, + fullScreen = false, + ...props +}: React.ComponentProps & { + showCloseButton?: boolean + fullScreen?: boolean +}) { + const baseClass = + "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0"; + const defaultLayout = + "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg"; + const fullscreenLayout = + "fixed inset-0 z-50 grid h-full w-full translate-x-0 translate-y-0 rounded-none border-none p-0 shadow-none duration-200"; + return ( + + + + {children} + {showCloseButton && ( + + + Close + + )} + + + ) +} + +function DialogHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function DialogFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function DialogTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DialogDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogOverlay, + DialogPortal, + DialogTitle, + DialogTrigger, +} diff --git a/src/components/ui/drawer.tsx b/src/components/ui/drawer.tsx new file mode 100644 index 00000000..8aa69230 --- /dev/null +++ b/src/components/ui/drawer.tsx @@ -0,0 +1,135 @@ +"use client" + +import * as React from "react" +import { Drawer as DrawerPrimitive } from "vaul" + +import { cn } from "@/lib/utils" + +function Drawer({ + ...props +}: React.ComponentProps) { + return +} + +function DrawerTrigger({ + ...props +}: React.ComponentProps) { + return +} + +function DrawerPortal({ + ...props +}: React.ComponentProps) { + return +} + +function DrawerClose({ + ...props +}: React.ComponentProps) { + return +} + +function DrawerOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DrawerContent({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + + +
+ {children} + + + ) +} + +function DrawerHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function DrawerFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function DrawerTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DrawerDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { + Drawer, + DrawerPortal, + DrawerOverlay, + DrawerTrigger, + DrawerClose, + DrawerContent, + DrawerHeader, + DrawerFooter, + DrawerTitle, + DrawerDescription, +} diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx new file mode 100644 index 00000000..bbe6fb01 --- /dev/null +++ b/src/components/ui/dropdown-menu.tsx @@ -0,0 +1,257 @@ +"use client" + +import * as React from "react" +import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" +import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react" + +import { cn } from "@/lib/utils" + +function DropdownMenu({ + ...props +}: React.ComponentProps) { + return +} + +function DropdownMenuPortal({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DropdownMenuTrigger({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DropdownMenuContent({ + className, + sideOffset = 4, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +function DropdownMenuGroup({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DropdownMenuItem({ + className, + inset, + variant = "default", + ...props +}: React.ComponentProps & { + inset?: boolean + variant?: "default" | "destructive" +}) { + return ( + + ) +} + +function DropdownMenuCheckboxItem({ + className, + children, + checked, + ...props +}: React.ComponentProps) { + return ( + + + + + + + {children} + + ) +} + +function DropdownMenuRadioGroup({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DropdownMenuRadioItem({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + + + + + + {children} + + ) +} + +function DropdownMenuLabel({ + className, + inset, + ...props +}: React.ComponentProps & { + inset?: boolean +}) { + return ( + + ) +} + +function DropdownMenuSeparator({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DropdownMenuShortcut({ + className, + ...props +}: React.ComponentProps<"span">) { + return ( + + ) +} + +function DropdownMenuSub({ + ...props +}: React.ComponentProps) { + return +} + +function DropdownMenuSubTrigger({ + className, + inset, + children, + ...props +}: React.ComponentProps & { + inset?: boolean +}) { + return ( + + {children} + + + ) +} + +function DropdownMenuSubContent({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { + DropdownMenu, + DropdownMenuPortal, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuLabel, + DropdownMenuItem, + DropdownMenuCheckboxItem, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuSub, + DropdownMenuSubTrigger, + DropdownMenuSubContent, +} diff --git a/src/components/ui/hover-card.tsx b/src/components/ui/hover-card.tsx new file mode 100644 index 00000000..47389043 --- /dev/null +++ b/src/components/ui/hover-card.tsx @@ -0,0 +1,30 @@ +"use client"; + +import * as HoverCardPrimitive from "@radix-ui/react-hover-card"; + +import { cn } from "@/lib/utils"; + +const HoverCard = HoverCardPrimitive.Root; + +const HoverCardTrigger = HoverCardPrimitive.Trigger; + +function HoverCardContent({ + className, + align = "center", + sideOffset = 8, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +export { HoverCard, HoverCardContent, HoverCardTrigger }; diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx new file mode 100644 index 00000000..89169058 --- /dev/null +++ b/src/components/ui/input.tsx @@ -0,0 +1,21 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +function Input({ className, type, ...props }: React.ComponentProps<"input">) { + return ( + + ) +} + +export { Input } diff --git a/src/components/ui/label.tsx b/src/components/ui/label.tsx new file mode 100644 index 00000000..fb5fbc3e --- /dev/null +++ b/src/components/ui/label.tsx @@ -0,0 +1,24 @@ +"use client" + +import * as React from "react" +import * as LabelPrimitive from "@radix-ui/react-label" + +import { cn } from "@/lib/utils" + +function Label({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { Label } diff --git a/src/components/ui/popover.tsx b/src/components/ui/popover.tsx new file mode 100644 index 00000000..01e468b6 --- /dev/null +++ b/src/components/ui/popover.tsx @@ -0,0 +1,48 @@ +"use client" + +import * as React from "react" +import * as PopoverPrimitive from "@radix-ui/react-popover" + +import { cn } from "@/lib/utils" + +function Popover({ + ...props +}: React.ComponentProps) { + return +} + +function PopoverTrigger({ + ...props +}: React.ComponentProps) { + return +} + +function PopoverContent({ + className, + align = "center", + sideOffset = 4, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +function PopoverAnchor({ + ...props +}: React.ComponentProps) { + return +} + +export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor } diff --git a/src/components/ui/scroll-area.tsx b/src/components/ui/scroll-area.tsx new file mode 100644 index 00000000..8e4fa13f --- /dev/null +++ b/src/components/ui/scroll-area.tsx @@ -0,0 +1,58 @@ +"use client" + +import * as React from "react" +import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area" + +import { cn } from "@/lib/utils" + +function ScrollArea({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + + {children} + + + + + ) +} + +function ScrollBar({ + className, + orientation = "vertical", + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +export { ScrollArea, ScrollBar } diff --git a/src/components/ui/separator.tsx b/src/components/ui/separator.tsx new file mode 100644 index 00000000..275381ca --- /dev/null +++ b/src/components/ui/separator.tsx @@ -0,0 +1,28 @@ +"use client" + +import * as React from "react" +import * as SeparatorPrimitive from "@radix-ui/react-separator" + +import { cn } from "@/lib/utils" + +function Separator({ + className, + orientation = "horizontal", + decorative = true, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { Separator } diff --git a/src/components/ui/sheet.tsx b/src/components/ui/sheet.tsx new file mode 100644 index 00000000..84649ad0 --- /dev/null +++ b/src/components/ui/sheet.tsx @@ -0,0 +1,139 @@ +"use client" + +import * as React from "react" +import * as SheetPrimitive from "@radix-ui/react-dialog" +import { XIcon } from "lucide-react" + +import { cn } from "@/lib/utils" + +function Sheet({ ...props }: React.ComponentProps) { + return +} + +function SheetTrigger({ + ...props +}: React.ComponentProps) { + return +} + +function SheetClose({ + ...props +}: React.ComponentProps) { + return +} + +function SheetPortal({ + ...props +}: React.ComponentProps) { + return +} + +function SheetOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function SheetContent({ + className, + children, + side = "right", + ...props +}: React.ComponentProps & { + side?: "top" | "right" | "bottom" | "left" +}) { + return ( + + + + {children} + + + Close + + + + ) +} + +function SheetHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function SheetFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function SheetTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function SheetDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { + Sheet, + SheetTrigger, + SheetClose, + SheetContent, + SheetHeader, + SheetFooter, + SheetTitle, + SheetDescription, +} diff --git a/src/components/ui/skeleton.tsx b/src/components/ui/skeleton.tsx new file mode 100644 index 00000000..32ea0ef7 --- /dev/null +++ b/src/components/ui/skeleton.tsx @@ -0,0 +1,13 @@ +import { cn } from "@/lib/utils" + +function Skeleton({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +export { Skeleton } diff --git a/src/components/ui/tabs.tsx b/src/components/ui/tabs.tsx new file mode 100644 index 00000000..497ba5ea --- /dev/null +++ b/src/components/ui/tabs.tsx @@ -0,0 +1,66 @@ +"use client" + +import * as React from "react" +import * as TabsPrimitive from "@radix-ui/react-tabs" + +import { cn } from "@/lib/utils" + +function Tabs({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function TabsList({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function TabsTrigger({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function TabsContent({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { Tabs, TabsList, TabsTrigger, TabsContent } diff --git a/src/components/ui/textarea.tsx b/src/components/ui/textarea.tsx new file mode 100644 index 00000000..7f21b5e7 --- /dev/null +++ b/src/components/ui/textarea.tsx @@ -0,0 +1,18 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +function Textarea({ className, ...props }: React.ComponentProps<"textarea">) { + return ( +