From 861ad83423705111f98eb2fd72da4475bd585e76 Mon Sep 17 00:00:00 2001
From: Lim Chee Aun <cheeaun@gmail.com>
Date: Fri, 15 Mar 2024 18:06:52 +0800
Subject: [PATCH] More keyboard shortcuts for Catch-up

---
 src/pages/catchup.css | 19 ++++++++++++++-
 src/pages/catchup.jsx | 55 ++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 72 insertions(+), 2 deletions(-)

diff --git a/src/pages/catchup.css b/src/pages/catchup.css
index 024ea614..05eb6a90 100644
--- a/src/pages/catchup.css
+++ b/src/pages/catchup.css
@@ -525,10 +525,13 @@
         background-color: var(--bg-faded-color);
         box-shadow: 0 8px 16px -8px var(--drop-shadow-color),
           inset 0 1px var(--bg-color);
-        outline: 1px solid var(--outline-color);
         text-shadow: 0 1px var(--bg-color);
       }
 
+      &:hover:not(:focus-visible) {
+        outline: 1px solid var(--outline-color);
+      }
+
       &:active {
         filter: brightness(0.95);
         box-shadow: none;
@@ -1091,6 +1094,20 @@
     dd {
       margin-block-end: 1em;
       margin-inline: 1em;
+
+      + dd {
+        margin-block-start: -0.9em;
+      }
     }
   }
+
+  kbd {
+    border-radius: 4px;
+    display: inline-block;
+    padding: 0.2em 0.3em;
+    margin: 1px 0;
+    line-height: 1;
+    border: 1px solid var(--outline-color);
+    background-color: var(--bg-faded-color);
+  }
 }
diff --git a/src/pages/catchup.jsx b/src/pages/catchup.jsx
index a9aeed7f..fe5cd5a7 100644
--- a/src/pages/catchup.jsx
+++ b/src/pages/catchup.jsx
@@ -740,18 +740,23 @@ function Catchup() {
     'h, l',
     (_, handler) => {
       // Go next/prev selectedAuthor in authorCountsList list
+      const key = handler.keys[0];
       if (selectedAuthor) {
-        const key = handler.keys[0];
         const index = authorCountsList.indexOf(selectedAuthor);
         if (key === 'h') {
           if (index > 0 && index < authorCountsList.length) {
             setSelectedAuthor(authorCountsList[index - 1]);
+            scrollableRef.current?.focus();
           }
         } else if (key === 'l') {
           if (index < authorCountsList.length - 1 && index >= 0) {
             setSelectedAuthor(authorCountsList[index + 1]);
+            scrollableRef.current?.focus();
           }
         }
+      } else if (key === 'l') {
+        setSelectedAuthor(authorCountsList[0]);
+        scrollableRef.current?.focus();
       }
     },
     {
@@ -761,6 +766,34 @@ function Catchup() {
     },
   );
 
+  const escRef = useHotkeys(
+    'esc',
+    () => {
+      setSelectedAuthor(null);
+      scrollableRef.current?.focus();
+    },
+    {
+      preventDefault: true,
+      ignoreModifiers: true,
+      enableOnFormTags: ['input'],
+    },
+  );
+
+  const dotRef = useHotkeys(
+    '.',
+    () => {
+      scrollableRef.current?.scrollTo({
+        top: 0,
+        behavior: 'smooth',
+      });
+    },
+    {
+      preventDefault: true,
+      ignoreModifiers: true,
+      enableOnFormTags: ['input'],
+    },
+  );
+
   return (
     <div
       ref={(node) => {
@@ -768,6 +801,7 @@ function Catchup() {
         jRef.current = node;
         kRef.current = node;
         hlRef.current = node;
+        escRef.current = node;
       }}
       id="catchup-page"
       class="deck-container"
@@ -1430,6 +1464,25 @@ function Catchup() {
                   Posts are grouped by authors, sorted by posts count per
                   author.
                 </dd>
+                <dt>Keyboard shortcuts</dt>
+                <dd>
+                  <kbd>j</kbd>: Next post
+                </dd>
+                <dd>
+                  <kbd>k</kbd>: Previous post
+                </dd>
+                <dd>
+                  <kbd>l</kbd>: Next author
+                </dd>
+                <dd>
+                  <kbd>h</kbd>: Previous author
+                </dd>
+                <dd>
+                  <kbd>Enter</kbd>: Open post details
+                </dd>
+                <dd>
+                  <kbd>.</kbd>: Scroll to top
+                </dd>
               </dl>
             </main>
           </div>