Feat/task widget global shortcut (#7099)

* feat(electron): add global shortcut for task widget toggle

Adds a configurable global shortcut (`globalToggleTaskWidget`) that
shows/hides the task widget without changing the persisted
enabled/disabled preference. The shortcut is a no-op while the task
widget feature is disabled.

When the user reveals the widget via the shortcut while the main window
is visible, a sticky "user-forced visible" flag keeps it up (like
always-show) instead of letting the next focus/show event immediately
hide it. The flag clears when the user toggles the widget off, opens the
app from the widget, or the feature is disabled.

Rebased onto master to drop the now-superseded tray-indicator refactor;
the only main-window.ts change is the additive user-forced-visible gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(electron): handle task widget shortcut races

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Symon Baikov 2026-06-09 17:11:16 +03:00 committed by GitHub
parent 72538c18d0
commit e0be3a1a16
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 449 additions and 24 deletions

View file

@ -2,6 +2,7 @@ import { globalShortcut, ipcMain } from 'electron';
import { IPC } from '../shared-with-frontend/ipc-events.const'; import { IPC } from '../shared-with-frontend/ipc-events.const';
import { KeyboardConfig } from '../../src/app/features/config/keyboard-config.model'; import { KeyboardConfig } from '../../src/app/features/config/keyboard-config.model';
import { getWin, setWasMaximizedBeforeHide } from '../main-window'; import { getWin, setWasMaximizedBeforeHide } from '../main-window';
import { toggleTaskWidgetVisibility } from '../task-widget/task-widget';
import { showOrFocus } from '../various-shared'; import { showOrFocus } from '../various-shared';
import { ensureIndicator } from '../indicator'; import { ensureIndicator } from '../indicator';
import { getIsMinimizeToTray } from '../shared-state'; import { getIsMinimizeToTray } from '../shared-state';
@ -22,6 +23,7 @@ const registerShowAppShortCuts = (cfg: KeyboardConfig): void => {
'globalToggleTaskStart', 'globalToggleTaskStart',
'globalAddNote', 'globalAddNote',
'globalAddTask', 'globalAddTask',
'globalToggleTaskWidget',
]; ];
if (cfg) { if (cfg) {
@ -82,6 +84,10 @@ const registerShowAppShortCuts = (cfg: KeyboardConfig): void => {
}; };
break; break;
case 'globalToggleTaskWidget':
actionFn = toggleTaskWidgetVisibility;
break;
default: default:
actionFn = () => undefined; actionFn = () => undefined;
} }

View file

