From 7b44dd58f55008ebec45b405040999f1b70b23bd Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Wed, 18 Feb 2026 00:26:18 +0530 Subject: [PATCH] feat: implement platform-aware keyboard shortcuts for sidebar and toolbar actions, enhancing user experience --- .../components/editor/plate-editor.tsx | 30 +++++++++- .../layout/hooks/useSidebarState.ts | 4 +- .../ui/sidebar/SidebarCollapseButton.tsx | 6 +- .../components/ui/fixed-toolbar-buttons.tsx | 29 +++++++--- surfsense_web/components/ui/sidebar.tsx | 2 +- surfsense_web/hooks/use-platform-shortcut.ts | 57 +++++++++++++++++++ 6 files changed, 113 insertions(+), 15 deletions(-) create mode 100644 surfsense_web/hooks/use-platform-shortcut.ts diff --git a/surfsense_web/components/editor/plate-editor.tsx b/surfsense_web/components/editor/plate-editor.tsx index 29eeb02f6..ee43b296c 100644 --- a/surfsense_web/components/editor/plate-editor.tsx +++ b/surfsense_web/components/editor/plate-editor.tsx @@ -1,8 +1,8 @@ "use client"; -import { useEffect, useRef } from "react"; +import { useEffect, useMemo, useRef } from "react"; import { MarkdownPlugin, remarkMdx } from "@platejs/markdown"; -import { Plate, usePlateEditor } from "platejs/react"; +import { createPlatePlugin, Key, Plate, usePlateEditor } from "platejs/react"; import remarkGfm from "remark-gfm"; import remarkMath from "remark-math"; @@ -69,6 +69,31 @@ export function PlateEditor({ }: PlateEditorProps) { const lastMarkdownRef = useRef(markdown); + // Keep a stable ref to the latest onSave callback so the plugin shortcut + // always calls the most recent version without re-creating the editor. + const onSaveRef = useRef(onSave); + useEffect(() => { + onSaveRef.current = onSave; + }, [onSave]); + + // Stable Plate plugin for ⌘+S / Ctrl+S save shortcut + const SaveShortcutPlugin = useMemo( + () => + createPlatePlugin({ + key: "save-shortcut", + shortcuts: { + save: { + keys: [[Key.Mod, "s"]], + handler: () => { + onSaveRef.current?.(); + }, + preventDefault: true, + }, + }, + }), + [] + ); + // When readOnly is forced, always start in readOnly. // Otherwise, respect defaultEditing to decide initial mode. // The user can still toggle between editing/viewing via ModeToolbarButton. @@ -89,6 +114,7 @@ export function PlateEditor({ ...FloatingToolbarKit, ...AutoformatKit, ...DndKit, + SaveShortcutPlugin, MarkdownPlugin.configure({ options: { remarkPlugins: [remarkGfm, remarkMath, remarkMdx], diff --git a/surfsense_web/components/layout/hooks/useSidebarState.ts b/surfsense_web/components/layout/hooks/useSidebarState.ts index 9caa0b451..78631694d 100644 --- a/surfsense_web/components/layout/hooks/useSidebarState.ts +++ b/surfsense_web/components/layout/hooks/useSidebarState.ts @@ -40,10 +40,10 @@ export function useSidebarState(defaultCollapsed = false): UseSidebarStateReturn setIsCollapsed(!isCollapsed); }, [isCollapsed, setIsCollapsed]); - // Keyboard shortcut: Cmd/Ctrl + B + // Keyboard shortcut: Cmd/Ctrl + \ useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { - if (event.key === "b" && (event.metaKey || event.ctrlKey)) { + if (event.key === "\\" && (event.metaKey || event.ctrlKey)) { event.preventDefault(); toggleCollapsed(); } diff --git a/surfsense_web/components/layout/ui/sidebar/SidebarCollapseButton.tsx b/surfsense_web/components/layout/ui/sidebar/SidebarCollapseButton.tsx index 44f05249c..3985e93e0 100644 --- a/surfsense_web/components/layout/ui/sidebar/SidebarCollapseButton.tsx +++ b/surfsense_web/components/layout/ui/sidebar/SidebarCollapseButton.tsx @@ -4,6 +4,7 @@ import { PanelLeft, PanelLeftClose } from "lucide-react"; import { useTranslations } from "next-intl"; import { Button } from "@/components/ui/button"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; +import { usePlatformShortcut } from "@/hooks/use-platform-shortcut"; interface SidebarCollapseButtonProps { isCollapsed: boolean; @@ -17,6 +18,7 @@ export function SidebarCollapseButton({ disableTooltip = false, }: SidebarCollapseButtonProps) { const t = useTranslations("sidebar"); + const { shortcut } = usePlatformShortcut(); const button = (