diff --git a/apps/web/src/components/NotebookPane.tsx b/apps/web/src/components/NotebookPane.tsx
index 2a543ea7..9c7fa805 100644
--- a/apps/web/src/components/NotebookPane.tsx
+++ b/apps/web/src/components/NotebookPane.tsx
@@ -248,14 +248,17 @@ const SidebarCollapseButton = ({
onToggle,
className,
tooltipSide = "bottom",
+ shortcutLabel,
}: {
collapsed: boolean;
onToggle: () => void;
className?: string;
tooltipSide?: "top" | "right" | "bottom" | "left";
+ shortcutLabel?: string;
}) => {
const { t } = useTranslation();
const label = t(collapsed ? "notebookPane.expandSidebar" : "notebookPane.collapseSidebar");
+ const tooltipLabel = shortcutLabel ? `${label} (${shortcutLabel})` : label;
return (
@@ -275,7 +278,7 @@ const SidebarCollapseButton = ({
{collapsed ? : }
- {label}
+ {tooltipLabel}
);
@@ -512,6 +515,7 @@ export const NotebookPane = ({
isResettingDemo = false,
collapsed = false,
onToggleCollapsed,
+ collapseShortcutLabel,
}: {
repository: EdgeEverRepository;
user: AuthUser | null;
@@ -550,6 +554,7 @@ export const NotebookPane = ({
isResettingDemo?: boolean;
collapsed?: boolean;
onToggleCollapsed?: () => void;
+ collapseShortcutLabel?: string;
}) => {
const { t } = useTranslation();
const showDescendantNotes = useShowDescendantNotesPreference();
@@ -946,7 +951,7 @@ export const NotebookPane = ({
notice={deployedUpdateUnseen}
onClick={onOpenSettings}
/>
-
+
@@ -1176,7 +1181,7 @@ export const NotebookPane = ({
{onToggleCollapsed ? (
-
+
) : null}
diff --git a/apps/web/src/components/WorkspaceApp.tsx b/apps/web/src/components/WorkspaceApp.tsx
index 8aca7455..8da6de73 100644
--- a/apps/web/src/components/WorkspaceApp.tsx
+++ b/apps/web/src/components/WorkspaceApp.tsx
@@ -71,6 +71,8 @@ import {
isTextEntryTarget,
getSearchShortcutScope,
getShortcutActionForEvent,
+ formatShortcutBinding,
+ canToggleNotebookSidebar,
getNotebookDropSortOrder,
getNotebookMoveOptions,
getMemoIdsNeedingMove,
@@ -2810,6 +2812,19 @@ export const WorkspaceApp = ({
return;
}
+ if (action === "toggleSidebar") {
+ event.preventDefault();
+ if (
+ event.repeat
+ || event.isComposing
+ || !canToggleNotebookSidebar({ isDesktop, focusModeActive: desktopFocusModeActive })
+ ) {
+ return;
+ }
+ setNotebookSidebarCollapsed(!notebookSidebarCollapsed);
+ return;
+ }
+
if (action === "focusSearch") {
event.preventDefault();
if (getSearchShortcutScope(selectedMemoId) === "memo-list") {
@@ -2868,6 +2883,10 @@ export const WorkspaceApp = ({
handleGlobalSearch,
handleMobileSearch,
shortcutSettings,
+ isDesktop,
+ desktopFocusModeActive,
+ notebookSidebarCollapsed,
+ setNotebookSidebarCollapsed,
emptyTrashConfirmationOpen,
memoDeleteConfirmation,
memoView,
@@ -3083,6 +3102,7 @@ export const WorkspaceApp = ({
isResettingDemo={resetDemoMutation.isPending}
collapsed={desktopNotebookSidebarCollapsed}
onToggleCollapsed={() => setNotebookSidebarCollapsed(!notebookSidebarCollapsed)}
+ collapseShortcutLabel={formatShortcutBinding(shortcutSettings.toggleSidebar)}
/>
)}
diff --git a/apps/web/src/lib/app-helpers.test.mjs b/apps/web/src/lib/app-helpers.test.mjs
index 3bc0b3a6..9506a990 100644
--- a/apps/web/src/lib/app-helpers.test.mjs
+++ b/apps/web/src/lib/app-helpers.test.mjs
@@ -1,4 +1,5 @@
import { afterEach, describe, expect, test } from "bun:test";
+import { readFileSync } from "node:fs";
import {
DEFAULT_SHORTCUT_SETTINGS,
DESKTOP_FOCUS_MODE_STORAGE_KEY,
@@ -14,6 +15,7 @@ import {
SHORTCUT_SETTINGS_STORAGE_KEY,
getSearchShortcutScope,
getShortcutActionForEvent,
+ canToggleNotebookSidebar,
getNotebookSortComparator,
readEditorContentAlignmentPreference,
readNotebookSortPreference,
@@ -445,6 +447,12 @@ describe("workspace shortcut preferences", () => {
shift: true,
alt: false,
});
+ expect(DEFAULT_SHORTCUT_SETTINGS.toggleSidebar).toEqual({
+ key: "\\",
+ ctrlOrMeta: true,
+ shift: false,
+ alt: false,
+ });
});
test("migrates the unreleased reading protection shortcut without replacing custom bindings", () => {
@@ -532,6 +540,21 @@ describe("workspace shortcut preferences", () => {
keyboardEvent("!", { code: "Digit1", ctrlKey: true, shiftKey: true }),
DEFAULT_SHORTCUT_SETTINGS,
)).toBe("toggleOutline");
+ expect(getShortcutActionForEvent(
+ keyboardEvent("\\", { metaKey: true }),
+ DEFAULT_SHORTCUT_SETTINGS,
+ )).toBe("toggleSidebar");
+ });
+
+ test("toggles the sidebar only when it is visible", () => {
+ expect(canToggleNotebookSidebar({ isDesktop: true, focusModeActive: false })).toBe(true);
+ // Focus Mode hides the sidebar, so the shortcut must not change its saved state.
+ expect(canToggleNotebookSidebar({ isDesktop: true, focusModeActive: true })).toBe(false);
+ expect(canToggleNotebookSidebar({ isDesktop: false, focusModeActive: false })).toBe(false);
+ expect(canToggleNotebookSidebar({ isDesktop: false, focusModeActive: true })).toBe(false);
+ // The workspace checks the same flag that hides the sidebar in its layout.
+ const workspaceSource = readFileSync(new URL("../components/WorkspaceApp.tsx", import.meta.url), "utf8");
+ expect(workspaceSource).toContain("canToggleNotebookSidebar({ isDesktop, focusModeActive: desktopFocusModeActive })");
});
});
diff --git a/apps/web/src/lib/app-helpers.ts b/apps/web/src/lib/app-helpers.ts
index 153bfab4..3f6a3d9b 100644
--- a/apps/web/src/lib/app-helpers.ts
+++ b/apps/web/src/lib/app-helpers.ts
@@ -26,7 +26,8 @@ export type ShortcutAction =
| "saveAndSync"
| "toggleReadingProtection"
| "toggleEditorMode"
- | "toggleOutline";
+ | "toggleOutline"
+ | "toggleSidebar";
export type ShortcutBinding = {
key: string;
ctrlOrMeta: boolean;
@@ -190,6 +191,7 @@ export const getShortcutActionOptions = (
{ value: "toggleReadingProtection", label: t("shortcuts.actions.toggleReadingProtection.label") },
{ value: "toggleEditorMode", label: t("shortcuts.actions.toggleEditorMode.label") },
{ value: "toggleOutline", label: t("shortcuts.actions.toggleOutline.label") },
+ { value: "toggleSidebar", label: t("shortcuts.actions.toggleSidebar.label") },
];
export const DEFAULT_SHORTCUT_SETTINGS: ShortcutSettings = {
@@ -206,6 +208,7 @@ export const DEFAULT_SHORTCUT_SETTINGS: ShortcutSettings = {
toggleReadingProtection: { key: "e", ctrlOrMeta: true, shift: false, alt: false },
toggleEditorMode: { key: "/", ctrlOrMeta: true, shift: false, alt: false },
toggleOutline: { key: "1", ctrlOrMeta: true, shift: true, alt: false },
+ toggleSidebar: { key: "\\", ctrlOrMeta: true, shift: false, alt: false },
};
const LEGACY_READING_PROTECTION_SHORTCUT: ShortcutBinding = {
@@ -233,6 +236,7 @@ const SHORTCUT_ACTION_VALUES: ShortcutAction[] = [
"toggleReadingProtection",
"toggleEditorMode",
"toggleOutline",
+ "toggleSidebar",
];
export const isDefaultMemoTitle = (title: string | null | undefined) => title?.trim() === DEFAULT_MEMO_TITLE;
@@ -583,6 +587,13 @@ export const shortcutBindingsEqual = (first: ShortcutBinding, second: ShortcutBi
first.shift === second.shift &&
first.alt === second.alt;
+// The sidebar shortcut only acts when the sidebar can be seen: Focus Mode hides
+// it, so toggling there would change a persisted state without any feedback.
+export const canToggleNotebookSidebar = ({ isDesktop, focusModeActive }: {
+ isDesktop: boolean;
+ focusModeActive: boolean;
+}) => isDesktop && !focusModeActive;
+
export const getShortcutActionForEvent = (event: KeyboardEvent, settings: ShortcutSettings): ShortcutAction | null => {
const eventBinding = shortcutBindingFromKeyboardEvent(event);
diff --git a/packages/shared/src/i18n/en-US.ts b/packages/shared/src/i18n/en-US.ts
index 1b70e904..3bdf1c36 100644
--- a/packages/shared/src/i18n/en-US.ts
+++ b/packages/shared/src/i18n/en-US.ts
@@ -1681,6 +1681,7 @@ export const enUS = {
toggleReadingProtection: { label: "Toggle reading protection" },
toggleEditorMode: { label: "Switch editor mode" },
toggleOutline: { label: "Show/hide document outline" },
+ toggleSidebar: { label: "Show/hide sidebar" },
},
},
quickSwitcher: {
diff --git a/packages/shared/src/i18n/ja.ts b/packages/shared/src/i18n/ja.ts
index e4a35334..83c7d9fe 100644
--- a/packages/shared/src/i18n/ja.ts
+++ b/packages/shared/src/i18n/ja.ts
@@ -1681,6 +1681,7 @@ export const ja = {
toggleReadingProtection: { label: "閲覧保護を切り替え" },
toggleEditorMode: { label: "エディタモードを切り替え" },
toggleOutline: { label: "ドキュメントアウトラインの表示/非表示" },
+ toggleSidebar: { label: "サイドバーの表示/非表示" },
},
},
quickSwitcher: {
diff --git a/packages/shared/src/i18n/zh-CN.ts b/packages/shared/src/i18n/zh-CN.ts
index 890435fb..b95021a5 100644
--- a/packages/shared/src/i18n/zh-CN.ts
+++ b/packages/shared/src/i18n/zh-CN.ts
@@ -1681,6 +1681,7 @@ export const zhCN = {
toggleReadingProtection: { label: "切换阅读保护" },
toggleEditorMode: { label: "切换编辑模式" },
toggleOutline: { label: "显示/隐藏文档大纲" },
+ toggleSidebar: { label: "显示/隐藏侧边栏" },
},
},
quickSwitcher: {