refactor(desktop): spell out the macOS Edit menu copy

Co-authored-by: std4453 <13191718+std4453@users.noreply.github.com>
Co-authored-by: 07akioni <18677354+07akioni@users.noreply.github.com>
This commit is contained in:
Turtle
2026-11-03 15:57:19 +08:00
co-authored by std4453 07akioni
parent 34d1ff94f7
commit 80cec4bde3
6 changed files with 45 additions and 30 deletions
+2 -2
View File
@@ -33,8 +33,8 @@
en: 372297005b57a08b
zh: 074a635ac17e4362
/deepseek-harness-desktop/installation-ownership:
en: 9de048b0c7aa1b39
zh: dbfeffa96db912b9
en: 575d82acb5d61018
zh: b1064786f63863f7
/deepseek-harness-desktop/installation-ownership/runtime-and-plugin-activation:
en: 4951cd3ee659eb33
zh: 96bdd6358dad587a
+1 -1
View File
@@ -161,7 +161,7 @@ Windows and macOS relay native fullscreen transitions and the state after each p
Windows uses a 40-DIP caption with native window controls and colors synchronized from the application palette. Localized Application and Edit entries beside the sidebar toggle open native popup menus. They mount only after the application frame publishes its shell overlay seat, and remain absent during startup loading. Application provides Check for Updates and Exit; Edit provides undo, redo, cut, copy, paste, delete, and select all by sending the corresponding keys to the focused editor, independently of custom shortcut bindings. Plugin management uses the main application's Plugins page. No separate native menu row appears on Alt. Other platforms retain their native menus. Editable fields retain keyboard commands and a context menu without shortcut labels; Chromium supplies command availability, and selected read-only text offers Copy.
On macOS the custom menu retains Electron's standard Window menu and application hide commands, including Minimize (⌘M) and Hide (⌘H). Its Edit menu binds Paste and Match Style to ⇧⌘V and keeps Electron's ⌥⇧⌘V as a hidden alias. Linux keeps the application and Edit menus.
On macOS the custom menu retains Electron's standard Window menu and application hide commands, including Minimize (⌘M) and Hide (⌘H). Its Edit menu copies Electron's, binds Paste and Match Style to ⇧⌘V, and keeps Electron's ⌥⇧⌘V as a hidden alias; the Substitutions and Speech titles come from the desktop locale, while the other items keep Electron's English labels. Linux keeps the application and Edit menus.
### Runtime and plugin activation
+1 -1
View File
@@ -161,7 +161,7 @@ Windows 和 macOS 会在原生全屏切换及每次页面加载后向渲染器
Windows 使用 40 DIP 顶栏,保留原生窗口按钮,颜色随应用调色板同步。侧栏开关旁的本地化“应用”和“编辑”入口打开原生弹出菜单。仅当应用框架发布 shell overlay 席位后才挂载菜单,启动加载期间不显示。“应用”提供检查更新和退出;“编辑”向当前编辑器发送对应按键,提供撤销、重做、剪切、复制、粘贴、删除和全选,不受自定义快捷键绑定影响。插件管理使用主应用的“插件”页面。按 Alt 不会出现额外的原生菜单行。其他平台保留原生菜单。可编辑区域保留快捷键和不带快捷键标注的右键菜单;命令可用状态由 Chromium 提供,选中的只读文本提供“复制”命令。
macOS 上自定义菜单保留 Electron 的标准 Window 菜单及应用隐藏命令,包括 Minimize(⌘M)和 Hide(⌘H)。其 Edit 菜单把 Paste and Match Style 绑定到 ⇧⌘V,并保留 Electron 默认的 ⌥⇧⌘V 作为隐藏别名。Linux 保留应用菜单和 Edit 菜单。
macOS 上自定义菜单保留 Electron 的标准 Window 菜单及应用隐藏命令,包括 Minimize(⌘M)和 Hide(⌘H)。其 Edit 菜单复制 Electron 的默认菜单,把 Paste and Match Style 绑定到 ⇧⌘V,并保留 Electron 默认的 ⌥⇧⌘V 作为隐藏别名;Substitutions 和 Speech 标题取自桌面端语言包,其余条目保留 Electron 的英文标签。Linux 保留应用菜单和 Edit 菜单。
### 运行时与插件激活
+8 -8
View File
@@ -36,9 +36,9 @@ export const en = {
aboutVersion: 'Version V{version}',
hideApplication: 'Hide DeepSeek Harness',
hideOtherApplications: 'Hide Others',
showAllApplications: 'Show All',
substitutionsMenu: 'Substitutions',
speechMenu: 'Speech',
showAllApplications: 'Show All',
quitApplication: 'Quit DeepSeek Harness',
openApplication: 'Open DeepSeek Harness',
quit: 'Quit',
@@ -218,9 +218,9 @@ export const zh = {
aboutVersion: '版本 V{version}',
hideApplication: '隐藏 DeepSeek Harness',
hideOtherApplications: '隐藏其他',
showAllApplications: '显示全部',
substitutionsMenu: '替换',
speechMenu: '语音',
showAllApplications: '显示全部',
quitApplication: '退出 DeepSeek Harness',
openApplication: '打开 DeepSeek Harness',
quit: '退出',
@@ -398,9 +398,9 @@ export const ja = {
aboutVersion: 'バージョン V{version}',
hideApplication: 'DeepSeek Harness を隠す',
hideOtherApplications: 'ほかを隠す',
showAllApplications: 'すべて表示',
substitutionsMenu: '自動置換',
speechMenu: 'スピーチ',
showAllApplications: 'すべて表示',
quitApplication: 'DeepSeek Harness を終了',
openApplication: 'DeepSeek Harness を開く',
quit: '終了',
@@ -578,9 +578,9 @@ export const es = {
aboutVersion: 'Versión V{version}',
hideApplication: 'Ocultar DeepSeek Harness',
hideOtherApplications: 'Ocultar otros',
showAllApplications: 'Mostrar todo',
substitutionsMenu: 'Sustituciones',
speechMenu: 'Voz',
showAllApplications: 'Mostrar todo',
quitApplication: 'Salir de DeepSeek Harness',
openApplication: 'Abrir DeepSeek Harness',
quit: 'Salir',
@@ -758,9 +758,9 @@ export const fr = {
aboutVersion: 'VersionV{version}',
hideApplication: 'Masquer DeepSeek Harness',
hideOtherApplications: 'Masquer les autres',
showAllApplications: 'Afficher tout',
substitutionsMenu: 'Substitutions',
speechMenu: 'Parole',
showAllApplications: 'Afficher tout',
quitApplication: 'Quitter DeepSeek Harness',
openApplication: 'Ouvrir DeepSeek Harness',
quit: 'Quitter',
@@ -938,9 +938,9 @@ export const de = {
aboutVersion: 'Version V{version}',
hideApplication: 'DeepSeek Harness ausblenden',
hideOtherApplications: 'Andere ausblenden',
showAllApplications: 'Alle einblenden',
substitutionsMenu: 'Ersetzungen',
speechMenu: 'Sprachausgabe',
showAllApplications: 'Alle einblenden',
quitApplication: 'DeepSeek Harness beenden',
openApplication: 'DeepSeek Harness öffnen',
quit: 'Beenden',
@@ -1118,9 +1118,9 @@ export const ru = {
aboutVersion: 'Версия V{version}',
hideApplication: 'Скрыть DeepSeek Harness',
hideOtherApplications: 'Скрыть других',
showAllApplications: 'Показать все',
substitutionsMenu: 'Замены',
speechMenu: 'Речь',
showAllApplications: 'Показать все',
quitApplication: 'Выйти из DeepSeek Harness',
openApplication: 'Открыть DeepSeek Harness',
quit: 'Выйти',
@@ -1298,9 +1298,9 @@ export const pt = {
aboutVersion: 'Versão V{version}',
hideApplication: 'Ocultar DeepSeek Harness',
hideOtherApplications: 'Ocultar outros',
showAllApplications: 'Mostrar tudo',
substitutionsMenu: 'Substituições',
speechMenu: 'Fala',
showAllApplications: 'Mostrar tudo',
quitApplication: 'Sair de DeepSeek Harness',
openApplication: 'Abra DeepSeek Harness',
quit: 'Sair',
+23 -11
View File
@@ -33,6 +33,7 @@ import { DesktopBackendController } from './backend-controller.ts'
import { DESKTOP_IPC, SCHEME, assertDesktopSender, type DesktopUpdateState } from './ipc.ts'
import { readDeviceInfo } from './device-info.ts'
import { desktopUpdateReadyConfirmation, formatDesktopMessage, resolveDesktopLocale, resolveDesktopStartupLocale } from './locale.ts'
import type { DesktopMessages } from './locale.ts'
import { claimDesktopSingleInstance } from './single-instance.ts'
import { desktopUpdateChannel } from './update-channel.ts'
import { DesktopUpdateCoordinator } from './update-coordinator.ts'
@@ -1043,20 +1044,31 @@ async function main(): Promise<void> {
// its standard menus and application hide commands declared explicitly.
// Keep app.name stable: Electron derives its default userData directory from it.
const darwin = process.platform === 'darwin'
// A copy of Electron's macOS Edit menu that binds Paste and Match Style to Shift+Command+V
// as chat and document apps do; Electron's Option+Shift+Command+V stays as a hidden alias.
const macEditMenu = (): MenuItemConstructorOptions => ({ role: 'editMenu', submenu: [
{ role: 'undo' }, { role: 'redo' }, { type: 'separator' },
{ role: 'cut' }, { role: 'copy' }, { role: 'paste' },
// A copy of Electron 44's macOS Edit menu that binds Paste and Match Style to Shift+Command+V
// (#7782) and keeps Electron's Option+Shift+Command+V as a hidden alias.
const macEditMenu = (messages: DesktopMessages): MenuItemConstructorOptions => ({ role: 'editMenu', submenu: [
{ role: 'undo' },
{ role: 'redo' },
{ type: 'separator' },
{ role: 'cut' },
{ role: 'copy' },
{ role: 'paste' },
{ role: 'pasteAndMatchStyle', accelerator: 'Shift+Command+V' },
{ role: 'pasteAndMatchStyle', accelerator: 'Option+Shift+Command+V', visible: false },
{ role: 'delete' }, { role: 'selectAll' }, { type: 'separator' },
{ label: currentDesktopLocale().messages.substitutionsMenu, submenu: [{ role: 'showSubstitutions' }, { type: 'separator' },
{ role: 'toggleSmartQuotes' }, { role: 'toggleSmartDashes' }, { role: 'toggleTextReplacement' }] },
{ label: currentDesktopLocale().messages.speechMenu, submenu: [{ role: 'startSpeaking' }, { role: 'stopSpeaking' }] },
{ role: 'pasteAndMatchStyle', accelerator: 'Option+Shift+Command+V', visible: false, acceleratorWorksWhenHidden: true },
{ role: 'delete' },
{ role: 'selectAll' },
{ type: 'separator' },
{ label: messages.substitutionsMenu, submenu: [
{ role: 'showSubstitutions' },
{ type: 'separator' },
{ role: 'toggleSmartQuotes' },
{ role: 'toggleSmartDashes' },
{ role: 'toggleTextReplacement' },
] },
{ label: messages.speechMenu, submenu: [{ role: 'startSpeaking' }, { role: 'stopSpeaking' }] },
] })
const platformMenus = (): MenuItemConstructorOptions[] => darwin
? [shortcuts.fileMenu(currentDesktopLocale().messages), macEditMenu(), { role: 'windowMenu' }]
? [shortcuts.fileMenu(currentDesktopLocale().messages), macEditMenu(currentDesktopLocale().messages), { role: 'windowMenu' }]
: [{ role: 'editMenu' }]
const hideCommands: MenuItemConstructorOptions[] = darwin
? [{ role: 'hide', label: currentDesktopLocale().messages.hideApplication },
+10 -7
View File
@@ -1096,7 +1096,7 @@ describe('desktop main startup', () => {
await edit
})
it.each(['darwin', 'linux'] as const)('adds the product File menu and standard window commands only on macOS (%s)', async (platform) => {
it.each(['darwin', 'linux'] as const)('declares the platform application menus, with the product File, Edit, and Window menus on macOS (%s)', async (platform) => {
vi.spyOn(process, 'platform', 'get').mockReturnValue(platform)
await import('../src/main.ts')
await harness.preparing.promise
@@ -1109,13 +1109,16 @@ describe('desktop main startup', () => {
expect(template.map(describeItem)).toEqual(platform === 'darwin'
? ['Desktop test', en.fileMenu, 'editMenu', 'windowMenu']
: ['Application', 'editMenu'])
const describeEdit = (items: MenuItemConstructorOptions[]): unknown[] => items.map(item => Array.isArray(item.submenu)
? [describeItem(item), describeEdit(item.submenu)]
: [describeItem(item), item.accelerator, item.visible, item.acceleratorWorksWhenHidden].filter(value => value !== undefined))
const edit = template.find(item => item.role === 'editMenu')!.submenu as MenuItemConstructorOptions[] | undefined
expect(edit?.map(item => [describeItem(item), item.accelerator, item.visible])).toEqual(platform === 'darwin'
? [['undo', undefined, undefined], ['redo', undefined, undefined], ['separator', undefined, undefined],
['cut', undefined, undefined], ['copy', undefined, undefined], ['paste', undefined, undefined],
['pasteAndMatchStyle', 'Shift+Command+V', undefined], ['pasteAndMatchStyle', 'Option+Shift+Command+V', false],
['delete', undefined, undefined], ['selectAll', undefined, undefined], ['separator', undefined, undefined],
[en.substitutionsMenu, undefined, undefined], [en.speechMenu, undefined, undefined]]
expect(edit === undefined ? undefined : describeEdit(edit)).toEqual(platform === 'darwin'
? [['undo'], ['redo'], ['separator'], ['cut'], ['copy'], ['paste'],
['pasteAndMatchStyle', 'Shift+Command+V'], ['pasteAndMatchStyle', 'Option+Shift+Command+V', false, true],
['delete'], ['selectAll'], ['separator'],
[en.substitutionsMenu, [['showSubstitutions'], ['separator'], ['toggleSmartQuotes'], ['toggleSmartDashes'], ['toggleTextReplacement']]],
[en.speechMenu, [['startSpeaking'], ['stopSpeaking']]]]
: undefined)
const application = template[0]!.submenu as MenuItemConstructorOptions[]
expect(application.filter(item => item.visible !== false).map(describeItem)).toEqual(platform === 'darwin'