From 2b18b8b2a2ac649abce2b7270411b3536c9c1963 Mon Sep 17 00:00:00 2001 From: KiriAky 107 Date: Tue, 8 Sep 2026 00:01:03 +0800 Subject: [PATCH] =?UTF-8?q?fix(desktop):=20=E6=89=A9=E5=85=85=E5=BA=94?= =?UTF-8?q?=E7=94=A8=E8=8F=9C=E5=8D=95=E4=B8=8E=E5=85=83=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=BF=AB=E6=8D=B7=E9=94=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../警告框与桌面编辑命令开发说明.md | 4 + frontend/src-tauri/src/main.rs | 6 +- .../components/common/TitleBarMenu.spec.ts | 34 ++++ .../src/components/common/TitleBarMenu.vue | 161 ++++++++++++++++-- frontend/src/features/editor/EditorHeader.vue | 4 +- .../src/services/editorCommandService.spec.ts | 9 +- frontend/src/services/editorCommandService.ts | 4 +- frontend/src/stores/workspace.ts | 10 +- frontend/src/stores/workspaceRefresh.spec.ts | 12 ++ 9 files changed, 220 insertions(+), 24 deletions(-) diff --git a/docs/development/警告框与桌面编辑命令开发说明.md b/docs/development/警告框与桌面编辑命令开发说明.md index 37b39b5..b4faa41 100644 --- a/docs/development/警告框与桌面编辑命令开发说明.md +++ b/docs/development/警告框与桌面编辑命令开发说明.md @@ -98,3 +98,7 @@ npm run build 警告框使用二级菜单,支持方向键、Home、End、Esc、Tab 和子菜单左右键。菜单采用 `menubar`、`menu`、`menuitem` / `menuitemradio` 语义,主题颜色全部来自现有设计变量。 桌面 WebView 的 CSP 在 `script-src` 中仅增加 `'wasm-unsafe-eval'`,用于初始化 Shiki 的 Oniguruma WASM;普通 `'unsafe-eval'` 仍保持禁用。回归测试同时检查发行配置的 CSP 约束,以及真实挂载的 Milkdown 代码块在 GitHub Light / Dark 下生成多种 `.shiki-token` 颜色。 + +文件菜单承载新建根目录笔记/文件夹、切换知识库、返回工作区、刷新文件树、保存、导出、下载 Markdown 副本和关闭当前笔记。桌面端导出只保留在文件菜单,Web 端继续保留编辑器标题栏入口。视图菜单提供现有工作区、搜索、对话、智能体、任务、音视频、Skill、Plugin 和 MCP 页面入口;帮助菜单提供日志、Benchmark、社区与设置诊断。切换知识库前先等待当前笔记安全保存,成功打开后再清理旧知识库标签页。 + +Tauri 的 `CmdOrCtrl+Alt+P` accelerator 根据 `editor.import-note-properties` 与 `editor.metadata.edit` 的联合能力启用。源码模式执行属性导入,写作模式创建或聚焦元数据区域,避免原生禁用项在 Windows 上提前吞掉写作模式快捷键。 diff --git a/frontend/src-tauri/src/main.rs b/frontend/src-tauri/src/main.rs index 3d2fd1a..77dfaa3 100644 --- a/frontend/src-tauri/src/main.rs +++ b/frontend/src-tauri/src/main.rs @@ -129,9 +129,9 @@ async fn core_request( } #[tauri::command] -fn editor_capabilities(app: tauri::AppHandle, import_enabled: bool) -> Result<(), String> { +fn editor_capabilities(app: tauri::AppHandle, metadata_enabled: bool) -> Result<(), String> { app.state::>() - .set_enabled(import_enabled) + .set_enabled(metadata_enabled) .map_err(|_| "MENU_UNAVAILABLE".into()) } @@ -256,7 +256,7 @@ fn main() { let import = MenuItem::with_id( app, "editor.import-note-properties", - "导入为笔记属性…", + "元数据 / YAML Front Matter…", false, Some("CmdOrCtrl+Alt+P"), )?; diff --git a/frontend/src/components/common/TitleBarMenu.spec.ts b/frontend/src/components/common/TitleBarMenu.spec.ts index 1cb7bb4..d630264 100644 --- a/frontend/src/components/common/TitleBarMenu.spec.ts +++ b/frontend/src/components/common/TitleBarMenu.spec.ts @@ -3,19 +3,26 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { mount } from '@vue/test-utils' import { createPinia, setActivePinia } from 'pinia' import { useEditorStore } from '@/stores/editor' +import { useWorkspaceStore } from '@/stores/workspace' import TitleBarMenu from './TitleBarMenu.vue' const execute = vi.hoisted(() => vi.fn()) const capabilities = vi.hoisted(() => vi.fn()) +const routerPush = vi.hoisted(() => vi.fn()) vi.mock('@/services/editorCommandService', () => ({ executeEditorCommand: execute, getEditorCommandCapabilities: capabilities, subscribeEditorCommandCapabilities: () => () => undefined, })) +vi.mock('vue-router', () => ({ + useRouter: () => ({ push: routerPush }), + useRoute: () => ({ name: 'workspace' }), +})) beforeEach(() => { setActivePinia(createPinia()) execute.mockReset().mockResolvedValue({ ok: true }) + routerPush.mockReset() capabilities.mockReturnValue([ { id: 'editor.paragraph', supported: true, enabled: true }, { id: 'editor.heading', supported: true, enabled: true }, @@ -25,6 +32,33 @@ beforeEach(() => { }) describe('桌面顶部段落菜单', () => { + it('文件菜单承载实际工作区命令和笔记导出', async () => { + const editor = useEditorStore() + editor.currentFilePath = '/示例.md' + editor.content = '# 示例' + editor.saveStatus = 'saved' + const wrapper = mount(TitleBarMenu, { global: { stubs: { ExportDialog: { template: '
' } } } }) + await wrapper.get('[data-menu="file"] .menu-trigger').trigger('click') + expect(wrapper.text()).toContain('新建笔记…') + expect(wrapper.text()).toContain('打开其他知识库…') + expect(wrapper.text()).toContain('刷新文件树') + expect(wrapper.text()).toContain('下载 Markdown 副本') + await wrapper.get('.export-command').trigger('click') + expect(wrapper.find('[data-testid="export-dialog"]').exists()).toBe(true) + wrapper.unmount() + }) + + it('视图和帮助菜单只连接项目已有页面', async () => { + useWorkspaceStore().hasVault = true + const wrapper = mount(TitleBarMenu) + await wrapper.get('[data-menu="view"] .menu-trigger').trigger('click') + expect(wrapper.text()).toContain('工作区搜索AI 对话智能体任务音视频') + expect(wrapper.text()).toContain('Skill 管理Plugin 管理MCP 服务器') + await wrapper.get('[data-menu="help"] .menu-trigger').trigger('click') + expect(wrapper.text()).toContain('运行日志Benchmark 评测社区目录设置与诊断…') + wrapper.unmount() + }) + it('源码笔记通过统一编辑命令执行属性导入', async () => { const editor = useEditorStore() editor.mode = 'source' diff --git a/frontend/src/components/common/TitleBarMenu.vue b/frontend/src/components/common/TitleBarMenu.vue index 0acb56b..a31fb3d 100644 --- a/frontend/src/components/common/TitleBarMenu.vue +++ b/frontend/src/components/common/TitleBarMenu.vue @@ -1,23 +1,47 @@