Skip to content

Commit bd5e1ef

Browse files
committed
Keyboard shortcuts for navigating to the next message:
Previous thread: k, ctrl-p, up Next thread: j, ctrl-j, down Previous message in this thread: h, ctrl-b, left Next message in this thread: l, ctrl-f, right
1 parent da6a5e5 commit bd5e1ef

File tree

2 files changed

+54
-5
lines changed

2 files changed

+54
-5
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { Controller } from "@hotwired/stimulus"
2+
3+
export default class extends Controller {
4+
connect() {
5+
this.handleKeydown = this.handleKeydown.bind(this)
6+
document.addEventListener('keydown', this.handleKeydown)
7+
}
8+
9+
disconnect() {
10+
document.removeEventListener('keydown', this.handleKeydown)
11+
}
12+
13+
handleKeydown(event) {
14+
// Don't trigger if user is typing in an input field
15+
if (event.target.matches('input, textarea, select')) {
16+
return
17+
}
18+
19+
const key = event.key.toLowerCase()
20+
21+
// Define key mappings
22+
const keyMappings = {
23+
// Arrow keys
24+
'arrowup': 'prev-thread',
25+
'arrowdown': 'next-thread',
26+
'arrowleft': 'prev-message',
27+
'arrowright': 'next-message',
28+
// Vim-style (only when Ctrl is not pressed)
29+
'k': !event.ctrlKey ? 'prev-thread' : null,
30+
'j': !event.ctrlKey ? 'next-thread' : null,
31+
'h': !event.ctrlKey ? 'prev-message' : null,
32+
'l': !event.ctrlKey ? 'next-message' : null,
33+
// Emacs-style (only when Ctrl is pressed)
34+
'p': event.ctrlKey ? 'prev-thread' : null,
35+
'n': event.ctrlKey ? 'next-thread' : null,
36+
'b': event.ctrlKey ? 'prev-message' : null,
37+
'f': event.ctrlKey ? 'next-message' : null,
38+
}
39+
40+
const navAction = keyMappings[key]
41+
if (!navAction) return
42+
43+
const link = this.element.querySelector(`[data-nav="${navAction}"]`)
44+
if (link && !link.classList.contains('cursor-not-allowed')) {
45+
event.preventDefault()
46+
link.click()
47+
}
48+
}
49+
}

app/views/messages/_message.html.erb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,13 @@
3434
<% end %>
3535

3636
<% if defined?(@prev_thread) || defined?(@next_thread) || defined?(@prev_message_in_thread) || defined?(@next_message_in_thread) %>
37-
<div class="border-t border-gray-200 dark:border-gray-700 px-6 py-4 bg-gray-50 dark:bg-gray-900">
37+
<div class="border-t border-gray-200 dark:border-gray-700 px-6 py-4 bg-gray-50 dark:bg-gray-900" data-controller="keyboard-nav">
3838
<div class="grid grid-cols-2 gap-4">
3939
<div>
4040
<h3 class="text-xs font-medium text-gray-500 dark:text-gray-400 mb-2">Thread</h3>
4141
<div class="flex gap-2">
4242
<% if @prev_thread %>
43-
<%= link_to [message.list, @prev_thread], class: "inline-flex items-center gap-1 px-3 py-2 text-sm font-medium rounded bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors", data: {turbo_frame: 'message_content', turbo_action: 'advance'} do %>
43+
<%= link_to [message.list, @prev_thread], class: "inline-flex items-center gap-1 px-3 py-2 text-sm font-medium rounded bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors", data: {turbo_frame: 'message_content', turbo_action: 'advance', nav: 'prev-thread'} do %>
4444
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
4545
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
4646
</svg>
@@ -55,7 +55,7 @@
5555
</span>
5656
<% end %>
5757
<% if @next_thread %>
58-
<%= link_to [message.list, @next_thread], class: "inline-flex items-center gap-1 px-3 py-2 text-sm font-medium rounded bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors", data: {turbo_frame: 'message_content', turbo_action: 'advance'} do %>
58+
<%= link_to [message.list, @next_thread], class: "inline-flex items-center gap-1 px-3 py-2 text-sm font-medium rounded bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors", data: {turbo_frame: 'message_content', turbo_action: 'advance', nav: 'next-thread'} do %>
5959
Next
6060
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
6161
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
@@ -75,7 +75,7 @@
7575
<h3 class="text-xs font-medium text-gray-500 dark:text-gray-400 mb-2">In This Thread</h3>
7676
<div class="flex gap-2">
7777
<% if @prev_message_in_thread %>
78-
<%= link_to [message.list, @prev_message_in_thread], class: "inline-flex items-center gap-1 px-3 py-2 text-sm font-medium rounded bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors", data: {turbo_frame: 'message_content', turbo_action: 'advance'} do %>
78+
<%= link_to [message.list, @prev_message_in_thread], class: "inline-flex items-center gap-1 px-3 py-2 text-sm font-medium rounded bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors", data: {turbo_frame: 'message_content', turbo_action: 'advance', nav: 'prev-message'} do %>
7979
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
8080
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
8181
</svg>
@@ -90,7 +90,7 @@
9090
</span>
9191
<% end %>
9292
<% if @next_message_in_thread %>
93-
<%= link_to [message.list, @next_message_in_thread], class: "inline-flex items-center gap-1 px-3 py-2 text-sm font-medium rounded bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors", data: {turbo_frame: 'message_content', turbo_action: 'advance'} do %>
93+
<%= link_to [message.list, @next_message_in_thread], class: "inline-flex items-center gap-1 px-3 py-2 text-sm font-medium rounded bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors", data: {turbo_frame: 'message_content', turbo_action: 'advance', nav: 'next-message'} do %>
9494
Next
9595
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
9696
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>

0 commit comments

Comments
 (0)