From 66b7340268124113e13a6a878b3c321f91d74baf Mon Sep 17 00:00:00 2001
From: 01zulfi <85733202+01zulfi@users.noreply.github.com>
Date: Wed, 18 Dec 2024 10:34:29 +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 | 52 +++++++++++++++++++
apps/web/src/components/editor/action-bar.tsx | 21 +++++++-
apps/web/src/components/editor/tiptap.tsx | 10 +++-
apps/web/src/stores/editor-store.ts | 22 ++++++++
4 files changed, 102 insertions(+), 3 deletions(-)
diff --git a/apps/web/__e2e__/editor.test.ts b/apps/web/__e2e__/editor.test.ts
index b5b9e22467..1d8b781584 100644
--- a/apps/web/__e2e__/editor.test.ts
+++ b/apps/web/__e2e__/editor.test.ts
@@ -270,3 +270,55 @@ test("#1468 count words separated by newlines", async ({ page }) => {
expect((await notes.editor.getWordCount()) === 10).toBeTruthy();
});
+
+test("control + 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+ArrowRight");
+
+ expect(await notes.editor.getTitle()).toBe("Note 1");
+ expect(await notes.editor.getContent("text")).toBe("Note 1 content");
+
+ await page.keyboard.press("Control+ArrowRight");
+
+ expect(await notes.editor.getTitle()).toBe("Note 2");
+ expect(await notes.editor.getContent("text")).toBe("Note 2 content");
+});
+
+test("control + 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+ArrowLeft");
+
+ expect(await notes.editor.getTitle()).toBe("Note 1");
+ expect(await notes.editor.getContent("text")).toBe("Note 1 content");
+
+ await page.keyboard.press("Control+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 1d4ec53ac2..23cd034716 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,
@@ -439,10 +439,27 @@ function Tab(props: TabProps) {
: Note;
const { attributes, listeners, setNodeRef, transform, transition, active } =
useSortable({ id });
+ const activeTabRef = useRef(null);
+
+ useEffect(() => {
+ if (activeTabRef.current) {
+ const tab = activeTabRef.current;
+ tab.scrollIntoView({
+ behavior: "smooth",
+ block: "nearest",
+ inline: "nearest"
+ });
+ }
+ }, [isActive]);
return (
{
+ setNodeRef(el);
+ if (isActive) {
+ activeTabRef.current = el;
+ }
+ }}
className="tab"
sx={{
borderRadius: "default",
diff --git a/apps/web/src/components/editor/tiptap.tsx b/apps/web/src/components/editor/tiptap.tsx
index 36cb12abbf..ba8aa0ae46 100644
--- a/apps/web/src/components/editor/tiptap.tsx
+++ b/apps/web/src/components/editor/tiptap.tsx
@@ -165,7 +165,15 @@ function TipTap(props: TipTapProps) {
const tiptapOptions = useMemo>(() => {
return {
editorProps: {
- handleKeyDown(view, event) {
+ handleKeyDown(_, event) {
+ if (event.ctrlKey && event.key === "ArrowRight") {
+ event.preventDefault();
+ useEditorStore.getState().openNextSession("right");
+ }
+ if (event.ctrlKey && event.key === "ArrowLeft") {
+ event.preventDefault();
+ useEditorStore.getState().openNextSession("left");
+ }
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 5434097eda..1ba83ffaf3 100644
--- a/apps/web/src/stores/editor-store.ts
+++ b/apps/web/src/stores/editor-store.ts
@@ -710,6 +710,28 @@ class EditorStore extends BaseStore {
}
};
+ openNextSession = (dir: "right" | "left") => {
+ 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;
+
+ switch (dir) {
+ case "right":
+ if (index === sessions.length - 1) {
+ return this.openSession(sessions[0].id);
+ }
+ return this.openSession(sessions[index + 1].id);
+ case "left":
+ 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;