mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-02 18:09:58 +08:00
feat: implement tray and minimize behaviors (#13851)
This PR introduces new window behaviors, which can be enabled when the menubar setting is active: New Features: - Quick open from tray icon - Minimize to tray - Exit to tray - Start minimized These changes have not yet been tested on macOS. <img width="645" height="479" alt="image" src="https://github.com/user-attachments/assets/7bdd13d0-5322-45a4-8e71-85c081aa0c86" /> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Configurable menubar/tray behaviors: open on left-click, minimize to tray, close to tray (exit to tray), and start minimized. * **UI** * Appearance settings add a Menubar → Window Behavior group with four toggles; group shows only when menubar/tray is enabled (hidden on macOS). * **Settings** * Tray settings persisted and exposed via the settings API with getters and setters for each option. * **Localization** * Added translation keys and English strings for the new controls and descriptions. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Peng Xiao <pengxiao@outlook.com>
This commit is contained in:
@@ -58,6 +58,10 @@ export type SpellCheckStateSchema = z.infer<typeof SpellCheckStateSchema>;
|
|||||||
export const MenubarStateKey = 'menubarState' as const;
|
export const MenubarStateKey = 'menubarState' as const;
|
||||||
export const MenubarStateSchema = z.object({
|
export const MenubarStateSchema = z.object({
|
||||||
enabled: z.boolean().default(true),
|
enabled: z.boolean().default(true),
|
||||||
|
openOnLeftClick: z.boolean().default(false),
|
||||||
|
minimizeToTray: z.boolean().default(false),
|
||||||
|
closeToTray: z.boolean().default(false),
|
||||||
|
startMinimized: z.boolean().default(false),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type MenubarStateSchema = z.infer<typeof MenubarStateSchema>;
|
export type MenubarStateSchema = z.infer<typeof MenubarStateSchema>;
|
||||||
|
|||||||
@@ -317,7 +317,14 @@ class TrayState implements Disposable {
|
|||||||
logger.debug('User clicked on tray icon');
|
logger.debug('User clicked on tray icon');
|
||||||
this.update();
|
this.update();
|
||||||
if (!isMacOS()) {
|
if (!isMacOS()) {
|
||||||
this.tray?.popUpContextMenu();
|
if (
|
||||||
|
TraySettingsState.value.enabled &&
|
||||||
|
TraySettingsState.value.openOnLeftClick
|
||||||
|
) {
|
||||||
|
showMainWindow();
|
||||||
|
} else {
|
||||||
|
this.tray?.popUpContextMenu();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
updateApplicationsPing$.next(Date.now());
|
updateApplicationsPing$.next(Date.now());
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
import { app, clipboard, nativeImage, nativeTheme } from 'electron';
|
import { app, clipboard, nativeImage, nativeTheme } from 'electron';
|
||||||
import { getLinkPreview } from 'link-preview-js';
|
import { getLinkPreview } from 'link-preview-js';
|
||||||
|
import { map, shareReplay } from 'rxjs';
|
||||||
|
|
||||||
import { isMacOS } from '../../shared/utils';
|
import { isMacOS } from '../../shared/utils';
|
||||||
import { persistentConfig } from '../config-storage/persist';
|
import { persistentConfig } from '../config-storage/persist';
|
||||||
import { logger } from '../logger';
|
import { logger } from '../logger';
|
||||||
import { openExternalSafely } from '../security/open-external';
|
import { openExternalSafely } from '../security/open-external';
|
||||||
import type { WorkbenchViewMeta } from '../shared-state-schema';
|
import type { WorkbenchViewMeta } from '../shared-state-schema';
|
||||||
|
import { MenubarStateKey, MenubarStateSchema } from '../shared-state-schema';
|
||||||
|
import { globalStateStorage } from '../shared-storage/storage';
|
||||||
import type { NamespaceHandlers } from '../type';
|
import type { NamespaceHandlers } from '../type';
|
||||||
import {
|
import {
|
||||||
activateView,
|
activateView,
|
||||||
@@ -34,6 +37,19 @@ import { getOrCreateCustomThemeWindow } from '../windows-manager/custom-theme-wi
|
|||||||
import { getChallengeResponse } from './challenge';
|
import { getChallengeResponse } from './challenge';
|
||||||
import { uiSubjects } from './subject';
|
import { uiSubjects } from './subject';
|
||||||
|
|
||||||
|
const TraySettingsState = {
|
||||||
|
$: globalStateStorage.watch<MenubarStateSchema>(MenubarStateKey).pipe(
|
||||||
|
map(v => MenubarStateSchema.parse(v ?? {})),
|
||||||
|
shareReplay(1)
|
||||||
|
),
|
||||||
|
|
||||||
|
get value() {
|
||||||
|
return MenubarStateSchema.parse(
|
||||||
|
globalStateStorage.get(MenubarStateKey) ?? {}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export const uiHandlers = {
|
export const uiHandlers = {
|
||||||
isMaximized: async () => {
|
isMaximized: async () => {
|
||||||
const window = await getMainWindow();
|
const window = await getMainWindow();
|
||||||
@@ -48,7 +64,14 @@ export const uiHandlers = {
|
|||||||
},
|
},
|
||||||
handleMinimizeApp: async () => {
|
handleMinimizeApp: async () => {
|
||||||
const window = await getMainWindow();
|
const window = await getMainWindow();
|
||||||
window?.minimize();
|
if (
|
||||||
|
TraySettingsState.value.enabled &&
|
||||||
|
TraySettingsState.value.minimizeToTray
|
||||||
|
) {
|
||||||
|
window?.hide();
|
||||||
|
} else {
|
||||||
|
window?.minimize();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
handleMaximizeApp: async () => {
|
handleMaximizeApp: async () => {
|
||||||
const window = await getMainWindow();
|
const window = await getMainWindow();
|
||||||
@@ -69,7 +92,15 @@ export const uiHandlers = {
|
|||||||
await handleWebContentsResize(e.sender);
|
await handleWebContentsResize(e.sender);
|
||||||
},
|
},
|
||||||
handleCloseApp: async () => {
|
handleCloseApp: async () => {
|
||||||
app.quit();
|
if (
|
||||||
|
TraySettingsState.value.enabled &&
|
||||||
|
TraySettingsState.value.closeToTray
|
||||||
|
) {
|
||||||
|
const window = await getMainWindow();
|
||||||
|
window?.hide();
|
||||||
|
} else {
|
||||||
|
app.quit();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
handleHideApp: async () => {
|
handleHideApp: async () => {
|
||||||
const window = await getMainWindow();
|
const window = await getMainWindow();
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { join } from 'node:path';
|
|||||||
|
|
||||||
import { BrowserWindow, nativeTheme } from 'electron';
|
import { BrowserWindow, nativeTheme } from 'electron';
|
||||||
import electronWindowState from 'electron-window-state';
|
import electronWindowState from 'electron-window-state';
|
||||||
import { BehaviorSubject } from 'rxjs';
|
import { BehaviorSubject, map, shareReplay } from 'rxjs';
|
||||||
|
|
||||||
import { isLinux, isMacOS, isWindows, resourcesPath } from '../../shared/utils';
|
import { isLinux, isMacOS, isWindows, resourcesPath } from '../../shared/utils';
|
||||||
import { beforeAppQuit } from '../cleanup';
|
import { beforeAppQuit } from '../cleanup';
|
||||||
@@ -10,11 +10,26 @@ import { buildType } from '../config';
|
|||||||
import { mainWindowOrigin } from '../constants';
|
import { mainWindowOrigin } from '../constants';
|
||||||
import { ensureHelperProcess } from '../helper-process';
|
import { ensureHelperProcess } from '../helper-process';
|
||||||
import { logger } from '../logger';
|
import { logger } from '../logger';
|
||||||
|
import { MenubarStateKey, MenubarStateSchema } from '../shared-state-schema';
|
||||||
|
import { globalStateStorage } from '../shared-storage/storage';
|
||||||
import { uiSubjects } from '../ui/subject';
|
import { uiSubjects } from '../ui/subject';
|
||||||
|
|
||||||
const IS_DEV: boolean =
|
const IS_DEV: boolean =
|
||||||
process.env.NODE_ENV === 'development' && !process.env.CI;
|
process.env.NODE_ENV === 'development' && !process.env.CI;
|
||||||
|
|
||||||
|
const TraySettingsState = {
|
||||||
|
$: globalStateStorage.watch<MenubarStateSchema>(MenubarStateKey).pipe(
|
||||||
|
map(v => MenubarStateSchema.parse(v ?? {})),
|
||||||
|
shareReplay(1)
|
||||||
|
),
|
||||||
|
|
||||||
|
get value() {
|
||||||
|
return MenubarStateSchema.parse(
|
||||||
|
globalStateStorage.get(MenubarStateKey) ?? {}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
function closeAllWindows() {
|
function closeAllWindows() {
|
||||||
BrowserWindow.getAllWindows().forEach(w => {
|
BrowserWindow.getAllWindows().forEach(w => {
|
||||||
if (!w.isDestroyed()) {
|
if (!w.isDestroyed()) {
|
||||||
@@ -125,9 +140,16 @@ export class MainWindowManager {
|
|||||||
// TODO(@pengx17): gracefully close the app, for example, ask user to save unsaved changes
|
// TODO(@pengx17): gracefully close the app, for example, ask user to save unsaved changes
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!isMacOS()) {
|
if (!isMacOS()) {
|
||||||
closeAllWindows();
|
if (
|
||||||
this.mainWindowReady = undefined;
|
TraySettingsState.value.enabled &&
|
||||||
this.mainWindow$.next(undefined);
|
TraySettingsState.value.closeToTray
|
||||||
|
) {
|
||||||
|
mainWindow.hide();
|
||||||
|
} else {
|
||||||
|
closeAllWindows();
|
||||||
|
this.mainWindowReady = undefined;
|
||||||
|
this.mainWindow$.next(undefined);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// hide window on macOS
|
// hide window on macOS
|
||||||
// application quit will be handled by closing the hidden window
|
// application quit will be handled by closing the hidden window
|
||||||
@@ -209,7 +231,10 @@ export class MainWindowManager {
|
|||||||
if (IS_DEV) {
|
if (IS_DEV) {
|
||||||
// do not gain focus in dev mode
|
// do not gain focus in dev mode
|
||||||
mainWindow.showInactive();
|
mainWindow.showInactive();
|
||||||
} else {
|
} else if (
|
||||||
|
!TraySettingsState.value.enabled ||
|
||||||
|
!TraySettingsState.value.startMinimized
|
||||||
|
) {
|
||||||
mainWindow.show();
|
mainWindow.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+84
-14
@@ -62,22 +62,92 @@ export const ThemeSettings = () => {
|
|||||||
const MenubarSetting = () => {
|
const MenubarSetting = () => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const traySettingService = useService(TraySettingService);
|
const traySettingService = useService(TraySettingService);
|
||||||
const { enabled } = useLiveData(traySettingService.setting$);
|
const traySetting = useLiveData(traySettingService.settings$);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingWrapper
|
<>
|
||||||
id="menubar"
|
<SettingWrapper
|
||||||
title={t['com.affine.appearanceSettings.menubar.title']()}
|
id="menubar"
|
||||||
>
|
title={t['com.affine.appearanceSettings.menubar.title']()}
|
||||||
<SettingRow
|
|
||||||
name={t['com.affine.appearanceSettings.menubar.toggle']()}
|
|
||||||
desc={t['com.affine.appearanceSettings.menubar.description']()}
|
|
||||||
>
|
>
|
||||||
<Switch
|
<SettingRow
|
||||||
checked={enabled}
|
name={t['com.affine.appearanceSettings.menubar.toggle']()}
|
||||||
onChange={checked => traySettingService.setEnabled(checked)}
|
desc={t['com.affine.appearanceSettings.menubar.description']()}
|
||||||
/>
|
>
|
||||||
</SettingRow>
|
<Switch
|
||||||
</SettingWrapper>
|
checked={traySetting.enabled}
|
||||||
|
onChange={checked => traySettingService.setEnabled(checked)}
|
||||||
|
/>
|
||||||
|
</SettingRow>
|
||||||
|
</SettingWrapper>
|
||||||
|
{traySetting.enabled && !environment.isMacOs ? (
|
||||||
|
<SettingWrapper
|
||||||
|
id="windowBehavior"
|
||||||
|
title={t[
|
||||||
|
'com.affine.appearanceSettings.menubar.windowBehavior.title'
|
||||||
|
]()}
|
||||||
|
>
|
||||||
|
<SettingRow
|
||||||
|
name={t[
|
||||||
|
'com.affine.appearanceSettings.menubar.windowBehavior.openOnLeftClick.toggle'
|
||||||
|
]()}
|
||||||
|
desc={t[
|
||||||
|
'com.affine.appearanceSettings.menubar.windowBehavior.openOnLeftClick.description'
|
||||||
|
]()}
|
||||||
|
>
|
||||||
|
<Switch
|
||||||
|
checked={traySetting.openOnLeftClick}
|
||||||
|
onChange={checked =>
|
||||||
|
traySettingService.setOpenOnLeftClick(checked)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</SettingRow>
|
||||||
|
<SettingRow
|
||||||
|
name={t[
|
||||||
|
'com.affine.appearanceSettings.menubar.windowBehavior.minimizeToTray.toggle'
|
||||||
|
]()}
|
||||||
|
desc={t[
|
||||||
|
'com.affine.appearanceSettings.menubar.windowBehavior.minimizeToTray.description'
|
||||||
|
]()}
|
||||||
|
>
|
||||||
|
<Switch
|
||||||
|
checked={traySetting.minimizeToTray}
|
||||||
|
onChange={checked =>
|
||||||
|
traySettingService.setMinimizeToTray(checked)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</SettingRow>
|
||||||
|
<SettingRow
|
||||||
|
name={t[
|
||||||
|
'com.affine.appearanceSettings.menubar.windowBehavior.closeToTray.toggle'
|
||||||
|
]()}
|
||||||
|
desc={t[
|
||||||
|
'com.affine.appearanceSettings.menubar.windowBehavior.closeToTray.description'
|
||||||
|
]()}
|
||||||
|
>
|
||||||
|
<Switch
|
||||||
|
checked={traySetting.closeToTray}
|
||||||
|
onChange={checked => traySettingService.setCloseToTray(checked)}
|
||||||
|
/>
|
||||||
|
</SettingRow>
|
||||||
|
<SettingRow
|
||||||
|
name={t[
|
||||||
|
'com.affine.appearanceSettings.menubar.windowBehavior.startMinimized.toggle'
|
||||||
|
]()}
|
||||||
|
desc={t[
|
||||||
|
'com.affine.appearanceSettings.menubar.windowBehavior.startMinimized.description'
|
||||||
|
]()}
|
||||||
|
>
|
||||||
|
<Switch
|
||||||
|
checked={traySetting.startMinimized}
|
||||||
|
onChange={checked =>
|
||||||
|
traySettingService.setStartMinimized(checked)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</SettingRow>
|
||||||
|
</SettingWrapper>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3,26 +3,73 @@ import type {
|
|||||||
MenubarStateSchema,
|
MenubarStateSchema,
|
||||||
} from '@affine/electron/main/shared-state-schema';
|
} from '@affine/electron/main/shared-state-schema';
|
||||||
import { LiveData, Service } from '@toeverything/infra';
|
import { LiveData, Service } from '@toeverything/infra';
|
||||||
|
import { defaults } from 'lodash-es';
|
||||||
|
|
||||||
import type { GlobalStateService } from '../../storage';
|
import type { GlobalStateService } from '../../storage';
|
||||||
|
|
||||||
const MENUBAR_SETTING_KEY: typeof MenubarStateKey = 'menubarState';
|
const MENUBAR_SETTING_KEY: typeof MenubarStateKey = 'menubarState';
|
||||||
|
|
||||||
|
const defaultTraySetting: MenubarStateSchema = {
|
||||||
|
enabled: true,
|
||||||
|
minimizeToTray: false,
|
||||||
|
closeToTray: false,
|
||||||
|
startMinimized: false,
|
||||||
|
openOnLeftClick: false,
|
||||||
|
};
|
||||||
|
|
||||||
export class TraySettingService extends Service {
|
export class TraySettingService extends Service {
|
||||||
constructor(private readonly globalStateService: GlobalStateService) {
|
constructor(private readonly globalStateService: GlobalStateService) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
setting$ = LiveData.from(
|
readonly settings$ = LiveData.computed(get => {
|
||||||
this.globalStateService.globalState.watch<MenubarStateSchema>(
|
const value = get(
|
||||||
MENUBAR_SETTING_KEY
|
LiveData.from(
|
||||||
),
|
this.globalStateService.globalState.watch<MenubarStateSchema>(
|
||||||
null
|
MENUBAR_SETTING_KEY
|
||||||
).map(v => v ?? { enabled: true });
|
),
|
||||||
|
undefined
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return defaults(value, defaultTraySetting);
|
||||||
|
});
|
||||||
|
|
||||||
|
get settings() {
|
||||||
|
return this.settings$.value;
|
||||||
|
}
|
||||||
|
|
||||||
setEnabled(enabled: boolean) {
|
setEnabled(enabled: boolean) {
|
||||||
this.globalStateService.globalState.set(MENUBAR_SETTING_KEY, {
|
this.globalStateService.globalState.set(MENUBAR_SETTING_KEY, {
|
||||||
enabled,
|
...this.settings$.value,
|
||||||
|
enabled: enabled,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setMinimizeToTray(minimizeToTray: boolean) {
|
||||||
|
this.globalStateService.globalState.set(MENUBAR_SETTING_KEY, {
|
||||||
|
...this.settings$.value,
|
||||||
|
minimizeToTray: minimizeToTray,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setCloseToTray(closeToTray: boolean) {
|
||||||
|
this.globalStateService.globalState.set(MENUBAR_SETTING_KEY, {
|
||||||
|
...this.settings$.value,
|
||||||
|
closeToTray: closeToTray,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setStartMinimized(startMinimized: boolean) {
|
||||||
|
this.globalStateService.globalState.set(MENUBAR_SETTING_KEY, {
|
||||||
|
...this.settings$.value,
|
||||||
|
startMinimized: startMinimized,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setOpenOnLeftClick(openOnLeftClick: boolean) {
|
||||||
|
this.globalStateService.globalState.set(MENUBAR_SETTING_KEY, {
|
||||||
|
...this.settings$.value,
|
||||||
|
openOnLeftClick: openOnLeftClick,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -991,6 +991,42 @@ export function useAFFiNEI18N(): {
|
|||||||
* `Display the menubar app in the tray for quick access to AFFiNE or meeting recordings.`
|
* `Display the menubar app in the tray for quick access to AFFiNE or meeting recordings.`
|
||||||
*/
|
*/
|
||||||
["com.affine.appearanceSettings.menubar.description"](): string;
|
["com.affine.appearanceSettings.menubar.description"](): string;
|
||||||
|
/**
|
||||||
|
* `Window behavior`
|
||||||
|
*/
|
||||||
|
["com.affine.appearanceSettings.menubar.windowBehavior.title"](): string;
|
||||||
|
/**
|
||||||
|
* `Quick open from tray icon`
|
||||||
|
*/
|
||||||
|
["com.affine.appearanceSettings.menubar.windowBehavior.openOnLeftClick.toggle"](): string;
|
||||||
|
/**
|
||||||
|
* `Open AFFiNE when left‑clicking the tray icon.`
|
||||||
|
*/
|
||||||
|
["com.affine.appearanceSettings.menubar.windowBehavior.openOnLeftClick.description"](): string;
|
||||||
|
/**
|
||||||
|
* `Minimize to tray`
|
||||||
|
*/
|
||||||
|
["com.affine.appearanceSettings.menubar.windowBehavior.minimizeToTray.toggle"](): string;
|
||||||
|
/**
|
||||||
|
* `Minimize AFFiNE to the system tray.`
|
||||||
|
*/
|
||||||
|
["com.affine.appearanceSettings.menubar.windowBehavior.minimizeToTray.description"](): string;
|
||||||
|
/**
|
||||||
|
* `Close to tray`
|
||||||
|
*/
|
||||||
|
["com.affine.appearanceSettings.menubar.windowBehavior.closeToTray.toggle"](): string;
|
||||||
|
/**
|
||||||
|
* `Close AFFiNE to the system tray.`
|
||||||
|
*/
|
||||||
|
["com.affine.appearanceSettings.menubar.windowBehavior.closeToTray.description"](): string;
|
||||||
|
/**
|
||||||
|
* `Start minimized`
|
||||||
|
*/
|
||||||
|
["com.affine.appearanceSettings.menubar.windowBehavior.startMinimized.toggle"](): string;
|
||||||
|
/**
|
||||||
|
* `Start AFFiNE minimized to the system tray.`
|
||||||
|
*/
|
||||||
|
["com.affine.appearanceSettings.menubar.windowBehavior.startMinimized.description"](): string;
|
||||||
/**
|
/**
|
||||||
* `Theme`
|
* `Theme`
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -237,6 +237,15 @@
|
|||||||
"com.affine.appearanceSettings.menubar.title": "Menubar",
|
"com.affine.appearanceSettings.menubar.title": "Menubar",
|
||||||
"com.affine.appearanceSettings.menubar.toggle": "Enable menubar app",
|
"com.affine.appearanceSettings.menubar.toggle": "Enable menubar app",
|
||||||
"com.affine.appearanceSettings.menubar.description": "Display the menubar app in the tray for quick access to AFFiNE or meeting recordings.",
|
"com.affine.appearanceSettings.menubar.description": "Display the menubar app in the tray for quick access to AFFiNE or meeting recordings.",
|
||||||
|
"com.affine.appearanceSettings.menubar.windowBehavior.title": "Window behavior",
|
||||||
|
"com.affine.appearanceSettings.menubar.windowBehavior.openOnLeftClick.toggle": "Quick open from tray icon",
|
||||||
|
"com.affine.appearanceSettings.menubar.windowBehavior.openOnLeftClick.description": "Open AFFiNE when left‑clicking the tray icon.",
|
||||||
|
"com.affine.appearanceSettings.menubar.windowBehavior.minimizeToTray.toggle": "Minimize to tray",
|
||||||
|
"com.affine.appearanceSettings.menubar.windowBehavior.minimizeToTray.description": "Minimize AFFiNE to the system tray.",
|
||||||
|
"com.affine.appearanceSettings.menubar.windowBehavior.closeToTray.toggle": "Close to tray",
|
||||||
|
"com.affine.appearanceSettings.menubar.windowBehavior.closeToTray.description": "Close AFFiNE to the system tray.",
|
||||||
|
"com.affine.appearanceSettings.menubar.windowBehavior.startMinimized.toggle": "Start minimized",
|
||||||
|
"com.affine.appearanceSettings.menubar.windowBehavior.startMinimized.description": "Start AFFiNE minimized to the system tray.",
|
||||||
"com.affine.appearanceSettings.theme.title": "Theme",
|
"com.affine.appearanceSettings.theme.title": "Theme",
|
||||||
"com.affine.appearanceSettings.title": "Appearance settings",
|
"com.affine.appearanceSettings.title": "Appearance settings",
|
||||||
"com.affine.appearanceSettings.translucentUI.description": "Use transparency effect on the sidebar.",
|
"com.affine.appearanceSettings.translucentUI.description": "Use transparency effect on the sidebar.",
|
||||||
|
|||||||
Reference in New Issue
Block a user