@ -19,6 +19,7 @@ import { IS_MAC, IS_GNOME_DESKTOP } from './common.const';
import { import {
destroyTaskWidget, destroyTaskWidget,
getIsTaskWidgetAlwaysShow, getIsTaskWidgetAlwaysShow,
getIsTaskWidgetUserForcedVisible,
hideTaskWidget, hideTaskWidget,
showTaskWidget, showTaskWidget,
} from './task-widget/task-widget'; } from './task-widget/task-widget';
@ -452,21 +453,27 @@ function initWinEventListeners(app: Electron.App): void {
appCloseHandler(app); appCloseHandler(app);
appMinimizeHandler(app); appMinimizeHandler(app);
// Handle restore and show events to hide task widget // Handle restore and show events to hide task widget. `getIsTaskWidgetUserForcedVisible()`
// keeps the widget up when the user explicitly revealed it via the global shortcut.
mainWin.on('restore', () => { mainWin.on('restore', () => {
if (!getIsTaskWidgetAlwaysShow()) { if (!getIsTaskWidgetAlwaysShow() && !getIsTaskWidgetUserForcedVisible()) {
hideTaskWidget(); hideTaskWidget();
} }
}); });
mainWin.on('show', () => { mainWin.on('show', () => {
if (!getIsTaskWidgetAlwaysShow()) { if (!getIsTaskWidgetAlwaysShow() && !getIsTaskWidgetUserForcedVisible()) {
hideTaskWidget(); hideTaskWidget();
} }
}); });
mainWin.on('focus', () => { mainWin.on('focus', () => {
if (mainWin.isVisible() && !mainWin.isMinimized() && !getIsTaskWidgetAlwaysShow()) { if (
mainWin.isVisible() &&
!mainWin.isMinimized() &&
!getIsTaskWidgetAlwaysShow() &&
!getIsTaskWidgetUserForcedVisible()
) {
hideTaskWidget(); hideTaskWidget();
} }
}); });

View file

@ -0,0 +1,297 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const path = require('node:path');
const Module = require('node:module');
require('ts-node/register/transpile-only');
const originalModuleLoad = Module._load;
const taskWidgetModulePath = path.resolve(__dirname, 'task-widget/task-widget.ts');
let createdWindows = [];
let loadSimpleStoreAllImpl;
const createDeferred = () => {
let resolve;
let reject;
const promise = new Promise((promiseResolve, promiseReject) => {
resolve = promiseResolve;
reject = promiseReject;
});
return { promise, resolve, reject };
};
class FakeWebContents {
on() {}
send() {}
focus() {}
isDestroyed() {
return false;
}
removeAllListeners() {}
}
class FakeBrowserWindow {
constructor() {
this._visible = false;
this.showCount = 0;
this.showInactiveCount = 0;
this.hideCount = 0;
this._handlers = new Map();
this.webContents = new FakeWebContents();
createdWindows.push(this);
}
static getAllWindows() {
return createdWindows.slice();
}
loadFile() {}
setVisibleOnAllWorkspaces() {}
setOpacity() {}
setClosable() {}
removeAllListeners() {}
destroy() {}
on(eventName, handler) {
this._handlers.set(eventName, handler);
}
emit(eventName) {
const handler = this._handlers.get(eventName);
if (handler) handler();
}
getBounds() {
return { width: 300, height: 80, x: 0, y: 0 };
}
isDestroyed() {
return false;
}
isVisible() {
return this._visible;
}
show() {
this._visible = true;
this.showCount += 1;
}
showInactive() {
this._visible = true;
this.showInactiveCount += 1;
}
hide() {
this._visible = false;
this.hideCount += 1;
}
}
const installMocks = () => {
Module._load = function patchedLoad(request, parent, isMain) {
if (request === 'electron') {
return {
BrowserWindow: FakeBrowserWindow,
ipcMain: { on: () => {}, removeAllListeners: () => {} },
screen: {
getPrimaryDisplay: () => ({ workAreaSize: { width: 1920, height: 1080 } }),
getDisplayMatching: () => ({
bounds: { x: 0, y: 0, width: 1920, height: 1080 },
}),
},
};
}
if (request === 'electron-log/main') {
return { info: () => {} };
}
if (request.endsWith('simple-store')) {
return {
loadSimpleStoreAll: () => loadSimpleStoreAllImpl(),
saveSimpleStore: () => {},
};
}
if (request.endsWith('common.const')) {
return { IS_MAC: false };
}
return originalModuleLoad.call(this, request, parent, isMain);
};
};
const loadModule = () => {
delete require.cache[taskWidgetModulePath];
return require(taskWidgetModulePath);
};
const flush = () => new Promise((resolve) => setImmediate(resolve));
test.beforeEach(() => {
createdWindows = [];
loadSimpleStoreAllImpl = async () => ({});
installMocks();
});
test.afterEach(() => {
Module._load = originalModuleLoad;
});
test('toggleTaskWidgetVisibility is a no-op while the task widget feature is disabled', () => {
const mod = loadModule();
mod.toggleTaskWidgetVisibility();
assert.equal(createdWindows.length, 0, 'no window should be created when disabled');
});
test('toggleTaskWidgetVisibility shows the widget when it is enabled but hidden', async () => {
const mod = loadModule();
mod.updateTaskWidgetEnabled(true);
await flush();
assert.equal(createdWindows.length, 1, 'enabling should create the widget window');
const win = createdWindows[0];
assert.equal(win.isVisible(), false, 'widget starts hidden');
mod.toggleTaskWidgetVisibility();
assert.equal(win.isVisible(), true, 'toggle should show the hidden widget');
assert.equal(win.showInactiveCount, 1);
});
test('toggleTaskWidgetVisibility hides the widget when it is enabled and visible', async () => {
const mod = loadModule();
mod.updateTaskWidgetEnabled(true);
await flush();
const win = createdWindows[0];
mod.showTaskWidget();
assert.equal(win.isVisible(), true, 'widget should be visible before toggling');
mod.toggleTaskWidgetVisibility();
assert.equal(win.isVisible(), false, 'toggle should hide the visible widget');
assert.equal(win.hideCount, 1);
});
test('forcing the widget visible via the shortcut sets a sticky user-forced flag', async () => {
const mod = loadModule();
mod.updateTaskWidgetEnabled(true);
await flush();
assert.equal(mod.getIsTaskWidgetUserForcedVisible(), false, 'flag starts cleared');
mod.toggleTaskWidgetVisibility();
assert.equal(
mod.getIsTaskWidgetUserForcedVisible(),
true,
'showing via the shortcut sets the sticky flag',
);
mod.toggleTaskWidgetVisibility();
assert.equal(
mod.getIsTaskWidgetUserForcedVisible(),
false,
'hiding via the shortcut clears the sticky flag',
);
});
test('disabling the widget clears the sticky user-forced flag', async () => {
const mod = loadModule();
mod.updateTaskWidgetEnabled(true);
await flush();
mod.toggleTaskWidgetVisibility();
assert.equal(mod.getIsTaskWidgetUserForcedVisible(), true);
mod.updateTaskWidgetEnabled(false);
assert.equal(
mod.getIsTaskWidgetUserForcedVisible(),
false,
'disabling the feature resets the sticky flag',
);
});
test('disabling clears the sticky flag even when the widget window is absent', () => {
const mod = loadModule();
// Enable but do not flush: createTaskWidgetWindow() is mid-flight, so
// taskWidgetWin is still null (the "absent window" / async re-create gap).
mod.updateTaskWidgetEnabled(true);
// User hits the shortcut during that gap — the flag is set without a window.
mod.toggleTaskWidgetVisibility();
assert.equal(createdWindows.length, 0, 'no window exists yet');
assert.equal(
mod.getIsTaskWidgetUserForcedVisible(),
true,
'shortcut sets the sticky flag even without a window',
);
// Disabling now must clear the flag even though destroyTaskWidget() is
// skipped (its guard requires an existing window), or it would leak into
// the next enable.
mod.updateTaskWidgetEnabled(false);
assert.equal(
mod.getIsTaskWidgetUserForcedVisible(),
false,
'disabling clears the flag regardless of whether the window exists',
);
});
test('disabling while async creation is pending prevents the widget window from being created', async () => {
const storeLoad = createDeferred();
loadSimpleStoreAllImpl = () => storeLoad.promise;
const mod = loadModule();
mod.updateTaskWidgetEnabled(true);
mod.updateTaskWidgetEnabled(false);
storeLoad.resolve({});
await flush();
assert.equal(
createdWindows.length,
0,
'disabling before persisted bounds load resolves should cancel window creation',
);
});
test('shortcut reveal while async creation is pending shows the widget after creation completes', async () => {
const storeLoad = createDeferred();
loadSimpleStoreAllImpl = () => storeLoad.promise;
const mod = loadModule();
mod.updateTaskWidgetEnabled(true);
mod.toggleTaskWidgetVisibility();
storeLoad.resolve({});
await flush();
assert.equal(createdWindows.length, 1, 'only the initial in-flight creation is reused');
assert.equal(
createdWindows[0].isVisible(),
true,
'pending shortcut reveal should show the window once it exists',
);
});
test('shortcut reveal uses showInactive so the current app keeps focus', async () => {
const mod = loadModule();
mod.updateTaskWidgetEnabled(true);
await flush();
const win = createdWindows[0];
mod.toggleTaskWidgetVisibility();
assert.equal(win.showInactiveCount, 1);
assert.equal(win.showCount, 0);
assert.equal(win.isVisible(), true, 'widget should still become visible');
});
test('the closed event clears the sticky flag so it does not outlive the window', async () => {
const mod = loadModule();
mod.updateTaskWidgetEnabled(true);
await flush();
const win = createdWindows[0];
mod.toggleTaskWidgetVisibility();
assert.equal(mod.getIsTaskWidgetUserForcedVisible(), true);
win.emit('closed');
assert.equal(
mod.getIsTaskWidgetUserForcedVisible(),
false,
'closing the window clears the sticky flag',
);
});

View file

@ -10,6 +10,14 @@ import { IS_MAC } from '../common.const';
let taskWidgetWin: BrowserWindow | null = null; let taskWidgetWin: BrowserWindow | null = null;
let isTaskWidgetEnabled = false; let isTaskWidgetEnabled = false;
let isAlwaysShow = false; let isAlwaysShow = false;
// Set when the user explicitly reveals the widget via the global shortcut
// (`globalToggleTaskWidget`) while the main window is visible. Like
// `isAlwaysShow`, it suppresses the automatic "hide the widget when the main
// window is shown/focused" behavior — but only until the user hides the widget
// again (toggles off) or opens the app from the widget. This gives the shortcut
// a sticky "user-forced visible" effect instead of being immediately undone by
// the next focus event.
let isUserForcedVisible = false;
let currentTask: TaskCopy | null = null; let currentTask: TaskCopy | null = null;
let isPomodoroEnabled = false; let isPomodoroEnabled = false;
let currentPomodoroSessionTime = 0; let currentPomodoroSessionTime = 0;
@ -18,16 +26,28 @@ let currentFocusSessionTime = 0;
let initTimeoutId: NodeJS.Timeout | null = null; let initTimeoutId: NodeJS.Timeout | null = null;
let currentOpacity = 95; let currentOpacity = 95;
let listenersRegistered = false; let listenersRegistered = false;
let isCreatingWindow = false; let taskWidgetCreationPromise: Promise<void> | null = null;
let taskWidgetCreationGeneration = 0;
let pendingShowAfterCreate = false;
let pendingShowAfterCreateInactive = false;
const TASK_WIDGET_BOUNDS_KEY = 'taskWidgetBounds'; const TASK_WIDGET_BOUNDS_KEY = 'taskWidgetBounds';
const LEGACY_BOUNDS_KEY = 'overlayBounds'; const LEGACY_BOUNDS_KEY = 'overlayBounds';
let boundsDebounceTimer: NodeJS.Timeout | null = null; let boundsDebounceTimer: NodeJS.Timeout | null = null;
type ShowTaskWidgetOptions = Readonly<{
inactive?: boolean;
}>;
export const updateTaskWidgetEnabled = (isEnabled: boolean): void => { export const updateTaskWidgetEnabled = (isEnabled: boolean): void => {
isTaskWidgetEnabled = isEnabled; isTaskWidgetEnabled = isEnabled;
if (isEnabled && !taskWidgetWin && !isCreatingWindow) { if (!isEnabled) {
destroyTaskWidget();
return;
}
if (!taskWidgetWin && !taskWidgetCreationPromise) {
initListeners(); initListeners();
createTaskWidgetWindow().then(() => { createTaskWidgetWindow().then(() => {
// Window creation is async; re-apply the cached opacity here because // Window creation is async; re-apply the cached opacity here because
@ -44,11 +64,16 @@ export const updateTaskWidgetEnabled = (isEnabled: boolean): void => {
mainWindow.webContents.send(IPC.REQUEST_CURRENT_TASK_FOR_TASK_WIDGET); mainWindow.webContents.send(IPC.REQUEST_CURRENT_TASK_FOR_TASK_WIDGET);
} }
}); });
} else if (!isEnabled && taskWidgetWin) {
destroyTaskWidget();
} }
}; };
const clearPendingTaskWidgetCreation = (): void => {
taskWidgetCreationGeneration += 1;
taskWidgetCreationPromise = null;
pendingShowAfterCreate = false;
pendingShowAfterCreateInactive = false;
};
export const destroyTaskWidget = (): void => { export const destroyTaskWidget = (): void => {
// Clear any pending timeouts // Clear any pending timeouts
if (initTimeoutId) { if (initTimeoutId) {
@ -64,7 +89,8 @@ export const destroyTaskWidget = (): void => {
// Disable task widget to prevent close event prevention // Disable task widget to prevent close event prevention
isTaskWidgetEnabled = false; isTaskWidgetEnabled = false;
isCreatingWindow = false; isUserForcedVisible = false;
clearPendingTaskWidgetCreation();
// Remove IPC listeners // Remove IPC listeners
ipcMain.removeAllListeners('task-widget-show-main-window'); ipcMain.removeAllListeners('task-widget-show-main-window');
@ -97,11 +123,34 @@ export const destroyTaskWidget = (): void => {
} }
}; };
const createTaskWidgetWindow = async (): Promise<void> => { const createTaskWidgetWindow = (): Promise<void> => {
if (taskWidgetWin || isCreatingWindow) { if (taskWidgetWin) {
return Promise.resolve();
}
if (taskWidgetCreationPromise) {
return taskWidgetCreationPromise;
}
const creationGeneration = taskWidgetCreationGeneration;
const nextCreationPromise = createTaskWidgetWindowForGeneration(
creationGeneration,
).finally(() => {
if (taskWidgetCreationPromise === nextCreationPromise) {
taskWidgetCreationPromise = null;
}
});
taskWidgetCreationPromise = nextCreationPromise;
return nextCreationPromise;
};
const createTaskWidgetWindowForGeneration = async (
creationGeneration: number,
): Promise<void> => {
if (taskWidgetWin) {
return; return;
} }
isCreatingWindow = true;
const primaryDisplay = screen.getPrimaryDisplay(); const primaryDisplay = screen.getPrimaryDisplay();
const { width: screenWidth } = primaryDisplay.workAreaSize; const { width: screenWidth } = primaryDisplay.workAreaSize;
@ -143,7 +192,14 @@ const createTaskWidgetWindow = async (): Promise<void> => {
// Use defaults (file may not exist on first run) // Use defaults (file may not exist on first run)
} }
isCreatingWindow = false; if (
taskWidgetWin ||
!isTaskWidgetEnabled ||
creationGeneration !== taskWidgetCreationGeneration
) {
return;
}
// On macOS, transparent + frameless windows do not support native window // On macOS, transparent + frameless windows do not support native window
// dragging or edge resizing (see Electron's BrowserWindow docs: "Transparent // dragging or edge resizing (see Electron's BrowserWindow docs: "Transparent
// windows are not resizable. Setting `resizable` to `true` may make a // windows are not resizable. Setting `resizable` to `true` may make a
@ -190,6 +246,10 @@ const createTaskWidgetWindow = async (): Promise<void> => {
taskWidgetWin.on('closed', () => { taskWidgetWin.on('closed', () => {
taskWidgetWin = null; taskWidgetWin = null;
// Tie "user-forced visible" to the window's lifetime: once the window is
// gone the sticky flag has no widget to keep visible, so don't let it
// linger into a future re-create.
isUserForcedVisible = false;
}); });
taskWidgetWin.on('ready-to-show', () => { taskWidgetWin.on('ready-to-show', () => {
@ -232,9 +292,30 @@ const createTaskWidgetWindow = async (): Promise<void> => {
// Update initial state // Update initial state
updateTaskWidgetContent(); updateTaskWidgetContent();
updateTaskWidgetOpacity(currentOpacity);
if (pendingShowAfterCreate) {
const showInactive = pendingShowAfterCreateInactive;
pendingShowAfterCreate = false;
pendingShowAfterCreateInactive = false;
showTaskWidgetWindow({ inactive: showInactive });
}
}; };
export const showTaskWidget = (): void => { const showTaskWidgetWindow = (options: ShowTaskWidgetOptions = {}): void => {
if (!taskWidgetWin || taskWidgetWin.isDestroyed()) {
return;
}
if (options.inactive) {
taskWidgetWin.showInactive();
} else {
taskWidgetWin.show();
}
};
export const showTaskWidget = (options: ShowTaskWidgetOptions = {}): void => {
if (!isTaskWidgetEnabled) { if (!isTaskWidgetEnabled) {
return; return;
} }
@ -242,12 +323,9 @@ export const showTaskWidget = (): void => {
// Recreate task widget if it was accidentally closed // Recreate task widget if it was accidentally closed
if (!taskWidgetWin) { if (!taskWidgetWin) {
info('Task widget window was destroyed, recreating'); info('Task widget window was destroyed, recreating');
createTaskWidgetWindow().then(() => { pendingShowAfterCreate = true;
if (taskWidgetWin && !taskWidgetWin.isDestroyed()) { pendingShowAfterCreateInactive = pendingShowAfterCreateInactive || !!options.inactive;
updateTaskWidgetOpacity(currentOpacity); createTaskWidgetWindow();
taskWidgetWin.show();
}
});
return; return;
} }
@ -258,7 +336,7 @@ export const showTaskWidget = (): void => {
// Only show if not already visible // Only show if not already visible
if (!taskWidgetWin.isVisible()) { if (!taskWidgetWin.isVisible()) {
info('Showing task widget'); info('Showing task widget');
taskWidgetWin.show(); showTaskWidgetWindow(options);
} else { } else {
info('Task widget already visible'); info('Task widget already visible');
} }
@ -284,6 +362,27 @@ export const hideTaskWidget = (): void => {
} }
}; };
/**
* Toggles the task widget's visibility. Intended for the global shortcut
* (`globalToggleTaskWidget`): it only acts when the task widget feature is
* enabled in settings and never changes that persisted enabled/disabled
* preference it just shows or hides the existing widget.
*/
export const toggleTaskWidgetVisibility = (): void => {
if (!isTaskWidgetEnabled) {
return;
}
if (taskWidgetWin && !taskWidgetWin.isDestroyed() && taskWidgetWin.isVisible()) {
isUserForcedVisible = false;
hideTaskWidget();
return;
}
isUserForcedVisible = true;
showTaskWidget({ inactive: true });
};
const initListeners = (): void => { const initListeners = (): void => {
if (listenersRegistered) { if (listenersRegistered) {
return; return;
@ -299,6 +398,10 @@ const initListeners = (): void => {
// event.preventDefault() on 'minimize' has no effect). // event.preventDefault() on 'minimize' has no effect).
mainWindow.restore(); mainWindow.restore();
mainWindow.show(); mainWindow.show();
// Opening the app from the widget is an explicit "I'm going to the app"
// gesture, so clear any sticky user-forced visibility and let the widget
// follow the normal companion behavior again.
isUserForcedVisible = false;
if (!isAlwaysShow) { if (!isAlwaysShow) {
hideTaskWidget(); hideTaskWidget();
} }
@ -374,6 +477,8 @@ export const updateTaskWidgetAlwaysShow = (alwaysShow: boolean): void => {
export const getIsTaskWidgetAlwaysShow = (): boolean => isAlwaysShow; export const getIsTaskWidgetAlwaysShow = (): boolean => isAlwaysShow;
export const getIsTaskWidgetUserForcedVisible = (): boolean => isUserForcedVisible;
export const updateTaskWidgetOpacity = (opacity: number): void => { export const updateTaskWidgetOpacity = (opacity: number): void => {
currentOpacity = opacity; currentOpacity = opacity;
if (!taskWidgetWin || taskWidgetWin.isDestroyed()) { if (!taskWidgetWin || taskWidgetWin.isDestroyed()) {

View file

@ -1,7 +1,11 @@
import { app, BrowserWindow } from 'electron'; import { app, BrowserWindow } from 'electron';
import { info } from 'electron-log/main'; import { info } from 'electron-log/main';
import { getWin, getWasMaximizedBeforeHide } from './main-window'; import { getWin, getWasMaximizedBeforeHide } from './main-window';
import { getIsTaskWidgetAlwaysShow, hideTaskWidget } from './task-widget/task-widget'; import {
getIsTaskWidgetAlwaysShow,
getIsTaskWidgetUserForcedVisible,
hideTaskWidget,
} from './task-widget/task-widget';
import { setIsQuiting } from './shared-state'; import { setIsQuiting } from './shared-state';
// eslint-disable-next-line prefer-arrow/prefer-arrow-functions // eslint-disable-next-line prefer-arrow/prefer-arrow-functions
@ -36,8 +40,9 @@ export function showOrFocus(passedWin: BrowserWindow): void {
if (getWasMaximizedBeforeHide()) win.maximize(); if (getWasMaximizedBeforeHide()) win.maximize();
} }
// Hide task widget when main window is shown // Hide task widget when main window is shown, unless the user explicitly
if (!getIsTaskWidgetAlwaysShow()) { // pinned it visible via the global shortcut.
if (!getIsTaskWidgetAlwaysShow() && !getIsTaskWidgetUserForcedVisible()) {
hideTaskWidget(); hideTaskWidget();
} }

View file

@ -131,6 +131,7 @@ export const DEFAULT_GLOBAL_CONFIG: GlobalConfigState = {
globalToggleTaskStart: null, globalToggleTaskStart: null,
globalAddNote: null, globalAddNote: null,
globalAddTask: null, globalAddTask: null,
globalToggleTaskWidget: null,
addNewTask: 'Shift+A', addNewTask: 'Shift+A',
addNewProject: 'Shift+P', addNewProject: 'Shift+P',
addNewNote: 'Alt+N', addNewNote: 'Alt+N',

View file

@ -40,6 +40,7 @@ export const KEYBOARD_SETTINGS_FORM_CFG: ConfigFormSection<KeyboardConfig> = {
kbField('globalToggleTaskStart', T.GCF.KEYBOARD.GLOBAL_TOGGLE_TASK_START), kbField('globalToggleTaskStart', T.GCF.KEYBOARD.GLOBAL_TOGGLE_TASK_START),
kbField('globalAddNote', T.GCF.KEYBOARD.GLOBAL_ADD_NOTE), kbField('globalAddNote', T.GCF.KEYBOARD.GLOBAL_ADD_NOTE),
kbField('globalAddTask', T.GCF.KEYBOARD.GLOBAL_ADD_TASK), kbField('globalAddTask', T.GCF.KEYBOARD.GLOBAL_ADD_TASK),
kbField('globalToggleTaskWidget', T.GCF.KEYBOARD.GLOBAL_TOGGLE_TASK_WIDGET),
] ]
: []), : []),
// APP WIDE // APP WIDE

View file

@ -2,6 +2,7 @@ export type KeyboardConfig = Readonly<{
globalShowHide?: string | null; globalShowHide?: string | null;
globalAddNote?: string | null; globalAddNote?: string | null;
globalAddTask?: string | null; globalAddTask?: string | null;
globalToggleTaskWidget?: string | null;
toggleBacklog?: string | null; toggleBacklog?: string | null;
goToFocusMode?: string | null; goToFocusMode?: string | null;
goToWorkView?: string | null; goToWorkView?: string | null;

View file

@ -2403,6 +2403,7 @@ const T = {
GLOBAL_ADD_TASK: 'GCF.KEYBOARD.GLOBAL_ADD_TASK', GLOBAL_ADD_TASK: 'GCF.KEYBOARD.GLOBAL_ADD_TASK',
GLOBAL_SHOW_HIDE: 'GCF.KEYBOARD.GLOBAL_SHOW_HIDE', GLOBAL_SHOW_HIDE: 'GCF.KEYBOARD.GLOBAL_SHOW_HIDE',
GLOBAL_TOGGLE_TASK_START: 'GCF.KEYBOARD.GLOBAL_TOGGLE_TASK_START', GLOBAL_TOGGLE_TASK_START: 'GCF.KEYBOARD.GLOBAL_TOGGLE_TASK_START',
GLOBAL_TOGGLE_TASK_WIDGET: 'GCF.KEYBOARD.GLOBAL_TOGGLE_TASK_WIDGET',
GO_TO_DAILY_AGENDA: 'GCF.KEYBOARD.GO_TO_DAILY_AGENDA', GO_TO_DAILY_AGENDA: 'GCF.KEYBOARD.GO_TO_DAILY_AGENDA',
GO_TO_FOCUS_MODE: 'GCF.KEYBOARD.GO_TO_FOCUS_MODE', GO_TO_FOCUS_MODE: 'GCF.KEYBOARD.GO_TO_FOCUS_MODE',
GO_TO_SCHEDULE: 'GCF.KEYBOARD.GO_TO_SCHEDULE', GO_TO_SCHEDULE: 'GCF.KEYBOARD.GO_TO_SCHEDULE',

View file

@ -2346,6 +2346,7 @@
"GLOBAL_ADD_TASK": "Add new task", "GLOBAL_ADD_TASK": "Add new task",
"GLOBAL_SHOW_HIDE": "Show/hide Super Productivity", "GLOBAL_SHOW_HIDE": "Show/hide Super Productivity",
"GLOBAL_TOGGLE_TASK_START": "Start/stop time tracking for last active task", "GLOBAL_TOGGLE_TASK_START": "Start/stop time tracking for last active task",
"GLOBAL_TOGGLE_TASK_WIDGET": "Show/hide task widget",
"GO_TO_DAILY_AGENDA": "Go to agenda", "GO_TO_DAILY_AGENDA": "Go to agenda",
"GO_TO_FOCUS_MODE": "Enter focus mode", "GO_TO_FOCUS_MODE": "Enter focus mode",
"GO_TO_SCHEDULE": "Go to Today", "GO_TO_SCHEDULE": "Go to Today",