@allbluecn/web-app 0.10.3 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +24 -11
- package/public/favicon-dark.svg +1 -0
- package/public/favicon.ico +0 -0
- package/public/favicon.svg +1 -0
- package/public/logo/allblue-logo-app-icon.svg +7 -0
- package/public/logo/allblue-logo-dark-square.svg +4 -0
- package/public/logo/allblue-logo-dark.png +0 -0
- package/public/logo/allblue-logo-dark.svg +4 -0
- package/public/logo/allblue-logo-light-square.svg +4 -0
- package/public/logo/allblue-logo-light.png +0 -0
- package/public/logo/allblue-logo-light.svg +4 -0
- package/src/__tests__/bench/baselines/format-timestamp.json +44 -0
- package/src/__tests__/bench/core-hot-path.bench.ts +71 -0
- package/src/__tests__/integration/db-crud.integration.test.ts +79 -0
- package/src/__tests__/integration/login-email-acceptance.test.ts +202 -0
- package/src/__tests__/integration/login-email-p2-acceptance.test.ts +142 -0
- package/src/__tests__/rejects-tothrow-regression.test.ts +31 -0
- package/src/__tests__/security/ai/prompt-injection.test.ts +105 -0
- package/src/__tests__/security/ssrf-url-whitelist.test.ts +54 -0
- package/src/__tests__/security/xss-sanitize.test.ts +99 -0
- package/src/__tests__/setup.ts +9 -0
- package/src/__tests__/visual/__screenshots__/theme-surface.vrt.test.tsx/theme-surface-dark-baseline-1-chromium-darwin.png +0 -0
- package/src/__tests__/visual/__screenshots__/theme-surface.vrt.test.tsx/theme-surface-light-baseline-1-chromium-darwin.png +0 -0
- package/src/__tests__/visual/__screenshots__/user-avatar.vrt.test.tsx/user-avatar-initial-fallback-baseline-1-chromium-darwin.png +0 -0
- package/src/__tests__/visual/theme-surface.vrt.test.tsx +54 -0
- package/src/__tests__/visual/user-avatar.vrt.test.tsx +32 -0
- package/src/components/auth/icon-input.tsx +9 -1
- package/src/components/auth/password-input.tsx +9 -1
- package/src/components/changelog/changelog-page.tsx +44 -1
- package/src/components/error/global-error-boundary.tsx +2 -2
- package/src/components/forms/app-field.tsx +3 -1
- package/src/components/knowledge/general-groups.ts +15 -0
- package/src/components/knowledge/kb-filter-popover.tsx +143 -0
- package/src/components/knowledge/knowledge-toolbar.tsx +17 -21
- package/src/components/knowledge-public/legal-compliance-editor.tsx +4 -2
- package/src/components/layout/sidebar-03/__tests__/nav-workspace.test.tsx +9 -6
- package/src/components/layout/sidebar-03/app-sidebar.tsx +4 -15
- package/src/components/layout/sidebar-03/nav-knowledge.tsx +19 -17
- package/src/components/layout/sidebar-03/nav-workspace.tsx +20 -22
- package/src/components/notifications/settings-notifications.tsx +41 -1
- package/src/components/settings/personal-team/account/settings-profile.tsx +68 -1
- package/src/components/shared-kit/avatars/notion-avatar.tsx +2 -1
- package/src/components/shared-kit/icons/__tests__/lucide-curated-icons.test.ts +2 -2
- package/src/components/shared-kit/icons/__tests__/lucide-icon-picker.test.tsx +3 -2
- package/src/components/shared-kit/icons/lucide-icon-picker/curated-icons.ts +5 -10
- package/src/components/shared-kit/icons/lucide-icon-picker/lucide-icon-by-name.tsx +3 -3
- package/src/components/shared-kit/theme/theme-surface.tsx +52 -0
- package/src/components/story/relations/general-groups.ts +1 -15
- package/src/components/story/relations/knowledge-select-dialog.tsx +13 -87
- package/src/components/story/story-list-view.tsx +7 -1
- package/src/components/tiptap/node/table-node/ui/table-move-row-column-button/use-table-move-row-column.ts +0 -2
- package/src/components/workspace/__tests__/activity/workspace-activity-card.test.tsx +36 -0
- package/src/components/workspace/__tests__/contribution/workspace-contribution-card.test.tsx +17 -0
- package/src/components/workspace/__tests__/dialogs/create-workspace-dialog.test.tsx +63 -0
- package/src/components/workspace/__tests__/dialogs/delete-workspace-options.test.tsx +89 -0
- package/src/components/workspace/__tests__/dialogs/edit-workspace-dialog.test.tsx +68 -0
- package/src/components/workspace/__tests__/header/workspace-header.test.tsx +40 -0
- package/src/components/workspace/__tests__/kb/workspace-kb-card.test.tsx +154 -0
- package/src/components/workspace/__tests__/members/workspace-member-item.test.tsx +24 -0
- package/src/components/workspace/__tests__/members/workspace-members-manager.test.tsx +51 -0
- package/src/components/workspace/__tests__/projects/workspace-project-list.test.tsx +46 -0
- package/src/components/workspace/{create-workspace-dialog.tsx → dialogs/create-workspace-dialog.tsx} +1 -1
- package/src/components/workspace/{edit-workspace-dialog.tsx → dialogs/edit-workspace-dialog.tsx} +1 -1
- package/src/components/workspace/kb/workspace-kb-card.tsx +594 -0
- package/src/components/workspace/kb/workspace-kb-manage-dialog.tsx +174 -0
- package/src/components/workspace/kb/workspace-quick-doc-dialog.tsx +431 -0
- package/src/components/workspace/{workspace-members-manager.tsx → members/workspace-members-manager.tsx} +78 -40
- package/src/lib/__tests__/edition-desktop.test.ts +23 -0
- package/src/lib/__tests__/msw-sample.test.ts +68 -0
- package/src/lib/__tests__/pgvector.test.ts +2 -2
- package/src/lib/app-version.ts +1 -1
- package/src/lib/auth/auth.config.ts +123 -99
- package/src/lib/changelog/sdk-versions.ts +21 -21
- package/src/lib/collections/knowledge/knowledge-bases-collection.ts +3 -1
- package/src/lib/collections/reference/workspaces-collection.ts +3 -1
- package/src/lib/edition-desktop.ts +22 -0
- package/src/lib/fetch-json.ts +23 -0
- package/src/{components/workspace/workspace-kb-card.tsx → lib/i18n/resolver.ts} +6 -12
- package/src/lib/sanitize.ts +14 -1
- package/src/lib/session.ts +10 -2
- package/src/lib/vector-store/__tests__/factory.test.ts +1 -1
- package/src/lib/vector-store/__tests__/sqlite-vec-store.test.ts +2 -2
- package/src/plugins/core/tags/__tests__/tags.test.ts +10 -10
- package/src/routeTree.gen.ts +114 -0
- package/src/routes/__root.tsx +20 -3
- package/src/routes/__tests__/-auth-schemas.test.ts +62 -0
- package/src/routes/__tests__/-edition-routing.test.tsx +18 -5
- package/src/routes/__tests__/route-tree-structure.test.ts +66 -0
- package/src/routes/_login/changelog/confirm/route.lazy.tsx +72 -0
- package/src/routes/_login/changelog/confirm/route.tsx +28 -0
- package/src/routes/_login/email-unsubscribe/route.lazy.tsx +76 -0
- package/src/routes/_login/email-unsubscribe/route.tsx +28 -0
- package/src/routes/_login/forgot-password/route.lazy.tsx +2 -1
- package/src/routes/_login/login/route.lazy.tsx +4 -2
- package/src/routes/_login/register/route.lazy.tsx +28 -17
- package/src/routes/_login/register/route.tsx +8 -1
- package/src/routes/_login/reset-password/route.lazy.tsx +4 -2
- package/src/routes/_login/schemas/-auth.ts +30 -7
- package/src/routes/_login/verify-email/route.lazy.tsx +82 -0
- package/src/routes/_login/verify-email/route.tsx +28 -0
- package/src/routes/_nav/knowledge/compliance/index.lazy.tsx +5 -2
- package/src/routes/_nav/knowledge/index.lazy.tsx +1 -1
- package/src/routes/_nav/knowledge/laws/index.lazy.tsx +5 -2
- package/src/routes/_nav/route.tsx +26 -0
- package/src/routes/_nav/workspaces/$workspaceId/route.lazy.tsx +13 -11
- package/src/routes/_nav/workspaces/$workspaceId/route.tsx +4 -2
- package/src/routes/_nav/workspaces/$workspaceId/settings/route.lazy.tsx +1 -1
- package/src/routes/_nav/workspaces/index.lazy.tsx +3 -3
- package/src/routes/_public/index/route.tsx +2 -1
- package/src/routes/api/auth/desktop-callback.ts +2 -2
- package/src/routes/api/billing-webhook.ts +4 -0
- package/src/routes/api/cron/changelog-release.ts +100 -0
- package/src/routes/api/cron/subscription-reminders.ts +110 -0
- package/src/routes/api.auth.$.ts +3 -5
- package/src/server/__tests__/agent-crud.test.ts +4 -4
- package/src/server/__tests__/auth-registration.test.ts +60 -0
- package/src/server/__tests__/chat-conversations.test.ts +1 -3
- package/src/server/__tests__/fetch-guard.test.ts +99 -0
- package/src/server/__tests__/permissions.test.ts +1 -3
- package/src/server/__tests__/resources.team-seats.test.ts +2 -0
- package/src/server/__tests__/skill-crud.test.ts +3 -3
- package/src/server/ai-gateway/__tests__/ai-output-guard.test.ts +95 -0
- package/src/server/ai-gateway/__tests__/kb-context-guard.test.ts +136 -0
- package/src/server/ai-gateway/__tests__/knowledge-search-fence.test.ts +240 -0
- package/src/server/ai-gateway/__tests__/llm-fence.test.ts +125 -0
- package/src/server/ai-gateway/__tests__/run-store.test.ts +52 -0
- package/src/server/ai-gateway/ai-output-guard.ts +77 -0
- package/src/server/ai-gateway/index.ts +5 -0
- package/src/server/ai-gateway/kb-context-guard.ts +46 -0
- package/src/server/ai-gateway/llm-fence.ts +82 -0
- package/src/server/ai-gateway/persistence/run-store.ts +19 -0
- package/src/server/ai-gateway/story-prompt.ts +4 -2
- package/src/server/ai-gateway/tools/definitions.ts +12 -6
- package/src/server/ai-gateway/tools/server.ts +97 -54
- package/src/server/auth-registration.ts +40 -0
- package/src/server/billing-email.ts +39 -0
- package/src/server/builtin-project.ts +21 -0
- package/src/server/email/__tests__/deliverability.test.ts +68 -0
- package/src/server/email/__tests__/index.test.ts +32 -0
- package/src/server/email/__tests__/outbox.test.tsx +129 -0
- package/src/server/email/__tests__/product-templates.test.tsx +106 -0
- package/src/server/email/__tests__/registry.test.tsx +50 -0
- package/src/server/email/deliverability.ts +57 -0
- package/src/server/email/drivers/console.ts +11 -0
- package/src/server/email/drivers/resend.ts +31 -0
- package/src/server/email/drivers/smtp.ts +27 -0
- package/src/server/email/index.ts +56 -0
- package/src/server/email/outbox.ts +141 -0
- package/src/server/email/preferences.ts +20 -0
- package/src/server/email/registry.ts +38 -0
- package/src/server/email/sender.ts +36 -13
- package/src/server/email/templates/_base/button.tsx +12 -0
- package/src/server/email/templates/_base/email-layout.tsx +117 -0
- package/src/server/email/templates/_base/theme-fonts.tsx +43 -0
- package/src/server/email/templates/_base/theme.ts +22 -0
- package/src/server/email/templates/auth/reset-password.tsx +58 -0
- package/src/server/email/templates/auth/verify-email.tsx +59 -0
- package/src/server/email/templates/auth/welcome.tsx +49 -0
- package/src/server/email/templates/billing/payment-receipt.tsx +51 -0
- package/src/server/email/templates/billing/subscription-canceled.tsx +51 -0
- package/src/server/email/templates/billing/subscription-ending.tsx +51 -0
- package/src/server/email/templates/notification/mirror.tsx +50 -0
- package/src/server/email/templates/product/changelog-confirm.tsx +53 -0
- package/src/server/email/templates/product/changelog-release.tsx +65 -0
- package/src/server/email/templates/team/apply-notify.tsx +53 -0
- package/src/server/email/templates/team/approved.tsx +53 -0
- package/src/server/email/templates/team/invite.tsx +57 -0
- package/src/server/fetch-guard.ts +170 -0
- package/src/server/middlewares/security-headers.ts +122 -0
- package/src/server/notifications/__tests__/email-mirror.test.ts +193 -0
- package/src/server/notifications/__tests__/verify-reminder.test.ts +94 -0
- package/src/server/notifications/notification-service.ts +71 -1
- package/src/server/notifications/verify-reminder.ts +55 -0
- package/src/server/oauth-config.ts +59 -0
- package/src/server/serverFns/__tests__/auth-server-schemas.test.ts +168 -0
- package/src/server/serverFns/__tests__/billing.test.ts +263 -0
- package/src/server/serverFns/__tests__/email-verification.test.ts +149 -0
- package/src/server/serverFns/__tests__/link-preview.test.ts +239 -0
- package/src/server/serverFns/__tests__/project-kb.test.ts +3 -3
- package/src/server/serverFns/__tests__/project-team-assign.test.ts +1 -1
- package/src/server/serverFns/__tests__/story-comments.test.ts +265 -0
- package/src/server/serverFns/__tests__/story-crud-archive-detail.test.ts +266 -0
- package/src/server/serverFns/__tests__/story-crud-labels-users.test.ts +227 -0
- package/src/server/serverFns/__tests__/story-crud.test.ts +526 -0
- package/src/server/serverFns/__tests__/story-links.test.ts +262 -0
- package/src/server/serverFns/__tests__/story-list-query.test.ts +259 -0
- package/src/server/serverFns/__tests__/story-reactions.test.ts +230 -0
- package/src/server/serverFns/__tests__/story-tree.test.ts +317 -0
- package/src/server/serverFns/__tests__/story-types.test.ts +294 -0
- package/src/server/serverFns/__tests__/team-accept.test.ts +6 -6
- package/src/server/serverFns/__tests__/team-invitation.test.ts +5 -5
- package/src/server/serverFns/__tests__/team-join-request.test.ts +8 -8
- package/src/server/serverFns/__tests__/update-seats.test.ts +4 -4
- package/src/server/serverFns/__tests__/workspace-kb-idor.test.ts +160 -0
- package/src/server/serverFns/__tests__/workspace-kb-utils.test.ts +54 -0
- package/src/server/serverFns/__tests__/workspace-kb.test.ts +348 -0
- package/src/server/serverFns/__tests__/workspace.test.ts +28 -8
- package/src/server/serverFns/auth/email-verification.ts +132 -0
- package/src/server/serverFns/auth/forgot-password.ts +22 -2
- package/src/server/serverFns/auth/providers.ts +4 -3
- package/src/server/serverFns/auth/register.ts +63 -19
- package/src/server/serverFns/auth/registration-mode.ts +23 -0
- package/src/server/serverFns/auth/reset-password.ts +1 -1
- package/src/server/serverFns/auth/{invitation.ts → verify-reminder.ts} +9 -26
- package/src/server/serverFns/changelog/__tests__/subscribe.test.ts +119 -0
- package/src/server/serverFns/changelog/subscribe.ts +89 -0
- package/src/server/serverFns/email-unsubscribe.ts +66 -0
- package/src/server/serverFns/iteration/__tests__/iteration-crud.test.ts +3 -5
- package/src/server/serverFns/iteration/__tests__/iteration-stories.test.ts +1 -1
- package/src/server/serverFns/iteration/__tests__/iteration-transfer.test.ts +2 -2
- package/src/server/serverFns/kb-doc-search.ts +111 -0
- package/src/server/serverFns/knowledge-public/compliance/create.ts +2 -1
- package/src/server/serverFns/knowledge-public/legal/create.ts +2 -1
- package/src/server/serverFns/notifications.ts +10 -0
- package/src/server/serverFns/story/__tests__/bulk.test.ts +1 -1
- package/src/server/serverFns/story/__tests__/label-suggest-fn.test.ts +2 -6
- package/src/server/serverFns/story/__tests__/story-assoc.test.ts +2 -4
- package/src/server/serverFns/story/__tests__/story-dependencies.test.ts +2 -2
- package/src/server/serverFns/story/__tests__/story-groups.test.ts +2 -2
- package/src/server/serverFns/story/kb-general-consts.ts +39 -0
- package/src/server/serverFns/story/kb-general.ts +30 -17
- package/src/server/serverFns/story/story-assoc.ts +13 -103
- package/src/server/serverFns/story/story-crud.ts +2 -5
- package/src/server/serverFns/story/story-groups.ts +4 -13
- package/src/server/serverFns/team.ts +50 -12
- package/src/server/serverFns/workspace-kb-utils.ts +138 -0
- package/src/server/serverFns/workspace-kb.ts +403 -0
- package/src/server/serverFns/workspace.ts +4 -1
- package/src/shared/ai-cli/__tests__/adapter.test.ts +67 -0
- package/src/shared/ai-cli/__tests__/cli-detector.test.ts +99 -0
- package/src/shared/ai-cli/adapter.ts +2 -2
- package/src/shared/ai-cli/cli-detector.ts +18 -9
- package/src/start.ts +2 -1
- package/src/styles/globals.css +114 -0
- package/src/types/jest-dom-vitest.d.ts +8 -7
- package/vite.shared.ts +105 -4
- package/public/file.svg +0 -1
- package/public/globe.svg +0 -1
- package/public/window.svg +0 -1
- package/src/lib/__tests__/cli-detector.test.ts +0 -80
- /package/src/components/workspace/{workspace-activity-card.tsx → activity/workspace-activity-card.tsx} +0 -0
- /package/src/components/workspace/{workspace-contribution-card.tsx → contribution/workspace-contribution-card.tsx} +0 -0
- /package/src/components/workspace/{delete-workspace-options.tsx → dialogs/delete-workspace-options.tsx} +0 -0
- /package/src/components/workspace/{workspace-header.tsx → header/workspace-header.tsx} +0 -0
- /package/src/components/workspace/{workspace-member-item.tsx → members/workspace-member-item.tsx} +0 -0
- /package/src/components/workspace/{workspace-project-list.tsx → projects/workspace-project-list.tsx} +0 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// AllBlue - 理想之海
|
|
3
|
+
// Copyright (C) 2026 AllBlue Contributors
|
|
4
|
+
//
|
|
5
|
+
// This program is free software: you can redistribute it and/or modify
|
|
6
|
+
// it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
// the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
// (at your option) any later version.
|
|
9
|
+
//
|
|
10
|
+
// This program is distributed in the hope that it will be useful,
|
|
11
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
// GNU Affero General Public License for more details.
|
|
14
|
+
//
|
|
15
|
+
// You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
// VRT 视觉回归:主题表面聚合组件的亮色/暗色渲染基线。
|
|
19
|
+
// 基线由 darwin 本机生成并提交入库;本机即门禁环境(同一台机器生成+对比)。
|
|
20
|
+
|
|
21
|
+
import { expect, test } from 'vitest'
|
|
22
|
+
import { page } from 'vitest/browser'
|
|
23
|
+
import { render } from 'vitest-browser-react'
|
|
24
|
+
import { ThemeProvider } from 'next-themes'
|
|
25
|
+
import { TooltipProvider } from '@/components/ui/tooltip'
|
|
26
|
+
import { ThemeSurface } from '@/components/shared-kit/theme/theme-surface'
|
|
27
|
+
|
|
28
|
+
test('theme surface light baseline', async () => {
|
|
29
|
+
await render(
|
|
30
|
+
<ThemeProvider forcedTheme="light">
|
|
31
|
+
<TooltipProvider>
|
|
32
|
+
<ThemeSurface />
|
|
33
|
+
</TooltipProvider>
|
|
34
|
+
</ThemeProvider>,
|
|
35
|
+
)
|
|
36
|
+
await expect(page.getByTestId('theme-surface')).toMatchScreenshot({
|
|
37
|
+
comparatorName: 'pixelmatch',
|
|
38
|
+
comparatorOptions: { threshold: 0.2, allowedMismatchedPixelRatio: 0.01 },
|
|
39
|
+
})
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
test('theme surface dark baseline', async () => {
|
|
43
|
+
await render(
|
|
44
|
+
<ThemeProvider forcedTheme="dark">
|
|
45
|
+
<TooltipProvider>
|
|
46
|
+
<ThemeSurface />
|
|
47
|
+
</TooltipProvider>
|
|
48
|
+
</ThemeProvider>,
|
|
49
|
+
)
|
|
50
|
+
await expect(page.getByTestId('theme-surface')).toMatchScreenshot({
|
|
51
|
+
comparatorName: 'pixelmatch',
|
|
52
|
+
comparatorOptions: { threshold: 0.2, allowedMismatchedPixelRatio: 0.01 },
|
|
53
|
+
})
|
|
54
|
+
})
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// AllBlue - 理想之海
|
|
3
|
+
// Copyright (C) 2026 AllBlue Contributors
|
|
4
|
+
//
|
|
5
|
+
// This program is free software: you can redistribute it and/or modify
|
|
6
|
+
// it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
// the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
// (at your option) any later version.
|
|
9
|
+
//
|
|
10
|
+
// This program is distributed in the hope that it will be useful,
|
|
11
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
// GNU Affero General Public License for more details.
|
|
14
|
+
//
|
|
15
|
+
// You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
// VRT 视觉回归:UserAvatar 首字母 fallback 渲染基线。
|
|
19
|
+
|
|
20
|
+
import { expect, test } from 'vitest'
|
|
21
|
+
import { page } from 'vitest/browser'
|
|
22
|
+
import { render } from 'vitest-browser-react'
|
|
23
|
+
import { UserAvatar } from '@/components/shared-kit'
|
|
24
|
+
|
|
25
|
+
test('user avatar initial fallback baseline', async () => {
|
|
26
|
+
const screen = await render(<UserAvatar name="张三" size="lg" />)
|
|
27
|
+
const root = screen.container.firstChild as HTMLElement
|
|
28
|
+
await expect(page.elementLocator(root)).toMatchScreenshot({
|
|
29
|
+
comparatorName: 'pixelmatch',
|
|
30
|
+
comparatorOptions: { threshold: 0.2, allowedMismatchedPixelRatio: 0.01 },
|
|
31
|
+
})
|
|
32
|
+
})
|
|
@@ -22,9 +22,10 @@ import type { LucideIcon } from 'lucide-react'
|
|
|
22
22
|
interface IconInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
|
23
23
|
icon: LucideIcon
|
|
24
24
|
label: string
|
|
25
|
+
error?: string | null
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
export function IconInput({ icon: Icon, label, id, className, ...props }: IconInputProps) {
|
|
28
|
+
export function IconInput({ icon: Icon, label, id, className, error, ...props }: IconInputProps) {
|
|
28
29
|
const inputId = id || label.toLowerCase()
|
|
29
30
|
|
|
30
31
|
return (
|
|
@@ -34,12 +35,19 @@ export function IconInput({ icon: Icon, label, id, className, ...props }: IconIn
|
|
|
34
35
|
<Input
|
|
35
36
|
id={inputId}
|
|
36
37
|
className={`peer ps-9 ${className || ''}`}
|
|
38
|
+
aria-invalid={!!error}
|
|
39
|
+
aria-describedby={error ? `${inputId}-error` : undefined}
|
|
37
40
|
{...props}
|
|
38
41
|
/>
|
|
39
42
|
<div className="pointer-events-none absolute inset-y-0 start-0 flex items-center justify-center ps-3 text-muted-foreground">
|
|
40
43
|
<Icon size={16} />
|
|
41
44
|
</div>
|
|
42
45
|
</div>
|
|
46
|
+
{error ? (
|
|
47
|
+
<p id={`${inputId}-error`} className="text-xs text-destructive mt-1">
|
|
48
|
+
{error}
|
|
49
|
+
</p>
|
|
50
|
+
) : null}
|
|
43
51
|
</div>
|
|
44
52
|
)
|
|
45
53
|
}
|
|
@@ -23,9 +23,10 @@ import { m } from "@/paraglide/messages.js";
|
|
|
23
23
|
|
|
24
24
|
interface PasswordInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'type'> {
|
|
25
25
|
label: string
|
|
26
|
+
error?: string | null
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
export function PasswordInput({ label, id, className, ...props }: PasswordInputProps) {
|
|
29
|
+
export function PasswordInput({ label, id, className, error, ...props }: PasswordInputProps) {
|
|
29
30
|
const inputId = id || label.toLowerCase()
|
|
30
31
|
const [isVisible, setIsVisible] = useState(false)
|
|
31
32
|
|
|
@@ -37,6 +38,8 @@ export function PasswordInput({ label, id, className, ...props }: PasswordInputP
|
|
|
37
38
|
id={inputId}
|
|
38
39
|
className={`ps-9 pe-9 ${className || ''}`}
|
|
39
40
|
type={isVisible ? 'text' : 'password'}
|
|
41
|
+
aria-invalid={!!error}
|
|
42
|
+
aria-describedby={error ? `${inputId}-error` : undefined}
|
|
40
43
|
{...props}
|
|
41
44
|
/>
|
|
42
45
|
<div className="pointer-events-none absolute inset-y-0 start-0 flex items-center justify-center ps-3 text-muted-foreground">
|
|
@@ -61,6 +64,11 @@ export function PasswordInput({ label, id, className, ...props }: PasswordInputP
|
|
|
61
64
|
)}
|
|
62
65
|
</button>
|
|
63
66
|
</div>
|
|
67
|
+
{error ? (
|
|
68
|
+
<p id={`${inputId}-error`} className="text-xs text-destructive mt-1">
|
|
69
|
+
{error}
|
|
70
|
+
</p>
|
|
71
|
+
) : null}
|
|
64
72
|
</div>
|
|
65
73
|
)
|
|
66
74
|
}
|
|
@@ -23,6 +23,8 @@ import { Route } from '@/routes/_nav/changelog/route'
|
|
|
23
23
|
import type { DeveloperLogEntry } from '@allbluecn/kernel'
|
|
24
24
|
import { SDK_GROUPS } from '@/lib/changelog/sdk-versions'
|
|
25
25
|
import { APP_FULL_VERSION, APP_UPDATE_DATE_DISPLAY } from '@/lib/app-version'
|
|
26
|
+
import { subscribeChangelogFn } from '@/server/serverFns/changelog/subscribe'
|
|
27
|
+
import { toast } from '@/components/ui/sonner'
|
|
26
28
|
import { m } from "@/paraglide/messages.js";
|
|
27
29
|
|
|
28
30
|
const TAG_KEYS = ['all', 'feat', 'fix', 'improvement', 'design', 'refactor', 'breaking', 'docs', 'security', 'perf']
|
|
@@ -51,10 +53,39 @@ const REQUIRES_ACTION_TAGS = new Set(['breaking'])
|
|
|
51
53
|
function ChangelogSidebar() {
|
|
52
54
|
const [activeGroup, setActiveGroup] = useState<string | null>(null)
|
|
53
55
|
const [isOverflowing, setIsOverflowing] = useState(false)
|
|
56
|
+
const [email, setEmail] = useState('')
|
|
57
|
+
const [subscribing, setSubscribing] = useState(false)
|
|
54
58
|
const outerRef = useRef<HTMLDivElement>(null)
|
|
55
59
|
const isOverflowingRef = useRef(false)
|
|
56
60
|
const expandedHeightRef = useRef(0)
|
|
57
61
|
|
|
62
|
+
const handleSubscribe = async () => {
|
|
63
|
+
const trimmed = email.trim()
|
|
64
|
+
if (!trimmed || subscribing) return
|
|
65
|
+
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmed)) {
|
|
66
|
+
toast.error(m.common_changelog_subscribeInvalidEmail())
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
setSubscribing(true)
|
|
70
|
+
try {
|
|
71
|
+
const result = await subscribeChangelogFn({ data: { email: trimmed } })
|
|
72
|
+
if (result.status === 'already') {
|
|
73
|
+
toast.info(m.common_changelog_subscribeAlready())
|
|
74
|
+
} else {
|
|
75
|
+
toast.success(m.common_changelog_subscribeCheckEmail())
|
|
76
|
+
}
|
|
77
|
+
setEmail('')
|
|
78
|
+
} catch (err) {
|
|
79
|
+
toast.error(
|
|
80
|
+
err instanceof Error && /email/i.test(err.message)
|
|
81
|
+
? m.common_changelog_subscribeInvalidEmail()
|
|
82
|
+
: m.common_changelog_subscribeFailed(),
|
|
83
|
+
)
|
|
84
|
+
} finally {
|
|
85
|
+
setSubscribing(false)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
58
89
|
const checkOverflow = useCallback(() => {
|
|
59
90
|
if (!outerRef.current) return
|
|
60
91
|
|
|
@@ -146,10 +177,22 @@ function ChangelogSidebar() {
|
|
|
146
177
|
<div className="mt-3 flex gap-1.5">
|
|
147
178
|
<input
|
|
148
179
|
type="email"
|
|
180
|
+
value={email}
|
|
181
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
182
|
+
onKeyDown={(e) => {
|
|
183
|
+
if (e.key === 'Enter') {
|
|
184
|
+
e.preventDefault()
|
|
185
|
+
handleSubscribe()
|
|
186
|
+
}
|
|
187
|
+
}}
|
|
149
188
|
placeholder={m.common_changelog_subscribe_placeholder()}
|
|
150
189
|
className="flex-1 min-w-0 h-8 rounded-md border border-border bg-background px-3 text-xs placeholder:text-muted-foreground/70 focus:outline-none focus:ring-2 focus:ring-primary/20 transition-shadow"
|
|
151
190
|
/>
|
|
152
|
-
<button
|
|
191
|
+
<button
|
|
192
|
+
onClick={handleSubscribe}
|
|
193
|
+
disabled={subscribing || !email.trim()}
|
|
194
|
+
className="h-8 px-3 rounded-md bg-primary text-primary-foreground text-xs font-medium hover:bg-primary/90 transition-colors shrink-0 disabled:opacity-50 disabled:pointer-events-none"
|
|
195
|
+
>
|
|
153
196
|
{m.common_changelog_subscribe_submit()}
|
|
154
197
|
</button>
|
|
155
198
|
</div>
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
import { Link, useLocation } from '@tanstack/react-router'
|
|
19
19
|
import { useMemo } from 'react'
|
|
20
|
-
import { LogIn,
|
|
20
|
+
import { LogIn, CircleAlert } from 'lucide-react'
|
|
21
21
|
import { isUnauthorizedError } from '@/lib/auth/anonymous-safe'
|
|
22
22
|
import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from '@/components/ui/empty'
|
|
23
23
|
import { Button } from '@/components/ui/button'
|
|
@@ -41,7 +41,7 @@ function GlobalErrorBoundaryInner({ error, info }: { error: unknown; info?: { co
|
|
|
41
41
|
<Empty>
|
|
42
42
|
<EmptyHeader>
|
|
43
43
|
<EmptyMedia variant="icon">
|
|
44
|
-
{unauthorized ? <LogIn /> : <
|
|
44
|
+
{unauthorized ? <LogIn className="size-5" /> : <CircleAlert className="size-5" />}
|
|
45
45
|
</EmptyMedia>
|
|
46
46
|
<EmptyTitle>
|
|
47
47
|
{unauthorized ? m.common_errorPage_loginRequired() : m.common_errorPage_error()}
|
|
@@ -37,6 +37,7 @@ export interface AppFieldProps<TValue> {
|
|
|
37
37
|
description?: string
|
|
38
38
|
validators?: AppFieldValidators<TValue>
|
|
39
39
|
children?: (field: AnyFieldApi) => ReactNode
|
|
40
|
+
renderError?: boolean
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
export function AppField<TValue>({
|
|
@@ -46,6 +47,7 @@ export function AppField<TValue>({
|
|
|
46
47
|
description,
|
|
47
48
|
validators,
|
|
48
49
|
children,
|
|
50
|
+
renderError = true,
|
|
49
51
|
}: AppFieldProps<TValue>) {
|
|
50
52
|
return (
|
|
51
53
|
<Field>
|
|
@@ -57,7 +59,7 @@ export function AppField<TValue>({
|
|
|
57
59
|
children={(field) => (
|
|
58
60
|
<>
|
|
59
61
|
{children ? children(field) : <DefaultInput field={field} />}
|
|
60
|
-
<AppFieldError field={field} />
|
|
62
|
+
{renderError && <AppFieldError field={field} />}
|
|
61
63
|
</>
|
|
62
64
|
)}
|
|
63
65
|
/>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { m } from '@/paraglide/messages.js'
|
|
2
|
+
|
|
3
|
+
const GROUP_LABELS: Record<string, () => string> = {
|
|
4
|
+
legal: m.story_knowledgeDialog_groupLegal,
|
|
5
|
+
compliance: m.story_knowledgeDialog_groupCompliance,
|
|
6
|
+
dictionary: m.story_knowledgeDialog_groupDictionary,
|
|
7
|
+
bookmark: m.story_knowledgeDialog_groupBookmark,
|
|
8
|
+
summary: m.story_knowledgeDialog_groupSummary,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** 通用知识库分组 key → 标签(未知 key 原样返回) */
|
|
12
|
+
export function generalGroupLabel(group: string): string {
|
|
13
|
+
const labelFn = GROUP_LABELS[group]
|
|
14
|
+
return labelFn ? labelFn() : group
|
|
15
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// AllBlue - 理想之海
|
|
3
|
+
// Copyright (C) 2026 AllBlue Contributors
|
|
4
|
+
//
|
|
5
|
+
// This program is free software: you can redistribute it and/or modify
|
|
6
|
+
// it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
// the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
// (at your option) any later version.
|
|
9
|
+
//
|
|
10
|
+
// This program is distributed in the hope that it will be useful,
|
|
11
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
// GNU Affero General Public License for more details.
|
|
14
|
+
//
|
|
15
|
+
// You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
import * as React from "react";
|
|
19
|
+
import { BookOpenText, Bookmark, BookMarked, Check, ChevronsUpDown, Scale, ShieldCheck, Sprout } from "lucide-react";
|
|
20
|
+
import { Button } from "@/components/ui/button";
|
|
21
|
+
import { Input } from "@/components/ui/input";
|
|
22
|
+
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
|
23
|
+
import { LucideIconByName } from "@/components/shared-kit/icons/lucide-icon-picker/lucide-icon-by-name";
|
|
24
|
+
import { generalGroupLabel } from "./general-groups";
|
|
25
|
+
import { m } from "@/paraglide/messages.js";
|
|
26
|
+
|
|
27
|
+
export type KbFilter = { type: "kb"; id: string } | { type: "general"; group: string } | null;
|
|
28
|
+
|
|
29
|
+
const GENERAL_GROUP_ICONS: Record<string, React.ComponentType<{ className?: string }>> = {
|
|
30
|
+
legal: Scale,
|
|
31
|
+
compliance: ShieldCheck,
|
|
32
|
+
dictionary: BookMarked,
|
|
33
|
+
bookmark: Bookmark,
|
|
34
|
+
summary: Sprout,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const GENERAL_GROUP_META = [
|
|
38
|
+
{ key: "legal" },
|
|
39
|
+
{ key: "compliance" },
|
|
40
|
+
{ key: "dictionary" },
|
|
41
|
+
{ key: "bookmark" },
|
|
42
|
+
{ key: "summary" },
|
|
43
|
+
] as const;
|
|
44
|
+
|
|
45
|
+
interface KbFilterPopoverProps {
|
|
46
|
+
search: string;
|
|
47
|
+
onSearchChange: (q: string) => void;
|
|
48
|
+
filter: KbFilter;
|
|
49
|
+
onFilterChange: (f: KbFilter) => void;
|
|
50
|
+
userKbs: Array<{ id: string; name: string; icon?: string | null }>;
|
|
51
|
+
/** 文案覆盖(缺省用 workspace_kb_* 通用文案);story 弹窗传入保持原文案零变化 */
|
|
52
|
+
searchPlaceholder?: string;
|
|
53
|
+
allLabel?: string;
|
|
54
|
+
userKbsLabel?: string;
|
|
55
|
+
generalKbsLabel?: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** 搜索框 + 知识库筛选下拉(用户 team 库 + 通用知识库两组)。story 关联弹窗与工作空间快捷文档弹窗共用。 */
|
|
59
|
+
export function KbFilterPopover({
|
|
60
|
+
search, onSearchChange, filter, onFilterChange, userKbs,
|
|
61
|
+
searchPlaceholder, allLabel, userKbsLabel, generalKbsLabel,
|
|
62
|
+
}: KbFilterPopoverProps) {
|
|
63
|
+
const [open, setOpen] = React.useState(false);
|
|
64
|
+
const activeKb = filter?.type === "kb" ? userKbs.find((k) => k.id === filter.id) : undefined;
|
|
65
|
+
|
|
66
|
+
const select = (f: KbFilter) => {
|
|
67
|
+
onFilterChange(f);
|
|
68
|
+
setOpen(false);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<div className="flex items-center gap-2">
|
|
73
|
+
<Input
|
|
74
|
+
placeholder={searchPlaceholder ?? m.workspace_kb_searchPlaceholder()}
|
|
75
|
+
value={search}
|
|
76
|
+
onChange={(e) => onSearchChange(e.target.value)}
|
|
77
|
+
className="h-8"
|
|
78
|
+
/>
|
|
79
|
+
<Popover open={open} onOpenChange={setOpen}>
|
|
80
|
+
<PopoverTrigger asChild>
|
|
81
|
+
<Button variant="outline" size="sm" className="h-8 shrink-0 gap-1">
|
|
82
|
+
<BookOpenText className="size-3.5" />
|
|
83
|
+
{activeKb
|
|
84
|
+
? activeKb.name
|
|
85
|
+
: filter?.type === "general"
|
|
86
|
+
? generalGroupLabel(filter.group)
|
|
87
|
+
: (allLabel ?? m.workspace_kb_allKbs())}
|
|
88
|
+
<ChevronsUpDown className="size-3.5 opacity-50" />
|
|
89
|
+
</Button>
|
|
90
|
+
</PopoverTrigger>
|
|
91
|
+
<PopoverContent align="end" className="w-60 p-1">
|
|
92
|
+
<button
|
|
93
|
+
type="button"
|
|
94
|
+
className="mt-1 flex w-full items-center justify-between rounded-sm px-2 py-1.5 text-sm hover:bg-muted"
|
|
95
|
+
onClick={() => select(null)}
|
|
96
|
+
>
|
|
97
|
+
{allLabel ?? m.workspace_kb_allKbs()}
|
|
98
|
+
{filter === null && <Check className="size-3.5" />}
|
|
99
|
+
</button>
|
|
100
|
+
<div className="border-t px-2 pt-3 text-xs font-medium text-muted-foreground">
|
|
101
|
+
{userKbsLabel ?? m.workspace_kb_userKbs()}
|
|
102
|
+
</div>
|
|
103
|
+
{userKbs.map((kb) => (
|
|
104
|
+
<button
|
|
105
|
+
key={kb.id}
|
|
106
|
+
type="button"
|
|
107
|
+
className="flex w-full items-center justify-between rounded-sm px-2 py-1.5 text-sm hover:bg-muted"
|
|
108
|
+
onClick={() => select({ type: "kb", id: kb.id })}
|
|
109
|
+
>
|
|
110
|
+
<span className="flex min-w-0 items-center gap-2">
|
|
111
|
+
{kb.icon && (
|
|
112
|
+
<LucideIconByName name={kb.icon} size={14} className="shrink-0 text-muted-foreground" />
|
|
113
|
+
)}
|
|
114
|
+
<span className="truncate">{kb.name}</span>
|
|
115
|
+
</span>
|
|
116
|
+
{filter?.type === "kb" && filter.id === kb.id && <Check className="size-3.5" />}
|
|
117
|
+
</button>
|
|
118
|
+
))}
|
|
119
|
+
<div className="border-t px-2 pt-3 text-xs font-medium text-muted-foreground">
|
|
120
|
+
{generalKbsLabel ?? m.workspace_kb_generalKbs()}
|
|
121
|
+
</div>
|
|
122
|
+
{GENERAL_GROUP_META.map((group) => {
|
|
123
|
+
const Icon = GENERAL_GROUP_ICONS[group.key];
|
|
124
|
+
return (
|
|
125
|
+
<button
|
|
126
|
+
key={group.key}
|
|
127
|
+
type="button"
|
|
128
|
+
className="flex w-full items-center justify-between rounded-sm px-2 py-1.5 text-sm hover:bg-muted"
|
|
129
|
+
onClick={() => select({ type: "general", group: group.key })}
|
|
130
|
+
>
|
|
131
|
+
<span className="flex min-w-0 items-center gap-2">
|
|
132
|
+
<Icon className="size-3.5 shrink-0 text-muted-foreground" />
|
|
133
|
+
<span className="truncate">{generalGroupLabel(group.key)}</span>
|
|
134
|
+
</span>
|
|
135
|
+
{filter?.type === "general" && filter.group === group.key && <Check className="size-3.5" />}
|
|
136
|
+
</button>
|
|
137
|
+
);
|
|
138
|
+
})}
|
|
139
|
+
</PopoverContent>
|
|
140
|
+
</Popover>
|
|
141
|
+
</div>
|
|
142
|
+
);
|
|
143
|
+
}
|
|
@@ -339,8 +339,8 @@ export function KnowledgeToolbar({ kb, onUpdate, breadcrumb, docType, treeCollap
|
|
|
339
339
|
>
|
|
340
340
|
{kb.name}
|
|
341
341
|
</BreadcrumbLink>
|
|
342
|
-
<BreadcrumbSeparator />
|
|
343
342
|
</BreadcrumbItem>
|
|
343
|
+
<BreadcrumbSeparator />
|
|
344
344
|
<BreadcrumbItem>
|
|
345
345
|
<DropdownMenu>
|
|
346
346
|
<DropdownMenuTrigger className="flex items-center text-sm text-muted-foreground hover:text-foreground">
|
|
@@ -357,8 +357,8 @@ export function KnowledgeToolbar({ kb, onUpdate, breadcrumb, docType, treeCollap
|
|
|
357
357
|
))}
|
|
358
358
|
</DropdownMenuContent>
|
|
359
359
|
</DropdownMenu>
|
|
360
|
-
<BreadcrumbSeparator />
|
|
361
360
|
</BreadcrumbItem>
|
|
361
|
+
<BreadcrumbSeparator />
|
|
362
362
|
{breadcrumb.slice(-2).map((item, index, arr) => {
|
|
363
363
|
const isLast = index === arr.length - 1
|
|
364
364
|
return (
|
|
@@ -368,16 +368,14 @@ export function KnowledgeToolbar({ kb, onUpdate, breadcrumb, docType, treeCollap
|
|
|
368
368
|
{item.title}
|
|
369
369
|
</BreadcrumbPage>
|
|
370
370
|
) : (
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
</BreadcrumbLink>
|
|
378
|
-
<BreadcrumbSeparator />
|
|
379
|
-
</>
|
|
371
|
+
<BreadcrumbLink
|
|
372
|
+
className="text-sm font-semibold cursor-pointer hover:text-foreground"
|
|
373
|
+
onClick={() => onNavigate?.(item.id)}
|
|
374
|
+
>
|
|
375
|
+
{item.title}
|
|
376
|
+
</BreadcrumbLink>
|
|
380
377
|
)}
|
|
378
|
+
{!isLast && <BreadcrumbSeparator />}
|
|
381
379
|
</BreadcrumbItem>
|
|
382
380
|
)
|
|
383
381
|
})}
|
|
@@ -391,8 +389,8 @@ export function KnowledgeToolbar({ kb, onUpdate, breadcrumb, docType, treeCollap
|
|
|
391
389
|
>
|
|
392
390
|
{kb.name}
|
|
393
391
|
</BreadcrumbLink>
|
|
394
|
-
<BreadcrumbSeparator />
|
|
395
392
|
</BreadcrumbItem>
|
|
393
|
+
<BreadcrumbSeparator />
|
|
396
394
|
{breadcrumb.map((item, index) => {
|
|
397
395
|
const isLast = index === breadcrumb.length - 1
|
|
398
396
|
return (
|
|
@@ -402,16 +400,14 @@ export function KnowledgeToolbar({ kb, onUpdate, breadcrumb, docType, treeCollap
|
|
|
402
400
|
{item.title}
|
|
403
401
|
</BreadcrumbPage>
|
|
404
402
|
) : (
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
</BreadcrumbLink>
|
|
412
|
-
<BreadcrumbSeparator />
|
|
413
|
-
</>
|
|
403
|
+
<BreadcrumbLink
|
|
404
|
+
className="text-sm font-semibold cursor-pointer hover:text-foreground"
|
|
405
|
+
onClick={() => onNavigate?.(item.id)}
|
|
406
|
+
>
|
|
407
|
+
{item.title}
|
|
408
|
+
</BreadcrumbLink>
|
|
414
409
|
)}
|
|
410
|
+
{!isLast && <BreadcrumbSeparator />}
|
|
415
411
|
</BreadcrumbItem>
|
|
416
412
|
)
|
|
417
413
|
})}
|
|
@@ -197,8 +197,9 @@ export function LegalComplianceEditor({ type, editingId }: Props) {
|
|
|
197
197
|
} else {
|
|
198
198
|
await updateComplianceFn({ data: payload })
|
|
199
199
|
}
|
|
200
|
+
qc.invalidateQueries({ queryKey: [type] })
|
|
200
201
|
},
|
|
201
|
-
[editingId, type],
|
|
202
|
+
[editingId, type, qc],
|
|
202
203
|
)
|
|
203
204
|
|
|
204
205
|
const handlePropertyChange = useCallback((key: string, value: unknown) => {
|
|
@@ -237,9 +238,10 @@ export function LegalComplianceEditor({ type, editingId }: Props) {
|
|
|
237
238
|
} else {
|
|
238
239
|
await updateComplianceFn({ data: payload })
|
|
239
240
|
}
|
|
241
|
+
qc.invalidateQueries({ queryKey: [type] })
|
|
240
242
|
},
|
|
241
243
|
}),
|
|
242
|
-
[editingId, type, saveField],
|
|
244
|
+
[editingId, type, saveField, qc],
|
|
243
245
|
)
|
|
244
246
|
|
|
245
247
|
if (!record) {
|
|
@@ -4,7 +4,7 @@ import { overwriteGetLocale } from "@/paraglide/runtime.js";
|
|
|
4
4
|
|
|
5
5
|
overwriteGetLocale(() => "zh");
|
|
6
6
|
|
|
7
|
-
const
|
|
7
|
+
const mockUseQuery = vi.fn();
|
|
8
8
|
const mockUseLocation = vi.fn();
|
|
9
9
|
const mockUseSidebar = vi.fn();
|
|
10
10
|
|
|
@@ -13,10 +13,12 @@ vi.mock("@tanstack/react-router", () => ({
|
|
|
13
13
|
<a href={to}>{children}</a>
|
|
14
14
|
),
|
|
15
15
|
useLocation: () => mockUseLocation(),
|
|
16
|
+
useRouter: () => ({ navigate: vi.fn() }),
|
|
16
17
|
}));
|
|
17
18
|
|
|
18
|
-
vi.mock("@tanstack/react-
|
|
19
|
-
|
|
19
|
+
vi.mock("@tanstack/react-query", () => ({
|
|
20
|
+
useQuery: (...args: any[]) => mockUseQuery(...args),
|
|
21
|
+
useQueryClient: () => ({ invalidateQueries: vi.fn() }),
|
|
20
22
|
}));
|
|
21
23
|
|
|
22
24
|
vi.mock("@/components/ui/sidebar", () => ({
|
|
@@ -52,10 +54,11 @@ vi.mock("@/lib/utils", () => ({
|
|
|
52
54
|
}));
|
|
53
55
|
|
|
54
56
|
vi.mock("@/lib/collections/reference/workspaces-collection", () => ({
|
|
55
|
-
|
|
57
|
+
WORKSPACES_QUERY_KEY: ["workspaces"],
|
|
56
58
|
}));
|
|
57
59
|
|
|
58
60
|
vi.mock("@/server/serverFns/workspace", () => ({
|
|
61
|
+
listPinnedWorkspacesBriefFn: vi.fn().mockResolvedValue([]),
|
|
59
62
|
toggleWorkspacePinFn: vi.fn().mockResolvedValue({ success: true }),
|
|
60
63
|
}));
|
|
61
64
|
|
|
@@ -68,7 +71,7 @@ beforeEach(() => {
|
|
|
68
71
|
cleanup();
|
|
69
72
|
mockUseLocation.mockReturnValue({ pathname: "/workspaces" });
|
|
70
73
|
mockUseSidebar.mockReturnValue({ state: "expanded" });
|
|
71
|
-
|
|
74
|
+
mockUseQuery.mockReturnValue({
|
|
72
75
|
data: [
|
|
73
76
|
{ id: "ws1", name: "工作空间 A", icon: "folder" },
|
|
74
77
|
{ id: "ws2", name: "工作空间 B", icon: "folder-kanban" },
|
|
@@ -103,7 +106,7 @@ describe("NavWorkspace", () => {
|
|
|
103
106
|
});
|
|
104
107
|
|
|
105
108
|
it("空 collection 时不渲染工作空间列表项", async () => {
|
|
106
|
-
|
|
109
|
+
mockUseQuery.mockReturnValue({ data: [] });
|
|
107
110
|
render(<NavWorkspace />);
|
|
108
111
|
await waitFor(() => {
|
|
109
112
|
expect(screen.queryByTestId("project-icon")).not.toBeInTheDocument();
|
|
@@ -24,7 +24,6 @@ import {
|
|
|
24
24
|
useSidebar,
|
|
25
25
|
} from "@/components/ui/sidebar";
|
|
26
26
|
import { cn } from "@/lib/utils";
|
|
27
|
-
import { m } from "motion/react";
|
|
28
27
|
import DashboardNavigation from "@/components/layout/sidebar-03/nav-main";
|
|
29
28
|
import { NotificationsBell } from "@/components/layout/sidebar-03/nav-notifications";
|
|
30
29
|
import { NavAnyHub } from "@/components/layout/sidebar-03/nav-anyhub";
|
|
@@ -57,15 +56,10 @@ export function DashboardSidebar() {
|
|
|
57
56
|
{isCollapsed ? (
|
|
58
57
|
<div className="flex flex-col items-center gap-2">
|
|
59
58
|
<LogoIcon className="h-6 w-6" />
|
|
60
|
-
<
|
|
61
|
-
className="flex flex-col-reverse items-center gap-1"
|
|
62
|
-
initial={{ opacity: 0 }}
|
|
63
|
-
animate={{ opacity: 1 }}
|
|
64
|
-
transition={{ duration: 0.8 }}
|
|
65
|
-
>
|
|
59
|
+
<div className="flex flex-col-reverse items-center gap-1">
|
|
66
60
|
<NotificationsBell />
|
|
67
61
|
<SidebarTrigger />
|
|
68
|
-
</
|
|
62
|
+
</div>
|
|
69
63
|
</div>
|
|
70
64
|
) : (
|
|
71
65
|
<>
|
|
@@ -78,17 +72,12 @@ export function DashboardSidebar() {
|
|
|
78
72
|
{/* <Badge className="h-4 text-[10px] relative -top-4 -left-9 ">v0</Badge> */}
|
|
79
73
|
</div>
|
|
80
74
|
</a>
|
|
81
|
-
<
|
|
82
|
-
className="flex items-center gap-2"
|
|
83
|
-
initial={{ opacity: 0 }}
|
|
84
|
-
animate={{ opacity: 1 }}
|
|
85
|
-
transition={{ duration: 0.8 }}
|
|
86
|
-
>
|
|
75
|
+
<div className="flex items-center gap-2">
|
|
87
76
|
<NotificationsBell />
|
|
88
77
|
<AnimatedThemeToggler />
|
|
89
78
|
|
|
90
79
|
<SidebarTrigger />
|
|
91
|
-
</
|
|
80
|
+
</div>
|
|
92
81
|
</>
|
|
93
82
|
)}
|
|
94
83
|
</SidebarHeader>
|