From 64b1c549119873e424428e1982b059fa057f998a Mon Sep 17 00:00:00 2001 From: Snowykami Date: Thu, 25 Sep 2025 00:51:29 +0800 Subject: [PATCH] feat: enhance post management with pagination, search, and order functionality - Added search input for filtering posts by keywords. - Implemented pagination controls for navigating through posts. - Introduced order selector for sorting posts based on various criteria. - Enhanced post item display with additional metrics (view count, like count, comment count). - Added dropdown menu for post actions (edit, view, toggle privacy, delete). - Integrated double confirmation for delete action. - Updated user profile to support background image upload. - Improved user security settings with better layout and validation. - Refactored auth context to use useCallback for logout function. - Added command palette component for improved command execution. - Introduced popover component for better UI interactions. - Implemented debounce hooks for optimized state updates. - Updated localization files with new keys for improved internationalization. - Added tailwind configuration for styling. --- README.md | 6 + internal/controller/v1/config.go | 6 +- internal/dto/user.go | 94 +- internal/model/user.go | 34 +- internal/router/apiv1/config.go | 21 + internal/router/apiv1/post.go | 32 +- internal/service/admin.go | 162 +- web/package.json | 4 + web/pnpm-lock.yaml | 1519 +++++++++++++++++ web/src/api/post.ts | 10 + web/src/app/auth/layout.tsx | 14 + web/src/app/auth/login/page.tsx | 36 +- web/src/app/auth/register/page.tsx | 36 +- web/src/app/auth/reset-password/page.tsx | 7 +- web/src/app/console/layout.tsx | 2 +- web/src/app/console/post/edit/[id]/page.tsx | 3 + web/src/app/layout.tsx | 19 +- .../components/auth/common/auth-header.tsx | 5 +- web/src/components/auth/login/login-form.tsx | 7 +- .../auth/register/register-form.tsx | 8 +- .../reset-password/reset-password-form.tsx | 14 +- web/src/components/blog-home/blog-home.tsx | 25 +- .../components/common/orderby-selector.tsx | 49 + web/src/components/common/pagination.tsx | 306 ++-- web/src/components/common/theme-toggle.tsx | 9 +- web/src/components/console/app-sidebar.tsx | 4 + .../components/console/dashboard/index.tsx | 12 +- web/src/components/console/data.ts | 31 +- web/src/components/console/nav-main.tsx | 5 +- web/src/components/console/nav-ucenter.tsx | 2 +- web/src/components/console/nav-user.tsx | 11 +- .../components/console/post-manage/index.tsx | 192 ++- .../components/console/user-profile/index.tsx | 117 +- .../console/user-security/index.tsx | 38 +- .../components/layout/nav/navbar-or-side.tsx | 2 +- web/src/components/ui/command.tsx | 184 ++ web/src/components/ui/pagination.tsx | 4 +- web/src/components/ui/popover.tsx | 48 + web/src/contexts/auth-context.tsx | 8 +- web/src/hooks/use-debounce.ts | 105 ++ web/src/hooks/use-route.ts | 26 + web/src/locales/zh-CN.json | 46 +- web/src/models/user.ts | 1 + web/tailwind.config.js | 0 44 files changed, 2790 insertions(+), 474 deletions(-) create mode 100644 web/src/app/auth/layout.tsx create mode 100644 web/src/app/console/post/edit/[id]/page.tsx create mode 100644 web/src/components/common/orderby-selector.tsx create mode 100644 web/src/components/ui/command.tsx create mode 100644 web/src/components/ui/popover.tsx create mode 100644 web/src/hooks/use-debounce.ts create mode 100644 web/tailwind.config.js diff --git a/README.md b/README.md index 56945a0..1c813bf 100644 --- a/README.md +++ b/README.md @@ -132,6 +132,12 @@ pnpm install pnpm dev ``` +### 前端规范 + +表单元素使用`grid gap-4`作为容器,表单项使用`grid gap-2`作为容器 + +flex布局横向使用`flex gap-3`作为容器 + ### 联合调试 默认情况下,本机启动后端和前端服务器无须额外配置即可互联,若后端在不同的主机上,需要在.env.development(自己创建)中配置`BACKEND_URL`变量 diff --git a/internal/controller/v1/config.go b/internal/controller/v1/config.go index 7905807..2bb4268 100644 --- a/internal/controller/v1/config.go +++ b/internal/controller/v1/config.go @@ -1,5 +1,7 @@ -package main +package v1 -func main() { +type ConfigController struct{} +func NewConfigController() *ConfigController { + return &ConfigController{} } diff --git a/internal/dto/user.go b/internal/dto/user.go index 14592b3..dc1dbc3 100644 --- a/internal/dto/user.go +++ b/internal/dto/user.go @@ -1,100 +1,102 @@ package dto type UserDto struct { - ID uint `json:"id"` // 用户ID - Username string `json:"username"` // 用户名 - Nickname string `json:"nickname"` - AvatarUrl string `json:"avatar_url"` // 头像URL - Email string `json:"email"` // 邮箱 - Gender string `json:"gender"` - Role string `json:"role"` - Language string `json:"language"` // 语言 + ID uint `json:"id"` // 用户ID + Username string `json:"username"` // 用户名 + Nickname string `json:"nickname"` + AvatarUrl string `json:"avatar_url"` // 头像URL + BackgroundUrl string `json:"background_url"` + Email string `json:"email"` // 邮箱 + Gender string `json:"gender"` + Role string `json:"role"` + Language string `json:"language"` // 语言 } type UserOidcConfigDto struct { - Name string `json:"name"` // OIDC配置名称 - DisplayName string `json:"display_name"` // OIDC配置显示名称 - Icon string `json:"icon"` // OIDC配置图标URL - LoginUrl string `json:"login_url"` // OIDC登录URL + Name string `json:"name"` // OIDC配置名称 + DisplayName string `json:"display_name"` // OIDC配置显示名称 + Icon string `json:"icon"` // OIDC配置图标URL + LoginUrl string `json:"login_url"` // OIDC登录URL } type UserLoginReq struct { - Username string `json:"username"` // username or email - Password string `json:"password"` + Username string `json:"username"` // username or email + Password string `json:"password"` } type UserLoginResp struct { - Token string `json:"token"` - RefreshToken string `json:"refresh_token"` - User UserDto `json:"user"` + Token string `json:"token"` + RefreshToken string `json:"refresh_token"` + User UserDto `json:"user"` } type UserRegisterReq struct { - Username string `json:"username"` // 用户名 - Nickname string `json:"nickname"` // 昵称 - Password string `json:"password"` // 密码 - Email string `json:"-" binding:"-"` + Username string `json:"username"` // 用户名 + Nickname string `json:"nickname"` // 昵称 + Password string `json:"password"` // 密码 + Email string `json:"-" binding:"-"` } type UserRegisterResp struct { - Token string `json:"token"` // 访问令牌 - RefreshToken string `json:"refresh_token"` // 刷新令牌 - User UserDto `json:"user"` // 用户信息 + Token string `json:"token"` // 访问令牌 + RefreshToken string `json:"refresh_token"` // 刷新令牌 + User UserDto `json:"user"` // 用户信息 } type VerifyEmailReq struct { - Email string `json:"email"` // 邮箱地址 + Email string `json:"email"` // 邮箱地址 } type VerifyEmailResp struct { - Success bool `json:"success"` // 验证码发送成功与否 + Success bool `json:"success"` // 验证码发送成功与否 } type OidcLoginReq struct { - Name string `json:"name"` // OIDC配置名称 - Code string `json:"code"` // OIDC授权码 - State string `json:"state"` + Name string `json:"name"` // OIDC配置名称 + Code string `json:"code"` // OIDC授权码 + State string `json:"state"` } type OidcLoginResp struct { - Token string `json:"token"` - RefreshToken string `json:"refresh_token"` - User UserDto `json:"user"` + Token string `json:"token"` + RefreshToken string `json:"refresh_token"` + User UserDto `json:"user"` } type ListOidcConfigResp struct { - OidcConfigs []UserOidcConfigDto `json:"oidc_configs"` // OIDC配置列表 + OidcConfigs []UserOidcConfigDto `json:"oidc_configs"` // OIDC配置列表 } type GetUserReq struct { - UserID uint `json:"user_id"` + UserID uint `json:"user_id"` } type GetUserByUsernameReq struct { - Username string `json:"username"` + Username string `json:"username"` } type GetUserResp struct { - User UserDto `json:"user"` // 用户信息 + User UserDto `json:"user"` // 用户信息 } type UpdateUserReq struct { - ID uint `json:"id"` - Username string `json:"username"` - Nickname string `json:"nickname"` - AvatarUrl string `json:"avatar_url"` - Gender string `json:"gender"` + ID uint `json:"id"` + Username string `json:"username"` + Nickname string `json:"nickname"` + AvatarUrl string `json:"avatar_url"` + BackgroundUrl string `json:"background_url"` + Gender string `json:"gender"` } type UpdateUserResp struct { - User *UserDto `json:"user"` // 更新后的用户信息 + User *UserDto `json:"user"` // 更新后的用户信息 } type UpdatePasswordReq struct { - OldPassword string `json:"old_password"` - NewPassword string `json:"new_password"` + OldPassword string `json:"old_password"` + NewPassword string `json:"new_password"` } type ResetPasswordReq struct { - Email string `json:"-" binding:"-"` - NewPassword string `json:"new_password"` + Email string `json:"-" binding:"-"` + NewPassword string `json:"new_password"` } diff --git a/internal/model/user.go b/internal/model/user.go index 7403ec6..24855b1 100644 --- a/internal/model/user.go +++ b/internal/model/user.go @@ -7,14 +7,15 @@ import ( type User struct { gorm.Model - Username string `gorm:"uniqueIndex;not null"` // 用户名,唯一 - Nickname string `gorm:"default:''"` // 昵称 - AvatarUrl string - Email string `gorm:"uniqueIndex"` - Gender string `gorm:"default:''"` - Role string `gorm:"default:'user'"` // user editor admin - Language string `gorm:"default:'en'"` - Password string // 密码,存储加密后的值 + Username string `gorm:"uniqueIndex;not null"` // 用户名,唯一 + Nickname string `gorm:"default:''"` // 昵称 + AvatarUrl string + BackgroundUrl string + Email string `gorm:"uniqueIndex"` + Gender string `gorm:"default:''"` + Role string `gorm:"default:'user'"` // user editor admin + Language string `gorm:"default:'en'"` + Password string // 密码,存储加密后的值 } type UserOpenID struct { @@ -27,13 +28,14 @@ type UserOpenID struct { func (user *User) ToDto() dto.UserDto { return dto.UserDto{ - ID: user.ID, - Username: user.Username, - Nickname: user.Nickname, - AvatarUrl: user.AvatarUrl, - Email: user.Email, - Gender: user.Gender, - Role: user.Role, - Language: user.Language, + ID: user.ID, + Username: user.Username, + Nickname: user.Nickname, + AvatarUrl: user.AvatarUrl, + BackgroundUrl: user.BackgroundUrl, + Email: user.Email, + Gender: user.Gender, + Role: user.Role, + Language: user.Language, } } diff --git a/internal/router/apiv1/config.go b/internal/router/apiv1/config.go index c0e14f5..e52da65 100644 --- a/internal/router/apiv1/config.go +++ b/internal/router/apiv1/config.go @@ -1 +1,22 @@ package apiv1 + +import ( + "github.com/cloudwego/hertz/pkg/route" + "github.com/snowykami/neo-blog/internal/controller/v1" + "github.com/snowykami/neo-blog/internal/middleware" + "github.com/snowykami/neo-blog/pkg/constant" +) + +func registerConfigRoutes(group *route.RouterGroup) { + // Need Admin Middleware + adminController := v1.NewAdminController() + consoleGroup := group.Group("/admin").Use(middleware.UseAuth(true)).Use(middleware.UseRole(constant.RoleAdmin)) + { + consoleGroup.POST("/oidc/o", adminController.CreateOidc) + consoleGroup.DELETE("/oidc/o/:id", adminController.DeleteOidc) + consoleGroup.GET("/oidc/o/:id", adminController.GetOidcByID) + consoleGroup.GET("/oidc/list", adminController.ListOidc) + consoleGroup.PUT("/oidc/o/:id", adminController.UpdateOidc) + consoleGroup.GET("/dashboard", adminController.GetDashboard) + } +} diff --git a/internal/router/apiv1/post.go b/internal/router/apiv1/post.go index 2cf04bc..c1cb509 100644 --- a/internal/router/apiv1/post.go +++ b/internal/router/apiv1/post.go @@ -1,23 +1,27 @@ package apiv1 import ( - "github.com/cloudwego/hertz/pkg/route" - v1 "github.com/snowykami/neo-blog/internal/controller/v1" - "github.com/snowykami/neo-blog/internal/middleware" - "github.com/snowykami/neo-blog/pkg/constant" + "github.com/cloudwego/hertz/pkg/route" + v1 "github.com/snowykami/neo-blog/internal/controller/v1" + "github.com/snowykami/neo-blog/internal/middleware" + "github.com/snowykami/neo-blog/pkg/constant" ) // post 文章API路由 func registerPostRoutes(group *route.RouterGroup) { - postController := v1.NewPostController() - postGroup := group.Group("/post").Use(middleware.UseAuth(true)).Use(middleware.UseRole(constant.RoleEditor)) - postGroupWithoutAuth := group.Group("/post").Use(middleware.UseAuth(false)) - { - postGroupWithoutAuth.GET("/p/:id", postController.Get) - postGroupWithoutAuth.GET("/list", postController.List) - postGroup.POST("/p", postController.Create) - postGroup.PUT("/p/:id", postController.Update) - postGroup.DELETE("/p/:id", postController.Delete) - } + postController := v1.NewPostController() + postGroup := group.Group("/post").Use(middleware.UseAuth(true)).Use(middleware.UseRole(constant.RoleEditor)) + postGroupWithoutAuth := group.Group("/post").Use(middleware.UseAuth(false)) + { + postGroupWithoutAuth.GET("/p/:id", postController.Get) + postGroupWithoutAuth.GET("/list", postController.List) + postGroup.POST("/p", postController.Create) + postGroup.PUT("/p/:id", postController.Update) + postGroup.DELETE("/p/:id", postController.Delete) + // draft + postGroup.POST("/d") + postGroup.GET("/d/:id") + postGroup.DELETE("/d/:id") + } } diff --git a/internal/service/admin.go b/internal/service/admin.go index d7c8cec..5a4d85f 100644 --- a/internal/service/admin.go +++ b/internal/service/admin.go @@ -1,110 +1,110 @@ package service import ( - "github.com/snowykami/neo-blog/internal/dto" - "github.com/snowykami/neo-blog/internal/model" - "github.com/snowykami/neo-blog/internal/repo" - "github.com/snowykami/neo-blog/pkg/errs" - "gorm.io/gorm" + "github.com/snowykami/neo-blog/internal/dto" + "github.com/snowykami/neo-blog/internal/model" + "github.com/snowykami/neo-blog/internal/repo" + "github.com/snowykami/neo-blog/pkg/errs" + "gorm.io/gorm" ) type AdminService struct{} func NewAdminService() *AdminService { - return &AdminService{} + return &AdminService{} } func (c *AdminService) GetDashboard() (map[string]any, error) { - var ( - postCount, commentCount, userCount, viewCount int64 - err error - mustCount = func(q *gorm.DB, dest *int64) { - if err == nil { - err = q.Count(dest).Error - } - } - mustScan = func(q *gorm.DB, dest *int64) { - if err == nil { - err = q.Scan(dest).Error - } - } - ) - db := repo.GetDB() + var ( + postCount, commentCount, userCount, viewCount int64 + err error + mustCount = func(q *gorm.DB, dest *int64) { + if err == nil { + err = q.Count(dest).Error + } + } + mustScan = func(q *gorm.DB, dest *int64) { + if err == nil { + err = q.Scan(dest).Error + } + } + ) + db := repo.GetDB() - mustCount(db.Model(&model.Comment{}), &commentCount) - mustCount(db.Model(&model.Post{}), &postCount) - mustCount(db.Model(&model.User{}), &userCount) - mustScan(db.Model(&model.Post{}).Select("SUM(view_count)"), &viewCount) + mustCount(db.Model(&model.Comment{}), &commentCount) + mustCount(db.Model(&model.Post{}), &postCount) + mustCount(db.Model(&model.User{}), &userCount) + mustScan(db.Model(&model.Post{}).Select("SUM(view_count)"), &viewCount) - if err != nil { - return nil, err - } - return map[string]any{ - "total_comments": commentCount, - "total_posts": postCount, - "total_users": userCount, - "total_views": viewCount, - }, nil + if err != nil { + return nil, err + } + return map[string]any{ + "total_comments": commentCount, + "total_posts": postCount, + "total_users": userCount, + "total_views": viewCount, + }, nil } func (c *AdminService) CreateOidcConfig(req *dto.AdminOidcConfigDto) error { - oidcConfig := &model.OidcConfig{ - Name: req.Name, - DisplayName: req.DisplayName, - Icon: req.Icon, - ClientID: req.ClientID, - ClientSecret: req.ClientSecret, - OidcDiscoveryUrl: req.OidcDiscoveryUrl, - Enabled: req.Enabled, - Type: req.Type, - } - return repo.Oidc.CreateOidcConfig(oidcConfig) + oidcConfig := &model.OidcConfig{ + Name: req.Name, + DisplayName: req.DisplayName, + Icon: req.Icon, + ClientID: req.ClientID, + ClientSecret: req.ClientSecret, + OidcDiscoveryUrl: req.OidcDiscoveryUrl, + Enabled: req.Enabled, + Type: req.Type, + } + return repo.Oidc.CreateOidcConfig(oidcConfig) } func (c *AdminService) DeleteOidcConfig(id string) error { - if id == "" { - return errs.ErrBadRequest - } - return repo.Oidc.DeleteOidcConfig(id) + if id == "" { + return errs.ErrBadRequest + } + return repo.Oidc.DeleteOidcConfig(id) } func (c *AdminService) GetOidcConfigByID(id string) (*dto.AdminOidcConfigDto, error) { - if id == "" { - return nil, errs.ErrBadRequest - } - config, err := repo.Oidc.GetOidcConfigByID(id) - if err != nil { - return nil, err - } - return config.ToAdminDto(), nil + if id == "" { + return nil, errs.ErrBadRequest + } + config, err := repo.Oidc.GetOidcConfigByID(id) + if err != nil { + return nil, err + } + return config.ToAdminDto(), nil } func (c *AdminService) ListOidcConfigs(onlyEnabled bool) ([]*dto.AdminOidcConfigDto, error) { - configs, err := repo.Oidc.ListOidcConfigs(onlyEnabled) - if err != nil { - return nil, err - } - var dtos []*dto.AdminOidcConfigDto - for _, config := range configs { - dtos = append(dtos, config.ToAdminDto()) - } - return dtos, nil + configs, err := repo.Oidc.ListOidcConfigs(onlyEnabled) + if err != nil { + return nil, err + } + var dtos []*dto.AdminOidcConfigDto + for _, config := range configs { + dtos = append(dtos, config.ToAdminDto()) + } + return dtos, nil } func (c *AdminService) UpdateOidcConfig(req *dto.AdminOidcConfigDto) error { - if req.ID == 0 { - return errs.ErrBadRequest - } - oidcConfig := &model.OidcConfig{ - Model: gorm.Model{ID: req.ID}, - Name: req.Name, - DisplayName: req.DisplayName, - Icon: req.Icon, - ClientID: req.ClientID, - ClientSecret: req.ClientSecret, - OidcDiscoveryUrl: req.OidcDiscoveryUrl, - Enabled: req.Enabled, - Type: req.Type, - } - return repo.Oidc.UpdateOidcConfig(oidcConfig) + if req.ID == 0 { + return errs.ErrBadRequest + } + oidcConfig := &model.OidcConfig{ + Model: gorm.Model{ID: req.ID}, + Name: req.Name, + DisplayName: req.DisplayName, + Icon: req.Icon, + ClientID: req.ClientID, + ClientSecret: req.ClientSecret, + OidcDiscoveryUrl: req.OidcDiscoveryUrl, + Enabled: req.Enabled, + Type: req.Type, + } + return repo.Oidc.UpdateOidcConfig(oidcConfig) } diff --git a/web/package.json b/web/package.json index ababb63..2acb420 100644 --- a/web/package.json +++ b/web/package.json @@ -15,12 +15,14 @@ "@dnd-kit/utilities": "^3.2.2", "@hcaptcha/react-hcaptcha": "^1.12.1", "@marsidev/react-turnstile": "^1.3.0", + "@mdxeditor/editor": "^3.46.1", "@radix-ui/react-avatar": "^1.1.10", "@radix-ui/react-checkbox": "^1.3.3", "@radix-ui/react-dialog": "^1.1.14", "@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-label": "^2.1.7", "@radix-ui/react-navigation-menu": "^1.2.13", + "@radix-ui/react-popover": "^1.1.15", "@radix-ui/react-select": "^2.2.6", "@radix-ui/react-separator": "^1.1.7", "@radix-ui/react-slot": "^1.2.3", @@ -34,6 +36,7 @@ "axios": "^1.11.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "cmdk": "^1.1.1", "deepmerge": "^4.3.1", "field-conv": "^1.0.9", "highlight.js": "^11.11.1", @@ -45,6 +48,7 @@ "next-intl": "^4.3.4", "next-mdx-remote-client": "^2.1.3", "next-themes": "^0.4.6", + "nuqs": "^2.6.0", "react": "19.1.0", "react-dom": "19.1.0", "react-google-recaptcha-v3": "^1.11.0", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index faa33d3..40871a9 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -26,6 +26,9 @@ importers: '@marsidev/react-turnstile': specifier: ^1.3.0 version: 1.3.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@mdxeditor/editor': + specifier: ^3.46.1 + version: 3.46.1(@codemirror/language@6.11.3)(@lezer/highlight@1.2.1)(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(yjs@13.6.27) '@radix-ui/react-avatar': specifier: ^1.1.10 version: 1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -44,6 +47,9 @@ importers: '@radix-ui/react-navigation-menu': specifier: ^1.2.13 version: 1.2.13(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-popover': + specifier: ^1.1.15 + version: 1.1.15(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@radix-ui/react-select': specifier: ^2.2.6 version: 2.2.6(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -83,6 +89,9 @@ importers: clsx: specifier: ^2.1.1 version: 2.1.1 + cmdk: + specifier: ^1.1.1 + version: 1.1.1(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) deepmerge: specifier: ^4.3.1 version: 4.3.1 @@ -116,6 +125,9 @@ importers: next-themes: specifier: ^0.4.6 version: 0.4.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + nuqs: + specifier: ^2.6.0 + version: 2.6.0(next@15.4.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0) react: specifier: 19.1.0 version: 19.1.0 @@ -203,6 +215,108 @@ packages: resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} engines: {node: '>=6.9.0'} + '@codemirror/autocomplete@6.18.7': + resolution: {integrity: sha512-8EzdeIoWPJDsMBwz3zdzwXnUpCzMiCyz5/A3FIPpriaclFCGDkAzK13sMcnsu5rowqiyeQN2Vs2TsOcoDPZirQ==} + + '@codemirror/commands@6.8.1': + resolution: {integrity: sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==} + + '@codemirror/lang-angular@0.1.4': + resolution: {integrity: sha512-oap+gsltb/fzdlTQWD6BFF4bSLKcDnlxDsLdePiJpCVNKWXSTAbiiQeYI3UmES+BLAdkmIC1WjyztC1pi/bX4g==} + + '@codemirror/lang-cpp@6.0.3': + resolution: {integrity: sha512-URM26M3vunFFn9/sm6rzqrBzDgfWuDixp85uTY49wKudToc2jTHUrKIGGKs+QWND+YLofNNZpxcNGRynFJfvgA==} + + '@codemirror/lang-css@6.3.1': + resolution: {integrity: sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==} + + '@codemirror/lang-go@6.0.1': + resolution: {integrity: sha512-7fNvbyNylvqCphW9HD6WFnRpcDjr+KXX/FgqXy5H5ZS0eC5edDljukm/yNgYkwTsgp2busdod50AOTIy6Jikfg==} + + '@codemirror/lang-html@6.4.10': + resolution: {integrity: sha512-h/SceTVsN5r+WE+TVP2g3KDvNoSzbSrtZXCKo4vkKdbfT5t4otuVgngGdFukOO/rwRD2++pCxoh6xD4TEVMkQA==} + + '@codemirror/lang-java@6.0.2': + resolution: {integrity: sha512-m5Nt1mQ/cznJY7tMfQTJchmrjdjQ71IDs+55d1GAa8DGaB8JXWsVCkVT284C3RTASaY43YknrK2X3hPO/J3MOQ==} + + '@codemirror/lang-javascript@6.2.4': + resolution: {integrity: sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==} + + '@codemirror/lang-json@6.0.2': + resolution: {integrity: sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==} + + '@codemirror/lang-less@6.0.2': + resolution: {integrity: sha512-EYdQTG22V+KUUk8Qq582g7FMnCZeEHsyuOJisHRft/mQ+ZSZ2w51NupvDUHiqtsOy7It5cHLPGfHQLpMh9bqpQ==} + + '@codemirror/lang-liquid@6.3.0': + resolution: {integrity: sha512-fY1YsUExcieXRTsCiwX/bQ9+PbCTA/Fumv7C7mTUZHoFkibfESnaXwpr2aKH6zZVwysEunsHHkaIpM/pl3xETQ==} + + '@codemirror/lang-markdown@6.3.4': + resolution: {integrity: sha512-fBm0BO03azXnTAsxhONDYHi/qWSI+uSEIpzKM7h/bkIc9fHnFp9y7KTMXKON0teNT97pFhc1a9DQTtWBYEZ7ug==} + + '@codemirror/lang-php@6.0.2': + resolution: {integrity: sha512-ZKy2v1n8Fc8oEXj0Th0PUMXzQJ0AIR6TaZU+PbDHExFwdu+guzOA4jmCHS1Nz4vbFezwD7LyBdDnddSJeScMCA==} + + '@codemirror/lang-python@6.2.1': + resolution: {integrity: sha512-IRjC8RUBhn9mGR9ywecNhB51yePWCGgvHfY1lWN/Mrp3cKuHr0isDKia+9HnvhiWNnMpbGhWrkhuWOc09exRyw==} + + '@codemirror/lang-rust@6.0.2': + resolution: {integrity: sha512-EZaGjCUegtiU7kSMvOfEZpaCReowEf3yNidYu7+vfuGTm9ow4mthAparY5hisJqOHmJowVH3Upu+eJlUji6qqA==} + + '@codemirror/lang-sass@6.0.2': + resolution: {integrity: sha512-l/bdzIABvnTo1nzdY6U+kPAC51czYQcOErfzQ9zSm9D8GmNPD0WTW8st/CJwBTPLO8jlrbyvlSEcN20dc4iL0Q==} + + '@codemirror/lang-sql@6.10.0': + resolution: {integrity: sha512-6ayPkEd/yRw0XKBx5uAiToSgGECo/GY2NoJIHXIIQh1EVwLuKoU8BP/qK0qH5NLXAbtJRLuT73hx7P9X34iO4w==} + + '@codemirror/lang-vue@0.1.3': + resolution: {integrity: sha512-QSKdtYTDRhEHCfo5zOShzxCmqKJvgGrZwDQSdbvCRJ5pRLWBS7pD/8e/tH44aVQT6FKm0t6RVNoSUWHOI5vNug==} + + '@codemirror/lang-wast@6.0.2': + resolution: {integrity: sha512-Imi2KTpVGm7TKuUkqyJ5NRmeFWF7aMpNiwHnLQe0x9kmrxElndyH0K6H/gXtWwY6UshMRAhpENsgfpSwsgmC6Q==} + + '@codemirror/lang-xml@6.1.0': + resolution: {integrity: sha512-3z0blhicHLfwi2UgkZYRPioSgVTo9PV5GP5ducFH6FaHy0IAJRg+ixj5gTR1gnT/glAIC8xv4w2VL1LoZfs+Jg==} + + '@codemirror/lang-yaml@6.1.2': + resolution: {integrity: sha512-dxrfG8w5Ce/QbT7YID7mWZFKhdhsaTNOYjOkSIMt1qmC4VQnXSDSYVHHHn8k6kJUfIhtLo8t1JJgltlxWdsITw==} + + '@codemirror/language-data@6.5.1': + resolution: {integrity: sha512-0sWxeUSNlBr6OmkqybUTImADFUP0M3P0IiSde4nc24bz/6jIYzqYSgkOSLS+CBIoW1vU8Q9KUWXscBXeoMVC9w==} + + '@codemirror/language@6.11.3': + resolution: {integrity: sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==} + + '@codemirror/legacy-modes@6.5.1': + resolution: {integrity: sha512-DJYQQ00N1/KdESpZV7jg9hafof/iBNp9h7TYo1SLMk86TWl9uDsVdho2dzd81K+v4retmK6mdC7WpuOQDytQqw==} + + '@codemirror/lint@6.8.5': + resolution: {integrity: sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==} + + '@codemirror/merge@6.10.2': + resolution: {integrity: sha512-rmHzVkt5FnCtsi0IgvDIDjh/J4LmbfOboB7FMvVl21IHO0p1QM6jSwjkBjBD3D+c+T79OabEqoduCqvJCBV8Yg==} + + '@codemirror/search@6.5.11': + resolution: {integrity: sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==} + + '@codemirror/state@6.5.2': + resolution: {integrity: sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==} + + '@codemirror/view@6.38.3': + resolution: {integrity: sha512-x2t87+oqwB1mduiQZ6huIghjMt4uZKFEdj66IcXw7+a5iBEvv9lh7EWDRHI7crnD4BMGpnyq/RzmCGbiEZLcvQ==} + + '@codesandbox/nodebox@0.1.8': + resolution: {integrity: sha512-2VRS6JDSk+M+pg56GA6CryyUSGPjBEe8Pnae0QL3jJF1mJZJVMDKr93gJRtBbLkfZN6LD/DwMtf+2L0bpWrjqg==} + + '@codesandbox/sandpack-client@2.19.8': + resolution: {integrity: sha512-CMV4nr1zgKzVpx4I3FYvGRM5YT0VaQhALMW9vy4wZRhEyWAtJITQIqZzrTGWqB1JvV7V72dVEUCUPLfYz5hgJQ==} + + '@codesandbox/sandpack-react@2.20.0': + resolution: {integrity: sha512-takd1YpW/PMQ6KPQfvseWLHWklJovGY8QYj8MtWnskGKbjOGJ6uZfyZbcJ6aCFLQMpNyjTqz9AKNbvhCOZ1TUQ==} + peerDependencies: + react: ^16.8.0 || ^17 || ^18 || ^19 + react-dom: ^16.8.0 || ^17 || ^18 || ^19 + '@dnd-kit/accessibility@3.1.1': resolution: {integrity: sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==} peerDependencies: @@ -290,6 +404,12 @@ packages: 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==} @@ -479,6 +599,131 @@ packages: '@jridgewell/trace-mapping@0.3.29': resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==} + '@lexical/clipboard@0.35.0': + resolution: {integrity: sha512-ko7xSIIiayvDiqjNDX6fgH9RlcM6r9vrrvJYTcfGVBor5httx16lhIi0QJZ4+RNPvGtTjyFv4bwRmsixRRwImg==} + + '@lexical/code@0.35.0': + resolution: {integrity: sha512-ox4DZwETQ9IA7+DS6PN8RJNwSAF7RMjL7YTVODIqFZ5tUFIf+5xoCHbz7Fll0Bvixlp12hVH90xnLwTLRGpkKw==} + + '@lexical/devtools-core@0.35.0': + resolution: {integrity: sha512-C2wwtsMCR6ZTfO0TqpSM17RLJWyfHmifAfCTjFtOJu15p3M6NO/nHYK5Mt7YMQteuS89mOjB4ng8iwoLEZ6QpQ==} + peerDependencies: + react: '>=17.x' + react-dom: '>=17.x' + + '@lexical/dragon@0.35.0': + resolution: {integrity: sha512-SL6mT5pcqrt6hEbJ16vWxip5+r3uvMd0bQV5UUxuk+cxIeuP86iTgRh0HFR7SM2dRTYovL6/tM/O+8QLAUGTIg==} + + '@lexical/hashtag@0.35.0': + resolution: {integrity: sha512-LYJWzXuO2ZjKsvQwrLkNZiS2TsjwYkKjlDgtugzejquTBQ/o/nfSn/MmVx6EkYLOYizaJemmZbz3IBh+u732FA==} + + '@lexical/history@0.35.0': + resolution: {integrity: sha512-onjDRLLxGbCfHexSxxrQaDaieIHyV28zCDrbxR5dxTfW8F8PxjuNyuaG0z6o468AXYECmclxkP+P4aT6poHEpQ==} + + '@lexical/html@0.35.0': + resolution: {integrity: sha512-rXGFE5S5rKsg3tVnr1s4iEgOfCApNXGpIFI3T2jGEShaCZ5HLaBY9NVBXnE9Nb49e9bkDkpZ8FZd1qokCbQXbw==} + + '@lexical/link@0.35.0': + resolution: {integrity: sha512-+0Wx6cBwO8TfdMzpkYFacsmgFh8X1rkiYbq3xoLvk3qV8upYxaMzK1s8Q1cpKmWyI0aZrU6z7fiK4vUqB7+69w==} + + '@lexical/list@0.35.0': + resolution: {integrity: sha512-owsmc8iwgExBX8sFe8fKTiwJVhYULt9hD1RZ/HwfaiEtRZZkINijqReOBnW2mJfRxBzhFSWc4NG3ISB+fHYzqw==} + + '@lexical/mark@0.35.0': + resolution: {integrity: sha512-W0hwMTAVeexvpk9/+J6n1G/sNkpI/Meq1yeDazahFLLAwXLHtvhIAq2P/klgFknDy1hr8X7rcsQuN/bqKcKHYg==} + + '@lexical/markdown@0.35.0': + resolution: {integrity: sha512-BlNyXZAt4gWidMw0SRWrhBETY1BpPglFBZI7yzfqukFqgXRh7HUQA28OYeI/nsx9pgNob8TiUduUwShqqvOdEA==} + + '@lexical/offset@0.35.0': + resolution: {integrity: sha512-DRE4Df6qYf2XiV6foh6KpGNmGAv2ANqt3oVXpyS6W8hTx3+cUuAA1APhCZmLNuU107um4zmHym7taCu6uXW5Yg==} + + '@lexical/overflow@0.35.0': + resolution: {integrity: sha512-B25YvnJQTGlZcrNv7b0PJBLWq3tl8sql497OHfYYLem7EOMPKKDGJScJAKM/91D4H/mMAsx5gnA/XgKobriuTg==} + + '@lexical/plain-text@0.35.0': + resolution: {integrity: sha512-lwBCUNMJf7Gujp2syVWMpKRahfbTv5Wq+H3HK1Q1gKH1P2IytPRxssCHvexw9iGwprSyghkKBlbF3fGpEdIJvQ==} + + '@lexical/react@0.35.0': + resolution: {integrity: sha512-uYAZSqumH8tRymMef+A0f2hQvMwplKK9DXamcefnk3vSNDHHqRWQXpiUo6kD+rKWuQmMbVa5RW4xRQebXEW+1A==} + peerDependencies: + react: '>=17.x' + react-dom: '>=17.x' + + '@lexical/rich-text@0.35.0': + resolution: {integrity: sha512-qEHu8g7vOEzz9GUz1VIUxZBndZRJPh9iJUFI+qTDHj+tQqnd5LCs+G9yz6jgNfiuWWpezTp0i1Vz/udNEuDPKQ==} + + '@lexical/selection@0.35.0': + resolution: {integrity: sha512-mMtDE7Q0nycXdFTTH/+ta6EBrBwxBB4Tg8QwsGntzQ1Cq//d838dpXpFjJOqHEeVHUqXpiuj+cBG8+bvz/rPRw==} + + '@lexical/table@0.35.0': + resolution: {integrity: sha512-9jlTlkVideBKwsEnEkqkdg7A3mije1SvmfiqoYnkl1kKJCLA5iH90ywx327PU0p+bdnURAytWUeZPXaEuEl2OA==} + + '@lexical/text@0.35.0': + resolution: {integrity: sha512-uaMh46BkysV8hK8wQwp5g/ByZW+2hPDt8ahAErxtf8NuzQem1FHG/f5RTchmFqqUDVHO3qLNTv4AehEGmXv8MA==} + + '@lexical/utils@0.35.0': + resolution: {integrity: sha512-2H393EYDnFznYCDFOW3MHiRzwEO5M/UBhtUjvTT+9kc+qhX4U3zc8ixQalo5UmZ5B2nh7L/inXdTFzvSRXtsRA==} + + '@lexical/yjs@0.35.0': + resolution: {integrity: sha512-3DSP7QpmTGYU9bN/yljP0PIao4tNIQtsR4ycauWNSawxs/GQCZtSmAPcLRnCm6qpqsDDjUtKjO/1Ej8FRp0m0w==} + peerDependencies: + yjs: '>=13.5.22' + + '@lezer/common@1.2.3': + resolution: {integrity: sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==} + + '@lezer/cpp@1.1.3': + resolution: {integrity: sha512-ykYvuFQKGsRi6IcE+/hCSGUhb/I4WPjd3ELhEblm2wS2cOznDFzO+ubK2c+ioysOnlZ3EduV+MVQFCPzAIoY3w==} + + '@lezer/css@1.3.0': + resolution: {integrity: sha512-pBL7hup88KbI7hXnZV3PQsn43DHy6TWyzuyk2AO9UyoXcDltvIdqWKE1dLL/45JVZ+YZkHe1WVHqO6wugZZWcw==} + + '@lezer/go@1.0.1': + resolution: {integrity: sha512-xToRsYxwsgJNHTgNdStpcvmbVuKxTapV0dM0wey1geMMRc9aggoVyKgzYp41D2/vVOx+Ii4hmE206kvxIXBVXQ==} + + '@lezer/highlight@1.2.1': + resolution: {integrity: sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==} + + '@lezer/html@1.3.10': + resolution: {integrity: sha512-dqpT8nISx/p9Do3AchvYGV3qYc4/rKr3IBZxlHmpIKam56P47RSHkSF5f13Vu9hebS1jM0HmtJIwLbWz1VIY6w==} + + '@lezer/java@1.1.3': + resolution: {integrity: sha512-yHquUfujwg6Yu4Fd1GNHCvidIvJwi/1Xu2DaKl/pfWIA2c1oXkVvawH3NyXhCaFx4OdlYBVX5wvz2f7Aoa/4Xw==} + + '@lezer/javascript@1.5.4': + resolution: {integrity: sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==} + + '@lezer/json@1.0.3': + resolution: {integrity: sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==} + + '@lezer/lr@1.4.2': + resolution: {integrity: sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==} + + '@lezer/markdown@1.4.3': + resolution: {integrity: sha512-kfw+2uMrQ/wy/+ONfrH83OkdFNM0ye5Xq96cLlaCy7h5UT9FO54DU4oRoIc0CSBh5NWmWuiIJA7NGLMJbQ+Oxg==} + + '@lezer/php@1.0.5': + resolution: {integrity: sha512-W7asp9DhM6q0W6DYNwIkLSKOvxlXRrif+UXBMxzsJUuqmhE7oVU+gS3THO4S/Puh7Xzgm858UNaFi6dxTP8dJA==} + + '@lezer/python@1.1.18': + resolution: {integrity: sha512-31FiUrU7z9+d/ElGQLJFXl+dKOdx0jALlP3KEOsGTex8mvj+SoE1FgItcHWK/axkxCHGUSpqIHt6JAWfWu9Rhg==} + + '@lezer/rust@1.0.2': + resolution: {integrity: sha512-Lz5sIPBdF2FUXcWeCu1//ojFAZqzTQNRga0aYv6dYXqJqPfMdCAI0NzajWUd4Xijj1IKJLtjoXRPMvTKWBcqKg==} + + '@lezer/sass@1.1.0': + resolution: {integrity: sha512-3mMGdCTUZ/84ArHOuXWQr37pnf7f+Nw9ycPUeKX+wu19b7pSMcZGLbaXwvD2APMBDOGxPmpK/O6S1v1EvLoqgQ==} + + '@lezer/xml@1.0.6': + resolution: {integrity: sha512-CdDwirL0OEaStFue/66ZmFSeppuL6Dwjlk8qk153mSQwiSH/Dlri4GNymrNWnUmPl2Um7QfV1FO9KFUyX3Twww==} + + '@lezer/yaml@1.0.3': + resolution: {integrity: sha512-GuBLekbw9jDBDhGur82nuwkxKQ+a3W5H0GfaAthDXcAu+XdpS43VlnxA9E9hllkpSP5ellRDKjLLj7Lu9Wr6xA==} + + '@marijn/find-cluster-break@1.0.2': + resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==} + '@marsidev/react-turnstile@1.3.0': resolution: {integrity: sha512-VO99Nynt+j4ETfMImQCj5LgbUKZ9mWPpy3RjP/3e/3vZu+FIphjEdU6g+cq4FeDoNshSxLlRzBTKcH5JMeM1GQ==} peerDependencies: @@ -494,6 +739,20 @@ packages: '@types/react': '>=16' react: '>=16' + '@mdxeditor/editor@3.46.1': + resolution: {integrity: sha512-TL0Ol88NhlXYfThD6kYGhxIQkUMjBkHZ2OsbvHU6mD2RpqcTp1/tinLmADzmoreKSl/52rcj+lTbrJwBmeiHRw==} + engines: {node: '>=16'} + peerDependencies: + react: '>= 18 || >= 19' + react-dom: '>= 18 || >= 19' + + '@mdxeditor/gurx@1.2.4': + resolution: {integrity: sha512-9ZykIFYhKaXaaSPCs1cuI+FvYDegJjbKwmA4ASE/zY+hJY6EYqvoye4esiO85CjhOw9aoD/izD/CU78/egVqmg==} + engines: {node: '>=16'} + peerDependencies: + react: '>= 18 || >= 19' + react-dom: '>= 18 || >= 19' + '@napi-rs/wasm-runtime@0.2.12': resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} @@ -567,6 +826,12 @@ packages: resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} engines: {node: '>=12.4.0'} + '@open-draft/deferred-promise@2.2.0': + resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} + + '@radix-ui/colors@3.0.0': + resolution: {integrity: sha512-FUOsGBkHrYJwCSEtWRCIfQbZG7q1e6DgxCIOe1SUQzDe/7rXXeA47s8yCn6fuTNQAj1Zq4oTFi9Yjp3wzElcxg==} + '@radix-ui/number@1.1.1': resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} @@ -738,6 +1003,11 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-icons@1.3.2': + resolution: {integrity: sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g==} + peerDependencies: + react: ^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc + '@radix-ui/react-id@1.1.1': resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} peerDependencies: @@ -786,6 +1056,19 @@ packages: '@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: @@ -951,6 +1234,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-toolbar@1.1.11': + resolution: {integrity: sha512-4ol06/1bLoFu1nwUqzdD4Y5RZ9oDdKeiHIsntug54Hcr1pgaHiPqHFEaXI1IFP/EsOfROQZ8Mig9VTIRza6Tjg==} + 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-tooltip@1.2.8': resolution: {integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==} peerDependencies: @@ -1061,6 +1357,16 @@ packages: '@radix-ui/rect@1.1.1': resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} + '@react-hook/intersection-observer@3.1.2': + resolution: {integrity: sha512-mWU3BMkmmzyYMSuhO9wu3eJVP21N8TcgYm9bZnTrMwuM818bEk+0NRM3hP+c/TqA9Ln5C7qE53p1H0QMtzYdvQ==} + peerDependencies: + react: '>=16.8' + + '@react-hook/passive-layout-effect@1.2.1': + resolution: {integrity: sha512-IwEphTD75liO8g+6taS+4oqz+nnroocNfWVHWz7j+N+ZO2vYrc6PV1q7GQhuahL0IOR7JccFTsFKQ/mb6iZWAg==} + peerDependencies: + react: '>=16.8' + '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} @@ -1070,6 +1376,12 @@ packages: '@schummar/icu-type-parser@1.21.5': resolution: {integrity: sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==} + '@standard-schema/spec@1.0.0': + resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} + + '@stitches/core@1.2.8': + resolution: {integrity: sha512-Gfkvwk9o9kE9r9XNBmJRfV8zONvXThnm1tcuojL04Uy5uRyqg93DC83lDebl0rocZCfKSjUv+fWYtMQmEDJldg==} + '@swc/helpers@0.5.15': resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} @@ -1430,6 +1742,9 @@ packages: ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + anser@2.3.2: + resolution: {integrity: sha512-PMqBCBvrOVDRqLGooQb+z+t1Q0PiPyurUQeZRR5uHBOVZcW8B04KMmnT12USnhpNX2wCPagWzLVppQMUG3u0Dw==} + ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} @@ -1512,6 +1827,9 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} @@ -1522,6 +1840,9 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -1570,6 +1891,12 @@ packages: class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + classnames@2.5.1: + resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} + + clean-set@1.1.2: + resolution: {integrity: sha512-cA8uCj0qSoG9e0kevyOWXwPaELRPVg5Pxp6WskLMwerx257Zfnh8Nl0JBH59d7wQzij2CK7qEfJQK3RjuKKIug==} + client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} @@ -1577,6 +1904,23 @@ packages: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} + cm6-theme-basic-light@0.2.0: + resolution: {integrity: sha512-1prg2gv44sYfpHscP26uLT/ePrh0mlmVwMSoSd3zYKQ92Ab3jPRLzyCnpyOCQLJbK+YdNs4HvMRqMNYdy4pMhA==} + peerDependencies: + '@codemirror/language': ^6.0.0 + '@codemirror/state': ^6.0.0 + '@codemirror/view': ^6.0.0 + '@lezer/highlight': ^1.0.0 + + 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 + + codemirror@6.0.2: + resolution: {integrity: sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==} + collapse-white-space@2.1.0: resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==} @@ -1601,9 +1945,15 @@ packages: comma-separated-tokens@2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + compute-scroll-into-view@2.0.4: + resolution: {integrity: sha512-y/ZA3BGnxoM/QHHQ2Uy49CLtnWPbt4tTPpEEZiEmmiWBFKjej7nEyH8Ryz54jH0MLXflUYA3Er2zUxPSJu5R+g==} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + crelt@1.0.6: + resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -1658,6 +2008,10 @@ packages: resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} engines: {node: '>=12'} + d@1.0.2: + resolution: {integrity: sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==} + engines: {node: '>=0.12'} + damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} @@ -1732,6 +2086,10 @@ packages: devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + diff@5.2.0: + resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} + engines: {node: '>=0.3.1'} + doctrine@2.1.0: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} engines: {node: '>=0.10.0'} @@ -1739,6 +2097,15 @@ packages: dom-helpers@5.2.1: resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} + dotenv@16.6.1: + resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} + engines: {node: '>=12'} + + downshift@7.6.2: + resolution: {integrity: sha512-iOv+E1Hyt3JDdL9yYcOgW7nZ7GQ2Uz6YbggwXvKUSleetYhU2nXD482Rz6CzvM4lvI1At34BYruKAL4swRGxaA==} + peerDependencies: + react: '>=16.12.0' + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -1782,16 +2149,34 @@ packages: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} + es5-ext@0.10.64: + resolution: {integrity: sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==} + engines: {node: '>=0.10'} + + es6-iterator@2.0.3: + resolution: {integrity: sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==} + + es6-symbol@3.1.4: + resolution: {integrity: sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==} + engines: {node: '>=0.12'} + esast-util-from-estree@2.0.0: resolution: {integrity: sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==} esast-util-from-js@2.0.1: resolution: {integrity: sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==} + escape-carriage@1.3.1: + resolution: {integrity: sha512-GwBr6yViW3ttx1kb7/Oh+gKQ1/TrhYwxKqVmg5gS+BK+Qe2KrOa/Vh7w3HPBvgGf0LfcDGoY9I6NHKoA5Hozhw==} + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + eslint-config-next@15.4.1: resolution: {integrity: sha512-XIIN+lq8XuSwXUrcv+0uHMDFGJFPxLAw04/a4muFZYygSvStvVa15nY7kh4Il6yOVJyxdMUyVdQ9ApGedaeupw==} peerDependencies: @@ -1888,6 +2273,10 @@ packages: jiti: optional: true + esniff@2.0.1: + resolution: {integrity: sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==} + engines: {node: '>=0.10'} + espree@10.4.0: resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1929,9 +2318,15 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + event-emitter@0.3.5: + resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==} + eventemitter3@4.0.7: resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + ext@1.7.0: + resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==} + extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} @@ -1959,6 +2354,9 @@ packages: fastq@1.19.1: resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + fault@2.0.1: + resolution: {integrity: sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==} + fdir@6.4.6: resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} peerDependencies: @@ -2006,6 +2404,10 @@ packages: resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} engines: {node: '>= 6'} + format@0.2.2: + resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} + engines: {node: '>=0.4.x'} + framer-motion@12.23.12: resolution: {integrity: sha512-6e78rdVtnBvlEVgu6eFEAgG9v3wLnYEboM8I5O5EXvfKC8gxGQB8wXJdhkMy10iVcn05jl6CNw7/HTsTCfwcWg==} peerDependencies: @@ -2124,6 +2526,9 @@ packages: hoist-non-react-statics@3.3.2: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -2157,6 +2562,9 @@ packages: resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} engines: {node: '>=12'} + intersection-observer@0.10.0: + resolution: {integrity: sha512-fn4bQ0Xq8FTej09YC/jqKZwtijpvARlRp6wxL5WTA6yPe2YWSJ5RJh7Nm79rK2qB0wr6iDQzH60XGq5V/7u8YQ==} + intl-messageformat@10.7.16: resolution: {integrity: sha512-UmdmHUmp5CIKKjSoE10la5yfU+AYJAaiYLsodbjL4lji83JNvgOQUjGaGhGrpFCb0Uh7sl7qfP1IyILa8Z40ug==} @@ -2291,6 +2699,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'} @@ -2326,6 +2737,10 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + language-subtag-registry@0.3.23: resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} @@ -2337,6 +2752,14 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} + lexical@0.35.0: + resolution: {integrity: sha512-3VuV8xXhh5xJA6tzvfDvE0YBCMkIZUmxtRilJQDDdCgJCc+eut6qAv2qbN+pbqvarqcQqPN1UF+8YvsjmyOZpw==} + + lib0@0.2.114: + resolution: {integrity: sha512-gcxmNFzA4hv8UYi8j43uPlQ7CGcyMJ2KQb5kZASw6SnAKAf10hK12i2fjrS3Cl/ugZa5Ui6WwIu1/6MIXiHttQ==} + engines: {node: '>=16'} + hasBin: true + lightningcss-darwin-arm64@1.30.1: resolution: {integrity: sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==} engines: {node: '>= 12.0.0'} @@ -2426,6 +2849,10 @@ packages: peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + magic-string@0.30.17: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} @@ -2433,6 +2860,9 @@ packages: resolution: {integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==} engines: {node: '>=16'} + 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'} @@ -2440,9 +2870,27 @@ packages: md5@2.3.0: resolution: {integrity: sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==} + mdast-util-directive@3.1.0: + resolution: {integrity: sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q==} + mdast-util-from-markdown@2.0.2: resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} + mdast-util-frontmatter@2.0.1: + resolution: {integrity: sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==} + + 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-highlight-mark@1.2.2: + resolution: {integrity: sha512-OYumVoytj+B9YgwzBhBcYUCLYHIPvJtAvwnMyKhUXbfUFuER5S+FDZyu9fadUxm2TCT5fRYK3jQXh2ioWAxrMw==} + mdast-util-mdx-expression@2.0.1: resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==} @@ -2474,6 +2922,24 @@ packages: micromark-core-commonmark@2.0.3: resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} + micromark-extension-directive@3.0.2: + resolution: {integrity: sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==} + + micromark-extension-frontmatter@2.0.0: + resolution: {integrity: sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==} + + 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-task-list-item@2.1.0: + resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} + + micromark-extension-highlight-mark@1.2.0: + resolution: {integrity: sha512-huGtbd/9kQsMk8u7nrVMaS5qH/47yDG6ZADggo5Owz5JoY8wdfQjfuy118/QiYNCvdFuFDbzT0A7K7Hp2cBsXA==} + micromark-extension-mdx-expression@3.0.1: resolution: {integrity: sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==} @@ -2610,6 +3076,10 @@ packages: react-dom: optional: true + mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -2653,6 +3123,9 @@ packages: react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc + next-tick@1.1.0: + resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} + next@15.4.1: resolution: {integrity: sha512-eNKB1q8C7o9zXF8+jgJs2CzSLIU3T6bQtX6DcTnCq1sIR1CJ0GlSyRs1BubQi3/JgCnr9Vr+rS5mOMI38FFyQw==} engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} @@ -2674,6 +3147,27 @@ packages: sass: optional: true + nuqs@2.6.0: + resolution: {integrity: sha512-EvIKBvfJeyL8n6lxfebxoAbkHJutNsxmy1oo4noFUYNUESbnecxWxgnxXoa0/4fYiFwdEuyMQx1Z9rV0h4bnkg==} + peerDependencies: + '@remix-run/react': '>=2' + '@tanstack/react-router': ^1 + next: '>=14.2.0' + react: '>=18.2.0 || ^19.0.0-0' + react-router: ^6 || ^7 + react-router-dom: ^6 || ^7 + peerDependenciesMeta: + '@remix-run/react': + optional: true + '@tanstack/react-router': + optional: true + next: + optional: true + react-router: + optional: true + react-router-dom: + optional: true + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -2710,6 +3204,9 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} + outvariant@1.4.0: + resolution: {integrity: sha512-AlWY719RF02ujitly7Kk/0QlV+pXGFDHrHf9O2OKqyqgBieaPOIeuSkL8sRK6j2WK+/ZAURq2kZsY0d8JapUiw==} + own-keys@1.0.1: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} @@ -2767,6 +3264,10 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + prismjs@1.30.0: + resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} + engines: {node: '>=6'} + prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} @@ -2783,17 +3284,32 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + react-devtools-inline@4.4.0: + resolution: {integrity: sha512-ES0GolSrKO8wsKbsEkVeiR/ZAaHQTY4zDh1UW8DImVmm8oaGLl3ijJDvSGe+qDRKPZdPRnDtWWnSvvrgxXdThQ==} + react-dom@19.1.0: resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==} peerDependencies: react: ^19.1.0 + react-error-boundary@3.1.4: + resolution: {integrity: sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==} + engines: {node: '>=10', npm: '>=6'} + peerDependencies: + react: '>=16.13.1' + react-google-recaptcha-v3@1.11.0: resolution: {integrity: sha512-kLQqpz/77m8+trpBwzqcxNtvWZYoZ/YO6Vm2cVTHW8hs80BWUfDpC7RDwuAvpswwtSYApWfaSpIDFWAIBNIYxQ==} peerDependencies: react: ^16.3 || ^17.0 || ^18.0 || ^19.0 react-dom: ^17.0 || ^18.0 || ^19.0 + react-hook-form@7.63.0: + resolution: {integrity: sha512-ZwueDMvUeucovM2VjkCf7zIHcs1aAlDimZu2Hvel5C5907gUzMpm4xCrQXtRzCvsBqFjonB4m3x4LzCFI1ZKWA==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^16.8.0 || ^17 || ^18 || ^19 + react-icons@5.5.0: resolution: {integrity: sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==} peerDependencies: @@ -2807,6 +3323,9 @@ packages: react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} @@ -2929,6 +3448,10 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + sade@1.8.1: + resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} + engines: {node: '>=6'} + safe-array-concat@1.1.3: resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} engines: {node: '>=0.4'} @@ -3020,10 +3543,16 @@ packages: stable-hash@0.0.5: resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} + static-browser-server@1.0.3: + resolution: {integrity: sha512-ZUyfgGDdFRbZGGJQ1YhiM930Yczz5VlbJObrQLlk24+qNHVQx4OlLcYswEUo3bIyNAbQUIUR9Yr5/Hqjzqb4zA==} + stop-iteration-iterator@1.1.0: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} + strict-event-emitter@0.4.6: + resolution: {integrity: sha512-12KWeb+wixJohmnwNFerbyiBrAlq5qJLwIt38etRtKtmmHyDSoGlIqFE9wx+4IwG0aDjI7GV8tc8ZccjWZZtTg==} + string.prototype.includes@2.0.1: resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==} engines: {node: '>= 0.4'} @@ -3058,6 +3587,9 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + style-mod@4.1.2: + resolution: {integrity: sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==} + style-to-js@1.1.17: resolution: {integrity: sha512-xQcBGDxJb6jjFCTzvQtfiPn6YvvP2O8U1MDIPNfJQlWMYfktPy+iGsHE7cssjs7y84d9fQaK4UF3RIJaAHSoYA==} @@ -3085,6 +3617,9 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + tabbable@6.2.0: + resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} + tailwind-merge@3.3.1: resolution: {integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==} @@ -3139,6 +3674,9 @@ packages: resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} engines: {node: '>=16'} + type@2.7.3: + resolution: {integrity: sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==} + typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} engines: {node: '>= 0.4'} @@ -3170,6 +3708,9 @@ packages: undici-types@7.8.0: resolution: {integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==} + unidiff@1.0.4: + resolution: {integrity: sha512-ynU0vsAXw0ir8roa+xPCUHmnJ5goc5BTM2Kuc3IJd8UwgaeRs7VSD5+eeaQL+xp1JtB92hu/Zy/Lgy7RZcr1pQ==} + unified@11.0.5: resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} @@ -3233,6 +3774,11 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + uvu@0.5.6: + resolution: {integrity: sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==} + engines: {node: '>=8'} + hasBin: true + vaul@1.1.2: resolution: {integrity: sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA==} peerDependencies: @@ -3251,6 +3797,9 @@ packages: victory-vendor@36.9.2: resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==} + w3c-keyname@2.2.8: + resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} + which-boxed-primitive@1.1.1: resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} engines: {node: '>= 0.4'} @@ -3285,6 +3834,10 @@ packages: engines: {node: '>= 14.6'} hasBin: true + 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'} @@ -3314,6 +3867,291 @@ snapshots: '@babel/runtime@7.28.4': {} + '@codemirror/autocomplete@6.18.7': + dependencies: + '@codemirror/language': 6.11.3 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.3 + '@lezer/common': 1.2.3 + + '@codemirror/commands@6.8.1': + dependencies: + '@codemirror/language': 6.11.3 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.3 + '@lezer/common': 1.2.3 + + '@codemirror/lang-angular@0.1.4': + dependencies: + '@codemirror/lang-html': 6.4.10 + '@codemirror/lang-javascript': 6.2.4 + '@codemirror/language': 6.11.3 + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@codemirror/lang-cpp@6.0.3': + dependencies: + '@codemirror/language': 6.11.3 + '@lezer/cpp': 1.1.3 + + '@codemirror/lang-css@6.3.1': + dependencies: + '@codemirror/autocomplete': 6.18.7 + '@codemirror/language': 6.11.3 + '@codemirror/state': 6.5.2 + '@lezer/common': 1.2.3 + '@lezer/css': 1.3.0 + + '@codemirror/lang-go@6.0.1': + dependencies: + '@codemirror/autocomplete': 6.18.7 + '@codemirror/language': 6.11.3 + '@codemirror/state': 6.5.2 + '@lezer/common': 1.2.3 + '@lezer/go': 1.0.1 + + '@codemirror/lang-html@6.4.10': + dependencies: + '@codemirror/autocomplete': 6.18.7 + '@codemirror/lang-css': 6.3.1 + '@codemirror/lang-javascript': 6.2.4 + '@codemirror/language': 6.11.3 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.3 + '@lezer/common': 1.2.3 + '@lezer/css': 1.3.0 + '@lezer/html': 1.3.10 + + '@codemirror/lang-java@6.0.2': + dependencies: + '@codemirror/language': 6.11.3 + '@lezer/java': 1.1.3 + + '@codemirror/lang-javascript@6.2.4': + dependencies: + '@codemirror/autocomplete': 6.18.7 + '@codemirror/language': 6.11.3 + '@codemirror/lint': 6.8.5 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.3 + '@lezer/common': 1.2.3 + '@lezer/javascript': 1.5.4 + + '@codemirror/lang-json@6.0.2': + dependencies: + '@codemirror/language': 6.11.3 + '@lezer/json': 1.0.3 + + '@codemirror/lang-less@6.0.2': + dependencies: + '@codemirror/lang-css': 6.3.1 + '@codemirror/language': 6.11.3 + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@codemirror/lang-liquid@6.3.0': + dependencies: + '@codemirror/autocomplete': 6.18.7 + '@codemirror/lang-html': 6.4.10 + '@codemirror/language': 6.11.3 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.3 + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@codemirror/lang-markdown@6.3.4': + dependencies: + '@codemirror/autocomplete': 6.18.7 + '@codemirror/lang-html': 6.4.10 + '@codemirror/language': 6.11.3 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.3 + '@lezer/common': 1.2.3 + '@lezer/markdown': 1.4.3 + + '@codemirror/lang-php@6.0.2': + dependencies: + '@codemirror/lang-html': 6.4.10 + '@codemirror/language': 6.11.3 + '@codemirror/state': 6.5.2 + '@lezer/common': 1.2.3 + '@lezer/php': 1.0.5 + + '@codemirror/lang-python@6.2.1': + dependencies: + '@codemirror/autocomplete': 6.18.7 + '@codemirror/language': 6.11.3 + '@codemirror/state': 6.5.2 + '@lezer/common': 1.2.3 + '@lezer/python': 1.1.18 + + '@codemirror/lang-rust@6.0.2': + dependencies: + '@codemirror/language': 6.11.3 + '@lezer/rust': 1.0.2 + + '@codemirror/lang-sass@6.0.2': + dependencies: + '@codemirror/lang-css': 6.3.1 + '@codemirror/language': 6.11.3 + '@codemirror/state': 6.5.2 + '@lezer/common': 1.2.3 + '@lezer/sass': 1.1.0 + + '@codemirror/lang-sql@6.10.0': + dependencies: + '@codemirror/autocomplete': 6.18.7 + '@codemirror/language': 6.11.3 + '@codemirror/state': 6.5.2 + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@codemirror/lang-vue@0.1.3': + dependencies: + '@codemirror/lang-html': 6.4.10 + '@codemirror/lang-javascript': 6.2.4 + '@codemirror/language': 6.11.3 + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@codemirror/lang-wast@6.0.2': + dependencies: + '@codemirror/language': 6.11.3 + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@codemirror/lang-xml@6.1.0': + dependencies: + '@codemirror/autocomplete': 6.18.7 + '@codemirror/language': 6.11.3 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.3 + '@lezer/common': 1.2.3 + '@lezer/xml': 1.0.6 + + '@codemirror/lang-yaml@6.1.2': + dependencies: + '@codemirror/autocomplete': 6.18.7 + '@codemirror/language': 6.11.3 + '@codemirror/state': 6.5.2 + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + '@lezer/yaml': 1.0.3 + + '@codemirror/language-data@6.5.1': + dependencies: + '@codemirror/lang-angular': 0.1.4 + '@codemirror/lang-cpp': 6.0.3 + '@codemirror/lang-css': 6.3.1 + '@codemirror/lang-go': 6.0.1 + '@codemirror/lang-html': 6.4.10 + '@codemirror/lang-java': 6.0.2 + '@codemirror/lang-javascript': 6.2.4 + '@codemirror/lang-json': 6.0.2 + '@codemirror/lang-less': 6.0.2 + '@codemirror/lang-liquid': 6.3.0 + '@codemirror/lang-markdown': 6.3.4 + '@codemirror/lang-php': 6.0.2 + '@codemirror/lang-python': 6.2.1 + '@codemirror/lang-rust': 6.0.2 + '@codemirror/lang-sass': 6.0.2 + '@codemirror/lang-sql': 6.10.0 + '@codemirror/lang-vue': 0.1.3 + '@codemirror/lang-wast': 6.0.2 + '@codemirror/lang-xml': 6.1.0 + '@codemirror/lang-yaml': 6.1.2 + '@codemirror/language': 6.11.3 + '@codemirror/legacy-modes': 6.5.1 + + '@codemirror/language@6.11.3': + dependencies: + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.3 + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + style-mod: 4.1.2 + + '@codemirror/legacy-modes@6.5.1': + dependencies: + '@codemirror/language': 6.11.3 + + '@codemirror/lint@6.8.5': + dependencies: + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.3 + crelt: 1.0.6 + + '@codemirror/merge@6.10.2': + dependencies: + '@codemirror/language': 6.11.3 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.3 + '@lezer/highlight': 1.2.1 + style-mod: 4.1.2 + + '@codemirror/search@6.5.11': + dependencies: + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.3 + crelt: 1.0.6 + + '@codemirror/state@6.5.2': + dependencies: + '@marijn/find-cluster-break': 1.0.2 + + '@codemirror/view@6.38.3': + dependencies: + '@codemirror/state': 6.5.2 + crelt: 1.0.6 + style-mod: 4.1.2 + w3c-keyname: 2.2.8 + + '@codesandbox/nodebox@0.1.8': + dependencies: + outvariant: 1.4.0 + strict-event-emitter: 0.4.6 + + '@codesandbox/sandpack-client@2.19.8': + dependencies: + '@codesandbox/nodebox': 0.1.8 + buffer: 6.0.3 + dequal: 2.0.3 + mime-db: 1.52.0 + outvariant: 1.4.0 + static-browser-server: 1.0.3 + + '@codesandbox/sandpack-react@2.20.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@codemirror/autocomplete': 6.18.7 + '@codemirror/commands': 6.8.1 + '@codemirror/lang-css': 6.3.1 + '@codemirror/lang-html': 6.4.10 + '@codemirror/lang-javascript': 6.2.4 + '@codemirror/language': 6.11.3 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.3 + '@codesandbox/sandpack-client': 2.19.8 + '@lezer/highlight': 1.2.1 + '@react-hook/intersection-observer': 3.1.2(react@19.1.0) + '@stitches/core': 1.2.8 + anser: 2.3.2 + clean-set: 1.1.2 + dequal: 2.0.3 + escape-carriage: 1.3.1 + lz-string: 1.5.0 + react: 19.1.0 + react-devtools-inline: 4.4.0 + react-dom: 19.1.0(react@19.1.0) + react-is: 17.0.2 + '@dnd-kit/accessibility@3.1.1(react@19.1.0)': dependencies: react: 19.1.0 @@ -3421,6 +4259,14 @@ snapshots: react: 19.1.0 react-dom: 19.1.0(react@19.1.0) + '@floating-ui/react@0.27.16(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@floating-ui/react-dom': 2.1.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@floating-ui/utils': 0.2.10 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + tabbable: 6.2.0 + '@floating-ui/utils@0.2.10': {} '@formatjs/ecma402-abstract@2.3.4': @@ -3579,6 +4425,247 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.4 + '@lexical/clipboard@0.35.0': + dependencies: + '@lexical/html': 0.35.0 + '@lexical/list': 0.35.0 + '@lexical/selection': 0.35.0 + '@lexical/utils': 0.35.0 + lexical: 0.35.0 + + '@lexical/code@0.35.0': + dependencies: + '@lexical/utils': 0.35.0 + lexical: 0.35.0 + prismjs: 1.30.0 + + '@lexical/devtools-core@0.35.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@lexical/html': 0.35.0 + '@lexical/link': 0.35.0 + '@lexical/mark': 0.35.0 + '@lexical/table': 0.35.0 + '@lexical/utils': 0.35.0 + lexical: 0.35.0 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + '@lexical/dragon@0.35.0': + dependencies: + lexical: 0.35.0 + + '@lexical/hashtag@0.35.0': + dependencies: + '@lexical/utils': 0.35.0 + lexical: 0.35.0 + + '@lexical/history@0.35.0': + dependencies: + '@lexical/utils': 0.35.0 + lexical: 0.35.0 + + '@lexical/html@0.35.0': + dependencies: + '@lexical/selection': 0.35.0 + '@lexical/utils': 0.35.0 + lexical: 0.35.0 + + '@lexical/link@0.35.0': + dependencies: + '@lexical/utils': 0.35.0 + lexical: 0.35.0 + + '@lexical/list@0.35.0': + dependencies: + '@lexical/selection': 0.35.0 + '@lexical/utils': 0.35.0 + lexical: 0.35.0 + + '@lexical/mark@0.35.0': + dependencies: + '@lexical/utils': 0.35.0 + lexical: 0.35.0 + + '@lexical/markdown@0.35.0': + dependencies: + '@lexical/code': 0.35.0 + '@lexical/link': 0.35.0 + '@lexical/list': 0.35.0 + '@lexical/rich-text': 0.35.0 + '@lexical/text': 0.35.0 + '@lexical/utils': 0.35.0 + lexical: 0.35.0 + + '@lexical/offset@0.35.0': + dependencies: + lexical: 0.35.0 + + '@lexical/overflow@0.35.0': + dependencies: + lexical: 0.35.0 + + '@lexical/plain-text@0.35.0': + dependencies: + '@lexical/clipboard': 0.35.0 + '@lexical/selection': 0.35.0 + '@lexical/utils': 0.35.0 + lexical: 0.35.0 + + '@lexical/react@0.35.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(yjs@13.6.27)': + dependencies: + '@floating-ui/react': 0.27.16(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@lexical/devtools-core': 0.35.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@lexical/dragon': 0.35.0 + '@lexical/hashtag': 0.35.0 + '@lexical/history': 0.35.0 + '@lexical/link': 0.35.0 + '@lexical/list': 0.35.0 + '@lexical/mark': 0.35.0 + '@lexical/markdown': 0.35.0 + '@lexical/overflow': 0.35.0 + '@lexical/plain-text': 0.35.0 + '@lexical/rich-text': 0.35.0 + '@lexical/table': 0.35.0 + '@lexical/text': 0.35.0 + '@lexical/utils': 0.35.0 + '@lexical/yjs': 0.35.0(yjs@13.6.27) + lexical: 0.35.0 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + react-error-boundary: 3.1.4(react@19.1.0) + transitivePeerDependencies: + - yjs + + '@lexical/rich-text@0.35.0': + dependencies: + '@lexical/clipboard': 0.35.0 + '@lexical/selection': 0.35.0 + '@lexical/utils': 0.35.0 + lexical: 0.35.0 + + '@lexical/selection@0.35.0': + dependencies: + lexical: 0.35.0 + + '@lexical/table@0.35.0': + dependencies: + '@lexical/clipboard': 0.35.0 + '@lexical/utils': 0.35.0 + lexical: 0.35.0 + + '@lexical/text@0.35.0': + dependencies: + lexical: 0.35.0 + + '@lexical/utils@0.35.0': + dependencies: + '@lexical/list': 0.35.0 + '@lexical/selection': 0.35.0 + '@lexical/table': 0.35.0 + lexical: 0.35.0 + + '@lexical/yjs@0.35.0(yjs@13.6.27)': + dependencies: + '@lexical/offset': 0.35.0 + '@lexical/selection': 0.35.0 + lexical: 0.35.0 + yjs: 13.6.27 + + '@lezer/common@1.2.3': {} + + '@lezer/cpp@1.1.3': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@lezer/css@1.3.0': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@lezer/go@1.0.1': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@lezer/highlight@1.2.1': + dependencies: + '@lezer/common': 1.2.3 + + '@lezer/html@1.3.10': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@lezer/java@1.1.3': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@lezer/javascript@1.5.4': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@lezer/json@1.0.3': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@lezer/lr@1.4.2': + dependencies: + '@lezer/common': 1.2.3 + + '@lezer/markdown@1.4.3': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + + '@lezer/php@1.0.5': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@lezer/python@1.1.18': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@lezer/rust@1.0.2': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@lezer/sass@1.1.0': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@lezer/xml@1.0.6': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@lezer/yaml@1.0.3': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@marijn/find-cluster-break@1.0.2': {} + '@marsidev/react-turnstile@1.3.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: react: 19.1.0 @@ -3620,6 +4707,79 @@ snapshots: '@types/react': 19.1.8 react: 19.1.0 + '@mdxeditor/editor@3.46.1(@codemirror/language@6.11.3)(@lezer/highlight@1.2.1)(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(yjs@13.6.27)': + dependencies: + '@codemirror/commands': 6.8.1 + '@codemirror/lang-markdown': 6.3.4 + '@codemirror/language-data': 6.5.1 + '@codemirror/merge': 6.10.2 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.3 + '@codesandbox/sandpack-react': 2.20.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@lexical/clipboard': 0.35.0 + '@lexical/link': 0.35.0 + '@lexical/list': 0.35.0 + '@lexical/markdown': 0.35.0 + '@lexical/plain-text': 0.35.0 + '@lexical/react': 0.35.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(yjs@13.6.27) + '@lexical/rich-text': 0.35.0 + '@lexical/selection': 0.35.0 + '@lexical/utils': 0.35.0 + '@mdxeditor/gurx': 1.2.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/colors': 3.0.0 + '@radix-ui/react-dialog': 1.1.14(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-icons': 1.3.2(react@19.1.0) + '@radix-ui/react-popover': 1.1.15(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-select': 2.2.6(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-toggle-group': 1.1.11(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-toolbar': 1.1.11(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-tooltip': 1.2.8(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + classnames: 2.5.1 + cm6-theme-basic-light: 0.2.0(@codemirror/language@6.11.3)(@codemirror/state@6.5.2)(@codemirror/view@6.38.3)(@lezer/highlight@1.2.1) + codemirror: 6.0.2 + downshift: 7.6.2(react@19.1.0) + js-yaml: 4.1.0 + lexical: 0.35.0 + mdast-util-directive: 3.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-frontmatter: 2.0.1 + 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-highlight-mark: 1.2.2 + mdast-util-mdx: 3.0.0 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-to-markdown: 2.1.2 + micromark-extension-directive: 3.0.2 + micromark-extension-frontmatter: 2.0.0 + micromark-extension-gfm-strikethrough: 2.1.0 + micromark-extension-gfm-table: 2.1.1 + micromark-extension-gfm-task-list-item: 2.1.0 + micromark-extension-highlight-mark: 1.2.0 + micromark-extension-mdx-jsx: 3.0.2 + micromark-extension-mdx-md: 2.0.0 + micromark-extension-mdxjs: 3.0.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + react-hook-form: 7.63.0(react@19.1.0) + unidiff: 1.0.4 + transitivePeerDependencies: + - '@codemirror/language' + - '@lezer/highlight' + - '@types/react' + - '@types/react-dom' + - supports-color + - yjs + + '@mdxeditor/gurx@1.2.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + '@napi-rs/wasm-runtime@0.2.12': dependencies: '@emnapi/core': 1.4.4 @@ -3671,6 +4831,10 @@ snapshots: '@nolyfill/is-core-module@1.0.39': {} + '@open-draft/deferred-promise@2.2.0': {} + + '@radix-ui/colors@3.0.0': {} + '@radix-ui/number@1.1.1': {} '@radix-ui/primitive@1.1.2': {} @@ -3831,6 +4995,10 @@ snapshots: '@types/react': 19.1.8 '@types/react-dom': 19.1.6(@types/react@19.1.8) + '@radix-ui/react-icons@1.3.2(react@19.1.0)': + dependencies: + react: 19.1.0 + '@radix-ui/react-id@1.1.1(@types/react@19.1.8)(react@19.1.0)': dependencies: '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) @@ -3895,6 +5063,29 @@ snapshots: '@types/react': 19.1.8 '@types/react-dom': 19.1.6(@types/react@19.1.8) + '@radix-ui/react-popover@1.1.15(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.0) + aria-hidden: 1.2.6 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + react-remove-scroll: 2.7.1(@types/react@19.1.8)(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + '@radix-ui/react-popper@1.2.8(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@floating-ui/react-dom': 2.1.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -4071,6 +5262,21 @@ snapshots: '@types/react': 19.1.8 '@types/react-dom': 19.1.6(@types/react@19.1.8) + '@radix-ui/react-toolbar@1.1.11(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-separator': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-toggle-group': 1.1.11(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + '@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -4163,12 +5369,26 @@ snapshots: '@radix-ui/rect@1.1.1': {} + '@react-hook/intersection-observer@3.1.2(react@19.1.0)': + dependencies: + '@react-hook/passive-layout-effect': 1.2.1(react@19.1.0) + intersection-observer: 0.10.0 + react: 19.1.0 + + '@react-hook/passive-layout-effect@1.2.1(react@19.1.0)': + dependencies: + react: 19.1.0 + '@rtsao/scc@1.1.0': {} '@rushstack/eslint-patch@1.12.0': {} '@schummar/icu-type-parser@1.21.5': {} + '@standard-schema/spec@1.0.0': {} + + '@stitches/core@1.2.8': {} + '@swc/helpers@0.5.15': dependencies: tslib: 2.8.1 @@ -4504,6 +5724,8 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + anser@2.3.2: {} + ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 @@ -4611,6 +5833,8 @@ snapshots: balanced-match@1.0.2: {} + base64-js@1.5.1: {} + brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 @@ -4624,6 +5848,11 @@ snapshots: dependencies: fill-range: 7.1.1 + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -4668,10 +5897,43 @@ snapshots: dependencies: clsx: 2.1.1 + classnames@2.5.1: {} + + clean-set@1.1.2: {} + client-only@0.0.1: {} clsx@2.1.1: {} + cm6-theme-basic-light@0.2.0(@codemirror/language@6.11.3)(@codemirror/state@6.5.2)(@codemirror/view@6.38.3)(@lezer/highlight@1.2.1): + dependencies: + '@codemirror/language': 6.11.3 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.3 + '@lezer/highlight': 1.2.1 + + cmdk@1.1.1(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-dialog': 1.1.14(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + + codemirror@6.0.2: + dependencies: + '@codemirror/autocomplete': 6.18.7 + '@codemirror/commands': 6.8.1 + '@codemirror/language': 6.11.3 + '@codemirror/lint': 6.8.5 + '@codemirror/search': 6.5.11 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.3 + collapse-white-space@2.1.0: {} color-convert@2.0.1: @@ -4698,8 +5960,12 @@ snapshots: comma-separated-tokens@2.0.3: {} + compute-scroll-into-view@2.0.4: {} + concat-map@0.0.1: {} + crelt@1.0.6: {} + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -4748,6 +6014,11 @@ snapshots: d3-timer@3.0.1: {} + d@1.0.2: + dependencies: + es5-ext: 0.10.64 + type: 2.7.3 + damerau-levenshtein@1.0.8: {} data-view-buffer@1.0.2: @@ -4812,6 +6083,8 @@ snapshots: dependencies: dequal: 2.0.3 + diff@5.2.0: {} + doctrine@2.1.0: dependencies: esutils: 2.0.3 @@ -4821,6 +6094,17 @@ snapshots: '@babel/runtime': 7.28.4 csstype: 3.1.3 + dotenv@16.6.1: {} + + downshift@7.6.2(react@19.1.0): + dependencies: + '@babel/runtime': 7.28.4 + compute-scroll-into-view: 2.0.4 + prop-types: 15.8.1 + react: 19.1.0 + react-is: 17.0.2 + tslib: 2.8.1 + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -4935,6 +6219,24 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 + es5-ext@0.10.64: + dependencies: + es6-iterator: 2.0.3 + es6-symbol: 3.1.4 + esniff: 2.0.1 + next-tick: 1.1.0 + + es6-iterator@2.0.3: + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + es6-symbol: 3.1.4 + + es6-symbol@3.1.4: + dependencies: + d: 1.0.2 + ext: 1.7.0 + esast-util-from-estree@2.0.0: dependencies: '@types/estree-jsx': 1.0.5 @@ -4949,8 +6251,12 @@ snapshots: esast-util-from-estree: 2.0.0 vfile-message: 4.0.3 + escape-carriage@1.3.1: {} + escape-string-regexp@4.0.0: {} + escape-string-regexp@5.0.0: {} + eslint-config-next@15.4.1(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3): dependencies: '@next/eslint-plugin-next': 15.4.1 @@ -5130,6 +6436,13 @@ snapshots: transitivePeerDependencies: - supports-color + esniff@2.0.1: + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + event-emitter: 0.3.5 + type: 2.7.3 + espree@10.4.0: dependencies: acorn: 8.15.0 @@ -5181,8 +6494,17 @@ snapshots: esutils@2.0.3: {} + event-emitter@0.3.5: + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + eventemitter3@4.0.7: {} + ext@1.7.0: + dependencies: + type: 2.7.3 + extend@3.0.2: {} fast-deep-equal@3.1.3: {} @@ -5213,6 +6535,10 @@ snapshots: dependencies: reusify: 1.1.0 + fault@2.0.1: + dependencies: + format: 0.2.2 + fdir@6.4.6(picomatch@4.0.3): optionalDependencies: picomatch: 4.0.3 @@ -5255,6 +6581,8 @@ snapshots: hasown: 2.0.2 mime-types: 2.1.35 + format@0.2.2: {} + framer-motion@12.23.12(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: motion-dom: 12.23.12 @@ -5412,6 +6740,8 @@ snapshots: dependencies: react-is: 16.13.1 + ieee754@1.2.1: {} + ignore@5.3.2: {} ignore@7.0.5: {} @@ -5438,6 +6768,8 @@ snapshots: internmap@2.0.3: {} + intersection-observer@0.10.0: {} + intl-messageformat@10.7.16: dependencies: '@formatjs/ecma402-abstract': 2.3.4 @@ -5578,6 +6910,8 @@ snapshots: isexe@2.0.0: {} + isomorphic.js@0.2.5: {} + iterator.prototype@1.1.5: dependencies: define-data-property: 1.1.4 @@ -5616,6 +6950,8 @@ snapshots: dependencies: json-buffer: 3.0.1 + kleur@4.1.5: {} + language-subtag-registry@0.3.23: {} language-tags@1.0.9: @@ -5627,6 +6963,12 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 + lexical@0.35.0: {} + + lib0@0.2.114: + dependencies: + isomorphic.js: 0.2.5 + lightningcss-darwin-arm64@1.30.1: optional: true @@ -5696,12 +7038,16 @@ snapshots: dependencies: react: 19.1.0 + lz-string@1.5.0: {} + magic-string@0.30.17: dependencies: '@jridgewell/sourcemap-codec': 1.5.4 markdown-extensions@2.0.0: {} + markdown-table@3.0.4: {} + math-intrinsics@1.1.0: {} md5@2.3.0: @@ -5710,6 +7056,20 @@ snapshots: crypt: 0.0.2 is-buffer: 1.1.6 + mdast-util-directive@3.1.0: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + parse-entities: 4.0.2 + stringify-entities: 4.0.4 + unist-util-visit-parents: 6.0.1 + transitivePeerDependencies: + - supports-color + mdast-util-from-markdown@2.0.2: dependencies: '@types/mdast': 4.0.4 @@ -5727,6 +7087,48 @@ snapshots: transitivePeerDependencies: - supports-color + mdast-util-frontmatter@2.0.1: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + escape-string-regexp: 5.0.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + micromark-extension-frontmatter: 2.0.0 + 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-highlight-mark@1.2.2: + dependencies: + micromark-extension-highlight-mark: 1.2.0 + mdast-util-mdx-expression@2.0.1: dependencies: '@types/estree-jsx': 1.0.5 @@ -5830,6 +7232,57 @@ snapshots: micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 + micromark-extension-directive@3.0.2: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + parse-entities: 4.0.2 + + micromark-extension-frontmatter@2.0.0: + dependencies: + fault: 2.0.1 + micromark-util-character: 2.1.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-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-highlight-mark@1.2.0: + dependencies: + 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 + uvu: 0.5.6 + micromark-extension-mdx-expression@3.0.1: dependencies: '@types/estree': 1.0.8 @@ -6060,6 +7513,8 @@ snapshots: react: 19.1.0 react-dom: 19.1.0(react@19.1.0) + mri@1.2.0: {} + ms@2.1.3: {} nanoid@3.3.11: {} @@ -6102,6 +7557,8 @@ snapshots: react: 19.1.0 react-dom: 19.1.0(react@19.1.0) + next-tick@1.1.0: {} + next@15.4.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@next/env': 15.4.1 @@ -6125,6 +7582,13 @@ snapshots: - '@babel/core' - babel-plugin-macros + nuqs@2.6.0(next@15.4.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0): + dependencies: + '@standard-schema/spec': 1.0.0 + react: 19.1.0 + optionalDependencies: + next: 15.4.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + object-assign@4.1.1: {} object-inspect@1.13.4: {} @@ -6176,6 +7640,8 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 + outvariant@1.4.0: {} + own-keys@1.0.1: dependencies: get-intrinsic: 1.3.0 @@ -6232,6 +7698,8 @@ snapshots: prelude-ls@1.2.1: {} + prismjs@1.30.0: {} + prop-types@15.8.1: dependencies: loose-envify: 1.4.0 @@ -6246,17 +7714,30 @@ snapshots: queue-microtask@1.2.3: {} + react-devtools-inline@4.4.0: + dependencies: + es6-symbol: 3.1.4 + react-dom@19.1.0(react@19.1.0): dependencies: react: 19.1.0 scheduler: 0.26.0 + react-error-boundary@3.1.4(react@19.1.0): + dependencies: + '@babel/runtime': 7.28.4 + react: 19.1.0 + react-google-recaptcha-v3@1.11.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: hoist-non-react-statics: 3.3.2 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) + react-hook-form@7.63.0(react@19.1.0): + dependencies: + react: 19.1.0 + react-icons@5.5.0(react@19.1.0): dependencies: react: 19.1.0 @@ -6267,6 +7748,8 @@ snapshots: react-is@16.13.1: {} + react-is@17.0.2: {} + react-is@18.3.1: {} react-remove-scroll-bar@2.3.8(@types/react@19.1.8)(react@19.1.0): @@ -6453,6 +7936,10 @@ snapshots: dependencies: queue-microtask: 1.2.3 + sade@1.8.1: + dependencies: + mri: 1.2.0 + safe-array-concat@1.1.3: dependencies: call-bind: 1.0.8 @@ -6586,11 +8073,20 @@ snapshots: stable-hash@0.0.5: {} + static-browser-server@1.0.3: + dependencies: + '@open-draft/deferred-promise': 2.2.0 + dotenv: 16.6.1 + mime-db: 1.52.0 + outvariant: 1.4.0 + stop-iteration-iterator@1.1.0: dependencies: es-errors: 1.3.0 internal-slot: 1.1.0 + strict-event-emitter@0.4.6: {} + string.prototype.includes@2.0.1: dependencies: call-bind: 1.0.8 @@ -6650,6 +8146,8 @@ snapshots: strip-json-comments@3.1.1: {} + style-mod@4.1.2: {} + style-to-js@1.1.17: dependencies: style-to-object: 1.0.9 @@ -6669,6 +8167,8 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + tabbable@6.2.0: {} + tailwind-merge@3.3.1: {} tailwindcss@4.1.11: {} @@ -6720,6 +8220,8 @@ snapshots: type-fest@4.41.0: {} + type@2.7.3: {} + typed-array-buffer@1.0.3: dependencies: call-bound: 1.0.4 @@ -6766,6 +8268,10 @@ snapshots: undici-types@7.8.0: {} + unidiff@1.0.4: + dependencies: + diff: 5.2.0 + unified@11.0.5: dependencies: '@types/unist': 3.0.3 @@ -6868,6 +8374,13 @@ snapshots: dependencies: react: 19.1.0 + uvu@0.5.6: + dependencies: + dequal: 2.0.3 + diff: 5.2.0 + kleur: 4.1.5 + sade: 1.8.1 + vaul@1.1.2(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@radix-ui/react-dialog': 1.1.14(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -6909,6 +8422,8 @@ snapshots: d3-time: 3.1.0 d3-timer: 3.0.1 + w3c-keyname@2.2.8: {} + which-boxed-primitive@1.1.1: dependencies: is-bigint: 1.1.0 @@ -6960,6 +8475,10 @@ snapshots: yaml@2.8.0: {} + yjs@13.6.27: + dependencies: + lib0: 0.2.114 + yocto-queue@0.1.0: {} zod@4.1.8: {} diff --git a/web/src/api/post.ts b/web/src/api/post.ts index 2e29f0a..f45f68f 100644 --- a/web/src/api/post.ts +++ b/web/src/api/post.ts @@ -45,3 +45,13 @@ export async function listPosts({ }) return res.data } + +export async function updatePost({post}: {post: Post}): Promise> { + const res = await axiosClient.put>(`/post/p/${post.id}`, post) + return res.data +} + +export async function deletePost({id}: {id: number}): Promise { + const res = await axiosClient.delete(`/post/p/${id}`) + return res.data +} \ No newline at end of file diff --git a/web/src/app/auth/layout.tsx b/web/src/app/auth/layout.tsx new file mode 100644 index 0000000..2d4568c --- /dev/null +++ b/web/src/app/auth/layout.tsx @@ -0,0 +1,14 @@ + +export default function AuthLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( +
+
+ {children} +
+
+ ) +} \ No newline at end of file diff --git a/web/src/app/auth/login/page.tsx b/web/src/app/auth/login/page.tsx index a22f4e8..32b143a 100644 --- a/web/src/app/auth/login/page.tsx +++ b/web/src/app/auth/login/page.tsx @@ -1,40 +1,12 @@ -import { Suspense } from 'react' import { LoginForm } from '@/components/auth/login/login-form' import { AuthHeader } from '@/components/auth/common/auth-header' -function LoginPageContent() { - return ( -
-
- - -
-
- ) -} export default function LoginPage() { return ( - -
-
-
-
-
-
-
-
-
-
-
-
-
-
- - )} - > - -
+ <> + + + ) } diff --git a/web/src/app/auth/register/page.tsx b/web/src/app/auth/register/page.tsx index 2ada8ce..9e86879 100644 --- a/web/src/app/auth/register/page.tsx +++ b/web/src/app/auth/register/page.tsx @@ -1,40 +1,12 @@ -import { Suspense } from 'react' import { AuthHeader } from '@/components/auth/common/auth-header' import { RegisterForm } from '@/components/auth/register/register-form' -function PageContent() { - return ( -
-
- - -
-
- ) -} export default function Page() { return ( - -
-
-
-
-
-
-
-
-
-
-
-
-
-
- - )} - > - -
+ <> + + + ) } diff --git a/web/src/app/auth/reset-password/page.tsx b/web/src/app/auth/reset-password/page.tsx index 0639f0e..37b209e 100644 --- a/web/src/app/auth/reset-password/page.tsx +++ b/web/src/app/auth/reset-password/page.tsx @@ -1,12 +1,11 @@ import { AuthHeader } from "@/components/auth/common/auth-header"; import { ResetPasswordForm } from "@/components/auth/reset-password/reset-password-form"; + export default function Page() { return ( -
-
+ <> -
-
+ ) } \ No newline at end of file diff --git a/web/src/app/console/layout.tsx b/web/src/app/console/layout.tsx index 806e95e..925167c 100644 --- a/web/src/app/console/layout.tsx +++ b/web/src/app/console/layout.tsx @@ -54,7 +54,7 @@ export default function ConsoleLayout({ -
+
{children}
diff --git a/web/src/app/console/post/edit/[id]/page.tsx b/web/src/app/console/post/edit/[id]/page.tsx new file mode 100644 index 0000000..9c26389 --- /dev/null +++ b/web/src/app/console/post/edit/[id]/page.tsx @@ -0,0 +1,3 @@ +export default function EditPostPage() { + return
Edit Post Page
; +} \ No newline at end of file diff --git a/web/src/app/layout.tsx b/web/src/app/layout.tsx index fc192ec..17aace4 100644 --- a/web/src/app/layout.tsx +++ b/web/src/app/layout.tsx @@ -8,6 +8,7 @@ import config from "@/config"; import { getFirstLocale } from '@/i18n/request'; import { Toaster } from "@/components/ui/sonner" import { getLoginUser } from "@/api/user"; +import { NuqsAdapter } from 'nuqs/adapters/next/app' import "./globals.css"; const geistSans = Geist({ variable: "--font-geist-sans", @@ -31,7 +32,7 @@ export default async function RootLayout({ }>) { const token = (await cookies()).get("token")?.value || ""; const refreshToken = (await cookies()).get("refresh_token")?.value || ""; - const user = await getLoginUser({token, refreshToken}).then(res => res.data).catch(() => null); + const user = await getLoginUser({ token, refreshToken }).then(res => res.data).catch(() => null); return ( @@ -39,13 +40,15 @@ export default async function RootLayout({ className={`${geistSans.variable} ${geistMono.variable} antialiased`} > - - - - {children} - - - + + + + + {children} + + + + ); diff --git a/web/src/components/auth/common/auth-header.tsx b/web/src/components/auth/common/auth-header.tsx index 23f0d70..fd6fcbe 100644 --- a/web/src/components/auth/common/auth-header.tsx +++ b/web/src/components/auth/common/auth-header.tsx @@ -1,9 +1,11 @@ import config from "@/config"; import Image from "next/image"; +import Link from "next/link"; export function AuthHeader() { return ( -
+
+
{config.metadata.name} +
) } \ No newline at end of file diff --git a/web/src/components/auth/login/login-form.tsx b/web/src/components/auth/login/login-form.tsx index e0ff1df..71b81f0 100644 --- a/web/src/components/auth/login/login-form.tsx +++ b/web/src/components/auth/login/login-form.tsx @@ -5,7 +5,6 @@ import { Button } from "@/components/ui/button" import { Card, CardContent, - CardDescription, CardHeader, CardTitle, } from "@/components/ui/card" @@ -102,7 +101,7 @@ export function LoginForm({ {t("welcome")} - + {user && } {t("with_oidc")}
@@ -134,7 +133,7 @@ export function LoginForm({ )} {/* 邮箱密码登录 */}
-
+
setCredentials(c => ({ ...c, username: e.target.value }))} />
-
+
{/* 用户名 */} -
+
@@ -145,7 +145,7 @@ export function RegisterForm({ />
{/* 密码 */} -
+
@@ -158,7 +158,7 @@ export function RegisterForm({ />
{/* 邮箱 */} -
+
{/* 邮箱验证码 */} -
+
-
-
+
+
setNewPassword(e.target.value)} />
-
+
-
+
setEmail(e.target.value)} />
-
+
-
+
setVerifyCode(value)} /> + + +
+ {orderBys.map((ob) => ( + [true, false].map((desc) => ( + + )) + ))} +
+
+ + ) +} \ No newline at end of file diff --git a/web/src/components/common/pagination.tsx b/web/src/components/common/pagination.tsx index 726da68..64b1d3f 100644 --- a/web/src/components/common/pagination.tsx +++ b/web/src/components/common/pagination.tsx @@ -7,132 +7,234 @@ import { PaginationNext, PaginationPrevious, } from "@/components/ui/pagination" -import { useEffect, useState, useCallback } from "react" +import { useCallback, useEffect, useMemo, useState, forwardRef, useImperativeHandle, useRef } from "react" +import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover" +import { Button } from "@/components/ui/button" -export function PaginationController({ - initialPage = 1, - totalPages = 10, - buttons = 7, // recommended odd number >=5 - onPageChange, - ...props -}: { +interface PaginationControllerProps extends React.HTMLAttributes { + /** 总条目数(数据项个数,不是页数) */ + total: number + /** 每页大小 */ + pageSize?: number + /** 初始页(仅首次生效,之后内部自管理) */ initialPage?: number - totalPages: number - buttons?: number + /** 最多显示的按钮数(会强制为 >=5 的奇数) */ + maxButtons?: number + /** 页码变化回调(用户点击或自动校正时触发) */ onPageChange?: (page: number) => void -} & React.HTMLAttributes) { - const btns = Math.max(5, buttons ?? 7); - const buttonsToShow = totalPages < btns ? totalPages : btns; + /** 是否禁用交互 */ + disabled?: boolean + /** 仅一页时隐藏组件(total>0 且 totalPages===1) */ + hideOnSinglePage?: boolean + /** total 或 pageSize 变化导致越界时是否回调 onPageChange(默认 true) */ + reportAutoAdjust?: boolean +} - const [currentPage, setCurrentPage] = useState(() => Math.min(Math.max(1, initialPage ?? 1), Math.max(1, totalPages))); +export interface PaginationControllerHandle { + /** 以 1-based 设置当前页,会自动 clamp */ + setPage: (page: number) => void + /** 获取当前页(1-based) */ + getPage: () => number +} + +export const PaginationController = forwardRef(function PaginationController({ + total, + pageSize = 10, + initialPage = 1, + maxButtons = 7, + onPageChange, + disabled = false, + hideOnSinglePage = false, + reportAutoAdjust = true, + className, + ...rest +}, ref) { + // 规范化 maxButtons: 至少5 且为奇数 (便于居中) + const maxBtns = useMemo(() => { + const m = Math.max(5, maxButtons || 7) + return m % 2 === 0 ? m + 1 : m + }, [maxButtons]) + const totalPages = useMemo(() => Math.max(1, Math.ceil(total / Math.max(1, pageSize))), [total, pageSize]) + const [currentPage, setCurrentPage] = useState(() => clampPage(initialPage, totalPages)) + + // 暴露方法给父组件 + useImperativeHandle(ref, () => ({ + setPage: (p: number) => setCurrentPage(() => clampPage(p, totalPages)), + getPage: () => currentPage, + }), [currentPage, totalPages]) + // 越界校正(不直接通知父组件) useEffect(() => { - const p = Math.min(Math.max(1, initialPage ?? 1), Math.max(1, totalPages)); - setCurrentPage(p); - }, [initialPage, totalPages]); + setCurrentPage(prev => { + const clamped = clampPage(prev, totalPages) + return clamped === prev ? prev : clamped + }) + }, [totalPages]) + + // 统一向父组件报告变化,避免在 setState 的 updater 中直接调用父级 setState 引发警告 + const lastReportedRef = useRef(null) + useEffect(() => { + if (!onPageChange) return + if (lastReportedRef.current === currentPage) return + // 如果是自动校正且不希望报告,则跳过 + if (!reportAutoAdjust && lastReportedRef.current !== null && currentPage > totalPages) return + lastReportedRef.current = currentPage + onPageChange(currentPage) + }, [currentPage, onPageChange, reportAutoAdjust, totalPages]) const handleSetPage = useCallback((p: number) => { - const next = Math.min(Math.max(1, Math.floor(p)), Math.max(1, totalPages)); - setCurrentPage(next); - if (typeof onPageChange === 'function') onPageChange(next); - }, [onPageChange, totalPages]); + if (disabled) return + setCurrentPage(() => clampPage(p, totalPages)) + }, [disabled, totalPages]) - // helper to render page link - const renderPage = (pageNum: number) => ( - + // 计算要显示的页码集合 + const pages = useMemo(() => { + if (totalPages <= maxBtns) { + return { type: "all" as const, list: range(1, totalPages) } + } + const windowSize = maxBtns - 4 // 去掉首尾及两个潜在省略号 + let start = currentPage - Math.floor(windowSize / 2) + let end = start + windowSize - 1 + if (start < 3) { + start = 3 + end = start + windowSize - 1 + } + if (end > totalPages - 2) { + end = totalPages - 2 + start = end - windowSize + 1 + } + return { type: "window" as const, list: range(start, end), start, end, windowSize } + }, [currentPage, maxBtns, totalPages]) + + // total=0 的场景: 显示单个不可切换页 + if (total === 0) { + return ( +
+ + + + 1 + + + +
+ ) + } + + if (hideOnSinglePage && totalPages === 1) { + return null + } + + const renderPage = (p: number) => ( + handleSetPage(pageNum)} - type="button" + isActive={p === currentPage} + aria-current={p === currentPage ? "page" : undefined} + aria-label={`Go to page ${p}`} + onClick={(e) => { e.preventDefault(); handleSetPage(p) }} + tabIndex={disabled ? -1 : 0} > - {pageNum} + {p} - ); + ) - // if totalPages small, render all - if (totalPages <= buttonsToShow) { - return ( + const prevDisabled = disabled || currentPage === 1 + const nextDisabled = disabled || currentPage === totalPages + + return ( +
currentPage > 1 && handleSetPage(currentPage - 1)} + aria-disabled={prevDisabled} + aria-label="Previous page" + tabIndex={prevDisabled ? -1 : 0} + onClick={(e) => { if (prevDisabled) return; e.preventDefault(); handleSetPage(currentPage - 1) }} /> - {Array.from({ length: totalPages }).map((_, i) => renderPage(i + 1))} + + {pages.type === "all" && ( + pages.list.map(renderPage) + )} + + {pages.type === "window" && ( + <> + {renderPage(1)} + {/* 前省略号 */} + {pages.start! > 3 ? ( + + + + ) : renderPage(2)} + + {pages.list.map(renderPage)} + + {/* 后省略号 */} + {pages.end! < totalPages - 2 ? ( + + + + ) : renderPage(totalPages - 1)} + {renderPage(totalPages)} + + )} + currentPage < totalPages && handleSetPage(currentPage + 1)} + aria-disabled={nextDisabled} + aria-label="Next page" + tabIndex={nextDisabled ? -1 : 0} + onClick={(e) => { if (nextDisabled) return; e.preventDefault(); handleSetPage(currentPage + 1) }} /> - ); - } - - // for larger totalPages, show: 1, 2 or ellipsis, center range, ellipsis or N-1, N - const centerCount = buttonsToShow - 4; // slots for center pages - let start = currentPage - Math.floor(centerCount / 2); - let end = start + centerCount - 1; - if (start < 3) { - start = 3; - end = start + centerCount - 1; - } - if (end > totalPages - 2) { - end = totalPages - 2; - start = end - (centerCount - 1); - } - - const centerPages = [] as number[]; - for (let i = start; i <= end; i++) centerPages.push(i); - - return ( -
- - - - currentPage > 1 && handleSetPage(currentPage - 1)} /> - - - {renderPage(1)} - - {/* second slot: either page 2 or ellipsis if center starts later */} - {start > 3 ? ( - - - - ) : renderPage(2)} - - {/* center pages */} - {centerPages.map((p) => ( - - handleSetPage(p)} - type="button" - > - {p} - - - ))} - - {end < totalPages - 2 ? ( - - - - ) : renderPage(totalPages - 1)} - - {renderPage(totalPages)} - - - currentPage < totalPages && handleSetPage(currentPage + 1)} /> - - -
- ); + ) +}) + +// -------- helpers -------- +function clampPage(p: number, totalPages: number) { + if (Number.isNaN(p)) return 1 + return Math.min(Math.max(1, Math.floor(p)), Math.max(1, totalPages)) } + +function range(start: number, end: number) { + const arr: number[] = [] + for (let i = start; i <= end; i++) arr.push(i) + return arr +} + +export function PageSizeSelector({ initialSize, onSizeChange }: { initialSize?: number, onSizeChange: (size: number) => void }) { + const [open, setOpen] = useState(false) + const [size, setSize] = useState(initialSize || 10) + const sizeList = [10, 20, 30, 50, 100] + return ( + + + + + +
+ {sizeList.map((item) => ( + + ))} +
+
+
+ ) +} \ No newline at end of file diff --git a/web/src/components/common/theme-toggle.tsx b/web/src/components/common/theme-toggle.tsx index e957bed..3b09a43 100644 --- a/web/src/components/common/theme-toggle.tsx +++ b/web/src/components/common/theme-toggle.tsx @@ -74,15 +74,14 @@ export function ThemeModeSegmented(props: React.HTMLAttributes & ))}
-
+
); } // 总组件:根据设备类型渲染 -export function ThemeModeToggle(props: React.HTMLAttributes = {}) { - const { isMobile, mode, setMode } = useDevice(); - const Comp: React.ElementType = isMobile ? ThemeModeSegmented : ThemeModeCycleButton; +export function ThemeModeToggle(props: React.HTMLAttributes & { showSegmented?: boolean }) { + const { mode, setMode } = useDevice(); + const Comp: React.ElementType = props.showSegmented ? ThemeModeSegmented : ThemeModeCycleButton; const { className, style } = props; - // 仅转发 className / style,避免复杂的 prop 类型不匹配 return ; } diff --git a/web/src/components/console/app-sidebar.tsx b/web/src/components/console/app-sidebar.tsx index 9e3f694..09546ca 100644 --- a/web/src/components/console/app-sidebar.tsx +++ b/web/src/components/console/app-sidebar.tsx @@ -19,6 +19,7 @@ import config from "@/config" import Link from "next/link" import { NavUserCenter } from "./nav-ucenter" import { sidebarData } from "./data" +import { ThemeModeToggle } from "../common/theme-toggle" @@ -45,6 +46,9 @@ export function AppSidebar({ ...props }: React.ComponentProps) { +
+ +
diff --git a/web/src/components/console/dashboard/index.tsx b/web/src/components/console/dashboard/index.tsx index 1ab49c0..5cd5889 100644 --- a/web/src/components/console/dashboard/index.tsx +++ b/web/src/components/console/dashboard/index.tsx @@ -2,11 +2,11 @@ import { getDashboard, DashboardResp } from "@/api/admin" import { Card, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Eye, MessageCircle, Newspaper, Users } from "lucide-react" -import { JSX, useEffect, useState } from "react" +import { useEffect, useState } from "react" import { toast } from "sonner" -import { path } from "../data" import Link from "next/link" import { IconType } from "@/types/icon" +import { consolePath } from "@/hooks/use-route" export function Dashboard() { return ( @@ -22,25 +22,25 @@ function DataOverview() { key: "totalPosts", label: "Total Posts", icon: Newspaper, - url: path.post + url: consolePath.post }, { key: "totalUsers", label: "Total Users", icon: Users, - url: path.user + url: consolePath.user }, { key: "totalComments", label: "Total Comments", icon: MessageCircle, - url: path.comment + url: consolePath.comment }, { key: "totalViews", label: "Total Views", icon: Eye, - url: path.file + url: consolePath.file }, ] diff --git a/web/src/components/console/data.ts b/web/src/components/console/data.ts index d775ee5..195281b 100644 --- a/web/src/components/console/data.ts +++ b/web/src/components/console/data.ts @@ -1,3 +1,4 @@ +import { consolePath } from "@/hooks/use-route"; import type { User } from "@/models/user"; import { IconType } from "@/types/icon"; import { isAdmin, isEditor } from "@/utils/common/permission"; @@ -11,53 +12,41 @@ export interface SidebarItem { permission: ({ user }: { user: User }) => boolean; } -export const path = { - dashboard: "/console", - post: "/console/post", - comment: "/console/comment", - file: "/console/file", - user: "/console/user", - global: "/console/global", - userProfile: "/console/user-profile", - userSecurity: "/console/user-security", - userPreference: "/console/user-preference", -} - export const sidebarData: { navMain: SidebarItem[]; navUserCenter: SidebarItem[] } = { navMain: [ { title: "dashboard.title", - url: path.dashboard, + url: consolePath.dashboard, icon: Gauge, permission: isAdmin }, { title: "post.title", - url: path.post, + url: consolePath.post, icon: Newspaper, permission: isEditor }, { title: "comment.title", - url: path.comment, + url: consolePath.comment, icon: MessageCircle, permission: isEditor }, { title: "file.title", - url: path.file, + url: consolePath.file, icon: Folder, permission: () => true }, { title: "user.title", - url: path.user, + url: consolePath.user, icon: Users, permission: isAdmin }, { title: "global.title", - url: path.global, + url: consolePath.global, icon: Settings, permission: isAdmin }, @@ -65,19 +54,19 @@ export const sidebarData: { navMain: SidebarItem[]; navUserCenter: SidebarItem[] navUserCenter: [ { title: "user_profile.title", - url: path.userProfile, + url: consolePath.userProfile, icon: UserPen, permission: () => true }, { title: "user_security.title", - url: path.userSecurity, + url: consolePath.userSecurity, icon: ShieldCheck, permission: () => true }, { title: "user-preference.title", - url: path.userPreference, + url: consolePath.userPreference, icon: Palette, permission: () => true } diff --git a/web/src/components/console/nav-main.tsx b/web/src/components/console/nav-main.tsx index 30eb7ff..94311bc 100644 --- a/web/src/components/console/nav-main.tsx +++ b/web/src/components/console/nav-main.tsx @@ -14,6 +14,7 @@ import { User } from "@/models/user"; import { useAuth } from "@/contexts/auth-context"; import { IconType } from "@/types/icon"; import { useTranslations } from "next-intl"; +import { consolePath } from "@/hooks/use-route"; export function NavMain({ items, @@ -34,12 +35,12 @@ export function NavMain({ return ( - General + {t("general")} {items.map((item) => ( item.permission({ user }) && - + {item.icon && } {t(item.title)} diff --git a/web/src/components/console/nav-ucenter.tsx b/web/src/components/console/nav-ucenter.tsx index b170a20..0c61f3b 100644 --- a/web/src/components/console/nav-ucenter.tsx +++ b/web/src/components/console/nav-ucenter.tsx @@ -32,7 +32,7 @@ export function NavUserCenter({ return ( - Personal + {t("personal")} {items.map((item) => ( item.permission({ user }) && diff --git a/web/src/components/console/nav-user.tsx b/web/src/components/console/nav-user.tsx index 8baeb34..fa4bd44 100644 --- a/web/src/components/console/nav-user.tsx +++ b/web/src/components/console/nav-user.tsx @@ -32,8 +32,13 @@ import { import { getGravatarFromUser } from "@/utils/common/gravatar" import { formatDisplayName, getFallbackAvatarFromUsername } from "@/utils/common/username" import { useAuth } from "@/contexts/auth-context" +import { useTranslations } from "next-intl" +import { useToUserProfile } from "@/hooks/use-route" export function NavUser() { + const operationT = useTranslations("Operation"); + const routeT = useTranslations("Route"); + const clickToProfile = useToUserProfile(); const { isMobile } = useSidebar() const { user, logout } = useAuth(); @@ -86,9 +91,9 @@ export function NavUser() { - + {clickToProfile(user.username)}}> - Account + {routeT("profile")} @@ -102,7 +107,7 @@ export function NavUser() { - Log out + {operationT("logout")} diff --git a/web/src/components/console/post-manage/index.tsx b/web/src/components/console/post-manage/index.tsx index b8cfa09..3de8e75 100644 --- a/web/src/components/console/post-manage/index.tsx +++ b/web/src/components/console/post-manage/index.tsx @@ -1,29 +1,102 @@ "use client"; -import { listPosts } from "@/api/post"; +import { deletePost, listPosts, updatePost } from "@/api/post"; +import { OrderSelector } from "@/components/common/orderby-selector"; +import { PageSizeSelector, PaginationController } from "@/components/common/pagination"; +import { Button } from "@/components/ui/button"; +import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; +import { Input } from "@/components/ui/input"; import { Separator } from "@/components/ui/separator"; -import config from "@/config"; +import { useDevice } from "@/contexts/device-context"; +import { useDoubleConfirm } from "@/hooks/use-double-confirm"; +import { useToEditPost, useToPost } from "@/hooks/use-route"; import { OrderBy } from "@/models/common"; import { Post } from "@/models/post" -import { useEffect, useState } from "react"; +import { DropdownMenuGroup } from "@radix-ui/react-dropdown-menu"; +import { Ellipsis, Eye } from "lucide-react"; +import { useTranslations } from "next-intl"; +import { useCallback, useEffect, useState } from "react"; +import { toast } from "sonner"; +import { + useQueryState, + parseAsInteger, + parseAsBoolean, + parseAsStringEnum, + parseAsString +} from "nuqs"; +import { useDebouncedState } from "@/hooks/use-debounce"; + +const PAGE_SIZE = 15; +const MOBILE_PAGE_SIZE = 10; export function PostManage() { + const orderT = useTranslations("Order"); + const commonT = useTranslations("Common"); + const metricsT = useTranslations("Metrics"); + const { isMobile } = useDevice(); const [posts, setPosts] = useState([]); - const [orderBy, setOrderBy] = useState(OrderBy.CreatedAt); - const [desc, setDesc] = useState(true); - const [page, setPage] = useState(1); + const [total, setTotal] = useState(0); + const [orderBy, setOrderBy] = useQueryState("order_by", parseAsStringEnum(Object.values(OrderBy)).withDefault(OrderBy.CreatedAt).withOptions({ history: "replace", clearOnDefault: true })); + const [desc, setDesc] = useQueryState("desc", parseAsBoolean.withDefault(true).withOptions({ history: "replace", clearOnDefault: true })); + const [page, setPage] = useQueryState("page", parseAsInteger.withDefault(1).withOptions({ history: "replace", clearOnDefault: true })); + const [size, setSize] = useQueryState("size", parseAsInteger.withDefault(isMobile ? MOBILE_PAGE_SIZE : PAGE_SIZE).withOptions({ history: "replace", clearOnDefault: true })); + const [keywords, setKeywords] = useQueryState("keywords", parseAsString.withDefault("").withOptions({ history: "replace", clearOnDefault: true })); + const [keywordsInput, setKeywordsInput, debouncedKeywordsInput] = useDebouncedState(keywords, 200); useEffect(() => { - listPosts({ page, size: config.postsPerPage, orderBy, desc }).then(res => { - setPosts(res.data.posts); - }); - }, [page, orderBy, desc]); + listPosts({ page, size, orderBy, desc, keywords }). + then(res => { + setPosts(res.data.posts); + setTotal(res.data.total); + }); + }, [page, orderBy, desc, size, keywords]); + + useEffect(() => { + setKeywords(debouncedKeywordsInput) + }, [debouncedKeywordsInput, setKeywords, keywords]) + + const onPostUpdate = useCallback(({ post }: { post: Partial & Pick }) => { + setPosts((prev) => prev.map((p) => (p.id === post.id ? { ...p, ...post } : p))); + }, [setPosts]); + + const onOrderChange = useCallback(({ orderBy, desc }: { orderBy: OrderBy; desc: boolean }) => { + setOrderBy(orderBy); + setDesc(desc); + setPage(1); + }, [setOrderBy, setDesc, setPage]); + + const onPageChange = useCallback((p: number) => { + setPage(p); + }, [setPage]); return
- {posts.map(post => )} +
+
+ setKeywordsInput(e.target.value)} /> +
+
+
+
{orderT("order")}
+ {} +
+
+
+ + {posts.map(post =>
+ + +
)} +
+ {total > 0 && } + { setSize(s); setPage(1); }} /> {metricsT("per_page")} +
; } -function PostItem({ post }: { post: Post }) { +function PostItem({ post, onPostUpdate }: { post: Post, onPostUpdate?: ({ post }: { post: Partial & Pick }) => void }) { + const commonT = useTranslations("Common"); + const postT = useTranslations("Metrics"); + const stateT = useTranslations("State"); + const clickToPost = useToPost(); return (
@@ -32,16 +105,97 @@ function PostItem({ post }: { post: Post }) {
{post.title}
-
- ID: {post.id} - | - Created At: {new Date(post.createdAt).toLocaleDateString()} - | - Updated At: {new Date(post.updatedAt).toLocaleDateString()} +
+ {stateT(post.isPrivate ? "private" : "public")} + {postT("view_count")}: {post.viewCount} + {postT("like_count")}: {post.likeCount} + {postT("comment_count")}: {post.commentCount} + {commonT("id")}: {post.id} + {commonT("created_at")}: {new Date(post.createdAt).toLocaleDateString()} + {commonT("updated_at")}: {new Date(post.updatedAt).toLocaleDateString()}
+ {/* right */} +
+ + +
-
) +} + +function PostDropdownMenu({ post, onPostUpdate }: { post: Post, onPostUpdate?: ({ post }: { post: Partial & Pick }) => void }) { + const operationT = useTranslations("Operation"); + const clickToPostEdit = useToEditPost(); + const clickToPost = useToPost(); + const { confirming: confirmingDelete, onClick: onDeleteClick, onBlur: onDeleteBlur } = useDoubleConfirm(); + const [open, setOpen] = useState(false); + const handleTogglePrivate = () => { + updatePost({ post: { ...post, isPrivate: !post.isPrivate } }) + .then(() => { + toast.success(operationT("update_success")); + onPostUpdate?.({ post: { id: post.id, isPrivate: !post.isPrivate } }); + }) + .catch(() => { + toast.error(operationT("update_failed")); + }); + } + + const handleDelete = () => { + deletePost({ id: post.id }) + .then(() => { + toast.success(operationT("delete_success")); + onPostUpdate?.({ post: { id: post.id } }); + }) + .catch(() => { + toast.error(operationT("delete_failed")); + }); + }; + + return ( + { + setOpen(o); + if (!o) onDeleteBlur(); + }} + > + + + + + + clickToPostEdit({ post })} className="cursor-pointer" > + {operationT("edit")} + + clickToPost({ post })} className="cursor-pointer" > + {operationT("view")} + + + + + + {operationT(post.isPrivate ? "set_public" : "set_private")} + + { + if (!confirmingDelete) { + e.preventDefault(); + onDeleteClick(() => handleDelete()); + } else { + onDeleteClick(() => handleDelete()); + } + }} + className="text-red-600 hover:bg-red-600/10 focus:bg-red-600/10 cursor-pointer"> + {confirmingDelete ? operationT("confirm_delete") : operationT("delete")} + + + + + ) } \ No newline at end of file diff --git a/web/src/components/console/user-profile/index.tsx b/web/src/components/console/user-profile/index.tsx index 7a04516..ba68bf7 100644 --- a/web/src/components/console/user-profile/index.tsx +++ b/web/src/components/console/user-profile/index.tsx @@ -32,9 +32,11 @@ export function UserProfilePage() { const [username, setUsername] = useState(user?.username || '') const [avatarFile, setAvatarFile] = useState(null) const [avatarFileUrl, setAvatarFileUrl] = useState(null) // 这部分交由useEffect控制,监听 avatarFile 变化 + const [backgroundFile, setBackgroundFile] = useState(null) + const [backgroundFileUrl, setBackgroundFileUrl] = useState(null) const [submitting, setSubmitting] = useState(false) const [gender, setGender] = useState(user?.gender || '') - + useEffect(() => { if (!user) return; @@ -50,6 +52,20 @@ export function UserProfilePage() { }; }, [avatarFile, user]); + useEffect(() => { + if (!user) return; + if (!backgroundFile) { + setBackgroundFileUrl(null); + return; + } + const url = URL.createObjectURL(backgroundFile); + setBackgroundFileUrl(url); + return () => { + URL.revokeObjectURL(url); + setBackgroundFileUrl(null); + }; + }, [backgroundFile, user]); + const handlePictureSelected = (e: PictureInputChangeEvent): void => { const file: File | null = e.target.files?.[0] ?? null; if (!file) { @@ -67,12 +83,35 @@ export function UserProfilePage() { } if (file.size > constraints.maxSize) { setAvatarFile(null); - toast.error(t("picture_size_cannot_exceed", {"size": "5MiB"})); + toast.error(t("picture_size_cannot_exceed", { "size": "5MiB" })); return; } setAvatarFile(file); } + const handleBackgroundSelected = (e: PictureInputChangeEvent): void => { + const file: File | null = e.target.files?.[0] ?? null; + if (!file) { + setBackgroundFile(null); + return; + } + const constraints: UploadConstraints = { + allowedTypes: ['image/png', 'image/jpeg', 'image/webp', 'image/gif'], + maxSize: 5 * 1024 * 1024, // 5 MB + }; + if (!file.type || !file.type.startsWith('image/') || !constraints.allowedTypes.includes(file.type)) { + setBackgroundFile(null); + toast.error(t("only_allow_picture")); + return; + } + if (file.size > constraints.maxSize) { + setBackgroundFile(null); + toast.error(t("picture_size_cannot_exceed", { "size": "5MiB" })); + return; + } + setBackgroundFile(file); + } + const handleSubmit = () => { if (!user) return; if ( @@ -87,7 +126,7 @@ export function UserProfilePage() { (username.length < 1 || username.length > 20) || (nickname.length < 1 || nickname.length > 20) ) { - toast.error(t("nickname_and_username_must_be_between", {"min": 1, "max": 20})) + toast.error(t("nickname_and_username_must_be_between", { "min": 1, "max": 20 })) return } @@ -95,13 +134,15 @@ export function UserProfilePage() { username === user.username && nickname === user.nickname && gender === user.gender && - avatarFile === null + avatarFile === null && + backgroundFile === null ) { toast.warning(t("no_changes_made")) return } let avatarUrl = user.avatarUrl; + let backgroundUrl = user.backgroundUrl; setSubmitting(true); (async () => { if (avatarFile) { @@ -114,8 +155,18 @@ export function UserProfilePage() { } } + if (backgroundFile) { + try { + const resp = await uploadFile({ file: backgroundFile }); + backgroundUrl = getFileUri(resp.data.id); + } catch (error: unknown) { + toast.error(`${t("failed_to_upload_background")}: ${error}`); + return; + } + } + try { - await updateUser({ nickname, username, avatarUrl, gender, id: user.id }); + await updateUser({ nickname, username, avatarUrl, backgroundUrl, gender, id: user.id }); window.location.reload(); } catch (error: unknown) { toast.error(`${t("failed_to_update_profile")}: ${error}`); @@ -123,7 +174,7 @@ export function UserProfilePage() { setSubmitting(false); } })(); - + } const handleCropped = (blob: Blob) => { @@ -139,28 +190,40 @@ export function UserProfilePage() { {t("public_profile")} -
- - - {avatarFileUrl ? - : - } - {getFallbackAvatarFromUsername(nickname || username)} - -
- +
+
+ + + {avatarFileUrl ? + : + } + {getFallbackAvatarFromUsername(nickname || username)} + +
+ +
- - setNickname(e.target.value)} /> - - setUsername(e.target.value)} /> - - setGender(e.target.value)}/> +
+ + setNickname(e.target.value)} /> +
+
+ + setUsername(e.target.value)} /> +
+
+ + setGender(e.target.value)} /> +
+ + + +
diff --git a/web/src/components/console/user-security/index.tsx b/web/src/components/console/user-security/index.tsx index 1f44a92..4dd09a7 100644 --- a/web/src/components/console/user-security/index.tsx +++ b/web/src/components/console/user-security/index.tsx @@ -34,7 +34,7 @@ export function UserSecurityPage() { } const handleSendVerifyCode = () => { - requestEmailVerifyCode({email}) + requestEmailVerifyCode({ email }) .then(() => { toast.success(t("send_verify_code_success")) }) @@ -61,14 +61,20 @@ export function UserSecurityPage() { if (!user) return null; return (
-
+

{t("password_setting")}

- - setOldPassword(e.target.value)} /> - - setNewPassword(e.target.value)} /> +
+ + setOldPassword(e.target.value)} /> +
+
+ + setNewPassword(e.target.value)} /> +
+ +
{t("forgot_password_or_no_password")} @@ -76,19 +82,23 @@ export function UserSecurityPage() {
-
+

{t("email_setting")}

- -
- setEmail(e.target.value)} /> +
+ + setEmail(e.target.value)} />
- -
- setVerifyCode(value)} /> - + +
+ +
+ setVerifyCode(value)} /> + +
+
diff --git a/web/src/components/layout/nav/navbar-or-side.tsx b/web/src/components/layout/nav/navbar-or-side.tsx index 16726fa..0689404 100644 --- a/web/src/components/layout/nav/navbar-or-side.tsx +++ b/web/src/components/layout/nav/navbar-or-side.tsx @@ -171,7 +171,7 @@ function SidebarMenu() { )}
- +
diff --git a/web/src/components/ui/command.tsx b/web/src/components/ui/command.tsx new file mode 100644 index 0000000..8cb4ca7 --- /dev/null +++ b/web/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/web/src/components/ui/pagination.tsx b/web/src/components/ui/pagination.tsx index 0d18541..ac63428 100644 --- a/web/src/components/ui/pagination.tsx +++ b/web/src/components/ui/pagination.tsx @@ -77,7 +77,7 @@ function PaginationPrevious({ {...props} > - Previous + ) } @@ -93,7 +93,7 @@ function PaginationNext({ className={cn("gap-1 px-2.5 sm:pr-2.5", className)} {...props} > - Next + ) diff --git a/web/src/components/ui/popover.tsx b/web/src/components/ui/popover.tsx new file mode 100644 index 0000000..01e468b --- /dev/null +++ b/web/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/web/src/contexts/auth-context.tsx b/web/src/contexts/auth-context.tsx index 434c205..0c87b5b 100644 --- a/web/src/contexts/auth-context.tsx +++ b/web/src/contexts/auth-context.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { createContext, useContext, useState, useMemo, useEffect } from "react"; +import React, { createContext, useContext, useState, useMemo, useEffect, useCallback } from "react"; import type { User } from "@/models/user"; import { getLoginUser, userLogout } from "@/api/user"; import { useTranslations } from "next-intl"; @@ -34,15 +34,15 @@ export function AuthProvider({ } }, [user]); - const logout = () => { + const logout = useCallback(() => { userLogout().then(() => { toast.success(commonT("logout_success")); setUser(null); }).catch(() => { toast.error(commonT("logout_failed")); }); - }; - const value = useMemo(() => ({ user, setUser, logout }), [user]); + }, [commonT]); + const value = useMemo(() => ({ user, setUser, logout }), [user, logout]); return {children}; } diff --git a/web/src/hooks/use-debounce.ts b/web/src/hooks/use-debounce.ts new file mode 100644 index 0000000..f7a3a19 --- /dev/null +++ b/web/src/hooks/use-debounce.ts @@ -0,0 +1,105 @@ +import { useEffect, useRef, useState, useCallback } from "react" + +/** + * useDebouncedValue + * 返回一个在指定 delay 毫秒后稳定下来的值。 + * 当输入频繁变化时,只在用户停止输入 delay 时间后才更新。 + */ +export function useDebouncedValue(value: T, delay = 400) { + const [debounced, setDebounced] = useState(value) + const timerRef = useRef(null) + + useEffect(() => { + if (timerRef.current) window.clearTimeout(timerRef.current) + timerRef.current = window.setTimeout(() => { + setDebounced(value) + }, delay) + return () => { + if (timerRef.current) window.clearTimeout(timerRef.current) + } + }, [value, delay]) + + return debounced +} + +/** 防抖回调:返回一个稳定函数,调用时会在 delay 后真正触发 cb(后触发覆盖先触发)。*/ +export function useDebouncedCallback unknown>(cb: T, delay = 400) { + const cbRef = useRef(cb) + const timerRef = useRef(null) + cbRef.current = cb + return (...args: Parameters) => { + if (timerRef.current) window.clearTimeout(timerRef.current) + timerRef.current = window.setTimeout(() => { + cbRef.current(...args) + }, delay) + } +} + +interface UseDebouncedStateOptions { + /** 是否在首次 set 时立即更新(默认 false 延迟) */ + immediate?: boolean + /** 组件卸载时是否自动触发最后一次(默认 false) */ + flushOnUnmount?: boolean +} + +/** + * useDebouncedState + * 返回: [state, setState, debouncedState, controls] + * - state: 立即更新的本地值(用户输入实时) + * - setState: 设定本地值并启动防抖计时,到期后同步到 debouncedState + * - debouncedState: 稳定值(可用于副作用 / 请求) + * - controls: { flush, cancel } + */ +export function useDebouncedState( + initial: T, + delay = 400, + options: UseDebouncedStateOptions = {} +) { + const { immediate = false, flushOnUnmount = false } = options + const [state, setState] = useState(initial) + const [debounced, setDebounced] = useState(initial) + const timerRef = useRef(null) + const pendingRef = useRef(initial) + + const flush = useCallback(() => { + if (timerRef.current) { + window.clearTimeout(timerRef.current) + timerRef.current = null + } + setDebounced(pendingRef.current) + }, []) + + const cancel = useCallback(() => { + if (timerRef.current) { + window.clearTimeout(timerRef.current) + timerRef.current = null + } + }, []) + + const set = useCallback((val: T | ((prev: T) => T)) => { + setState(prev => { + const next = typeof val === 'function' ? (val as (p: T) => T)(prev) : val + pendingRef.current = next + if (timerRef.current) window.clearTimeout(timerRef.current) + if (immediate && debounced !== next && timerRef.current === null) { + setDebounced(next) + } else { + timerRef.current = window.setTimeout(() => { + timerRef.current = null + setDebounced(pendingRef.current) + }, delay) + } + return next + }) + }, [delay, immediate, debounced]) + + useEffect(() => () => { + if (flushOnUnmount && timerRef.current) { + flush() + } else if (timerRef.current) { + window.clearTimeout(timerRef.current) + } + }, [flushOnUnmount, flush]) + + return [state, set, debounced, { flush, cancel }] as const +} \ No newline at end of file diff --git a/web/src/hooks/use-route.ts b/web/src/hooks/use-route.ts index ee8de65..ce368bd 100644 --- a/web/src/hooks/use-route.ts +++ b/web/src/hooks/use-route.ts @@ -1,4 +1,5 @@ +import { Post } from "@/models/post" import { useRouter, usePathname } from "next/navigation" /** @@ -10,6 +11,18 @@ export const loginPath = authPath + "/login" export const registerPath = authPath + "/register" export const resetPasswordPath = authPath + "/reset-password" +export const consolePath = { + dashboard: "/console", + post: "/console/post", + comment: "/console/comment", + file: "/console/file", + user: "/console/user", + global: "/console/global", + userProfile: "/console/user-profile", + userSecurity: "/console/user-security", + userPreference: "/console/user-preference", +} + export function useToLogin() { const router = useRouter() const pathname = usePathname() @@ -25,3 +38,16 @@ export function useToUserProfile() { }; } +export function useToPost(){ + const router = useRouter(); + return ({post}:{post: Post}) => { + router.push(`/p/${post.id}`); + }; +} + +export function useToEditPost(){ + const router = useRouter(); + return ({post}:{post: Post}) => { + router.push(`${consolePath.post}/edit/${post.id}`); + }; +} \ No newline at end of file diff --git a/web/src/locales/zh-CN.json b/web/src/locales/zh-CN.json index 4371301..c78e5a8 100644 --- a/web/src/locales/zh-CN.json +++ b/web/src/locales/zh-CN.json @@ -54,8 +54,10 @@ "update": "更新" }, "Common": { + "created_at": "创建于", "email": "邮箱", "forgot_password": "忘记密码?", + "id": "编号", "login": "登录", "logout_failed": "退出登录失败", "logout_success": "退出登录成功", @@ -65,10 +67,12 @@ "obtain": "获取", "password": "密码", "register": "注册", + "search": "搜索", "secondsAgo": "秒前", "send_verify_code": "发送验证码", "submit": "提交", "update": "更新", + "updated_at": "更新于", "username": "用户名", "verify_code": "验证码" }, @@ -82,10 +86,12 @@ "file": { "title": "文件管理" }, + "general": "常规", "global": { "title": "全局配置" }, "login_required": "请先登录再进入后台", + "personal": "个人", "post": { "title": "文章管理" }, @@ -93,7 +99,7 @@ "title": "用户管理" }, "user_profile": { - "title": "个人资料", + "title": "资料设置", "edit": "编辑", "failed_to_upload_avatar": "上传头像失败", "failed_to_update_profile": "更新个人资料失败", @@ -155,6 +161,37 @@ "and": "和", "privacy_policy": "隐私政策" }, + "Metrics": { + "comment_count": "评论数", + "like_count": "点赞数", + "per_page": " /页", + "view_count": "浏览数" + }, + "Operation": { + "confirm_delete": "确认删除?", + "delete": "删除", + "delete_failed": "删除失败", + "delete_success": "删除成功", + "edit": "编辑", + "login": "登录", + "logout": "登出", + "previous": "上一页", + "next": "下一页", + "set_private": "设为私密", + "set_public": "设为公开", + "update_failed": "更新失败", + "update_success": "更新成功", + "view": "查看" + }, + "Order": { + "order": "排序", + "comment_count": "评论数", + "created_at": "创建时间", + "heat": "热度", + "like_count": "点赞数", + "updated_at": "更新时间", + "view_count": "浏览数" + }, "Register": { "title": "注册", "already_have_account": "已经有账号?", @@ -175,5 +212,12 @@ "send_verify_code_failed": "发送验证码失败", "send_verify_code_success": "验证码已发送", "verify_code": "验证码" + }, + "Route": { + "profile": "个人资料" + }, + "State": { + "private": "私密", + "public": "公开" } } \ No newline at end of file diff --git a/web/src/models/user.ts b/web/src/models/user.ts index 6b0fc6c..1177349 100644 --- a/web/src/models/user.ts +++ b/web/src/models/user.ts @@ -3,6 +3,7 @@ export interface User { username: string; nickname?: string; avatarUrl?: string; + backgroundUrl?: string; email: string; gender?: string; role: string; diff --git a/web/tailwind.config.js b/web/tailwind.config.js new file mode 100644 index 0000000..e69de29