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;