From d0728abb706b275690cf50a8a37549d18229a2bf Mon Sep 17 00:00:00 2001 From: 01zulfi <85733202+01zulfi@users.noreply.github.com> Date: Mon, 30 Dec 2024 12:39:57 +0500 Subject: [PATCH] web: add go to next/previous tab shortcut Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com> --- apps/web/__e2e__/editor.test.ts | 54 +++++++++++++++++++ apps/web/src/components/editor/action-bar.tsx | 19 ++++++- apps/web/src/components/editor/index.tsx | 25 +++++++++ apps/web/src/components/editor/tiptap.tsx | 2 +- apps/web/src/stores/editor-store.ts | 26 +++++++++ 5 files changed, 123 insertions(+), 3 deletions(-) diff --git a/apps/web/__e2e__/editor.test.ts b/apps/web/__e2e__/editor.test.ts index fff0c70561..5f79ab377f 100644 --- a/apps/web/__e2e__/editor.test.ts +++ b/apps/web/__e2e__/editor.test.ts @@ -370,3 +370,57 @@ test("when autosave is disabled, closing the note should save it", async ({ await expect(notes.editor.savedIcon).toBeVisible(); expect(await notes.editor.getContent("text")).toBe(content.trim()); }); + +test("control + alt + right arrow should go to next note", async ({ page }) => { + const app = new AppModel(page); + await app.goto(); + const notes = await app.goToNotes(); + const note1 = await notes.createNote({ + title: "Note 1", + content: "Note 1 content" + }); + const note2 = await notes.createNote({ + title: "Note 2", + content: "Note 2 content" + }); + + await note1?.openNote(); + await note2?.openNote(); + await page.keyboard.press("Control+Alt+ArrowRight"); + + expect(await notes.editor.getTitle()).toBe("Note 1"); + expect(await notes.editor.getContent("text")).toBe("Note 1 content"); + + await page.keyboard.press("Control+Alt+ArrowRight"); + + expect(await notes.editor.getTitle()).toBe("Note 2"); + expect(await notes.editor.getContent("text")).toBe("Note 2 content"); +}); + +test("control + alt + left arrow should go to previous note", async ({ + page +}) => { + const app = new AppModel(page); + await app.goto(); + const notes = await app.goToNotes(); + const note1 = await notes.createNote({ + title: "Note 1", + content: "Note 1 content" + }); + const note2 = await notes.createNote({ + title: "Note 2", + content: "Note 2 content" + }); + + await note1?.openNote(); + await note2?.openNote(); + await page.keyboard.press("Control+Alt+ArrowLeft"); + + expect(await notes.editor.getTitle()).toBe("Note 1"); + expect(await notes.editor.getContent("text")).toBe("Note 1 content"); + + await page.keyboard.press("Control+Alt+ArrowLeft"); + + expect(await notes.editor.getTitle()).toBe("Note 2"); + expect(await notes.editor.getContent("text")).toBe("Note 2 content"); +}); diff --git a/apps/web/src/components/editor/action-bar.tsx b/apps/web/src/components/editor/action-bar.tsx index 5c522e17fc..e8d26c6cd9 100644 --- a/apps/web/src/components/editor/action-bar.tsx +++ b/apps/web/src/components/editor/action-bar.tsx @@ -18,7 +18,7 @@ along with this program. If not, see . */ import { Button, Flex, Text } from "@theme-ui/components"; -import { useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { ArrowLeft, Cross, @@ -450,10 +450,25 @@ function Tab(props: TabProps) { : Note; const { attributes, listeners, setNodeRef, transform, transition, active } = useSortable({ id }); + const activeTabRef = useRef(null); + + useEffect(() => { + if (activeTabRef.current && isActive) { + const tab = activeTabRef.current; + tab.scrollIntoView({ + behavior: "smooth", + block: "nearest", + inline: "nearest" + }); + } + }, [isActive]); return ( { + setNodeRef(el); + activeTabRef.current = el; + }} className="tab" data-test-id={`tab-${id}`} sx={{ diff --git a/apps/web/src/components/editor/index.tsx b/apps/web/src/components/editor/index.tsx index 54f94824bd..f8d41539b4 100644 --- a/apps/web/src/components/editor/index.tsx +++ b/apps/web/src/components/editor/index.tsx @@ -124,6 +124,31 @@ export default function TabsView() { const isTOCVisible = useEditorStore((store) => store.isTOCVisible); const [dropRef, overlayRef] = useDragOverlay(); + useEffect(() => { + const onKeyDown = (event: KeyboardEvent) => { + if ( + (event.ctrlKey || event.metaKey) && + event.altKey && + event.key === "ArrowRight" + ) { + event.preventDefault(); + useEditorStore.getState().openNextSession(); + } + if ( + (event.ctrlKey || event.metaKey) && + event.altKey && + event.key === "ArrowLeft" + ) { + event.preventDefault(); + useEditorStore.getState().openPreviousSession(); + } + }; + document.body.addEventListener("keydown", onKeyDown); + return () => { + document.body.removeEventListener("keydown", onKeyDown); + }; + }, []); + return ( <> {!hasNativeTitlebar ? ( diff --git a/apps/web/src/components/editor/tiptap.tsx b/apps/web/src/components/editor/tiptap.tsx index 91255fd710..6530dc25e9 100644 --- a/apps/web/src/components/editor/tiptap.tsx +++ b/apps/web/src/components/editor/tiptap.tsx @@ -166,7 +166,7 @@ function TipTap(props: TipTapProps) { const tiptapOptions = useMemo>(() => { return { editorProps: { - handleKeyDown(view, event) { + handleKeyDown(_, event) { if ((event.ctrlKey || event.metaKey) && event.key === "s") { event.preventDefault(); onChange?.( diff --git a/apps/web/src/stores/editor-store.ts b/apps/web/src/stores/editor-store.ts index 83ec322389..e7fa10a895 100644 --- a/apps/web/src/stores/editor-store.ts +++ b/apps/web/src/stores/editor-store.ts @@ -717,6 +717,32 @@ class EditorStore extends BaseStore { } }; + openNextSession = () => { + const { sessions, activeSessionId } = this.get(); + if (sessions.length === 0 || sessions.length === 1) return; + + const index = sessions.findIndex((s) => s.id === activeSessionId); + if (index === -1) return; + + if (index === sessions.length - 1) { + return this.openSession(sessions[0].id); + } + return this.openSession(sessions[index + 1].id); + }; + + openPreviousSession = () => { + const { sessions, activeSessionId } = this.get(); + if (sessions.length === 0 || sessions.length === 1) return; + + const index = sessions.findIndex((s) => s.id === activeSessionId); + if (index === -1) return; + + if (index === 0) { + return this.openSession(sessions[sessions.length - 1].id); + } + return this.openSession(sessions[index - 1].id); + }; + addSession = (session: EditorSession, activate = true) => { let oldSessionId: string | null = null;