Add RES-style keyboard navigation
Add RES-style keyboard navigation
Implements keyboard shortcuts for navigating and interacting with posts and comments across Lemmy UI.
Closes #984
Keyboard Shortcuts
Posts (homepage, community pages, user profiles)
Navigation:
| Key | Action |
|---|---|
j |
Next post |
k |
Previous post |
J |
Last post |
K |
First post |
Pagination:
| Key | Action |
|---|---|
n |
Next page |
p |
Previous page |
P |
First page |
Actions:
| Key | Action |
|---|---|
a |
Upvote (toggle) |
z |
Downvote (toggle) |
s |
Save/unsave (toggle) |
x |
Expand post body (toggle) |
e |
Edit own post |
. |
Show advanced menu (toggle) |
Quick Navigation:
| Key | Action |
|---|---|
c |
Go to post comments (current tab) |
C |
Go to post comments (new tab) |
l |
Open post link (current tab) |
L |
Open post link (new tab) |
u |
Go to user profile (current tab) |
U |
Go to user profile (new tab) |
r |
Go to community (current tab) |
R |
Go to community (new tab) |
Comments (post detail pages)
Navigation:
| Key | Action |
|---|---|
j |
Next comment |
k |
Previous comment |
J |
Next sibling comment |
K |
Previous sibling comment |
p |
Parent comment |
t |
Thread root (top-level comment) |
Actions:
| Key | Action |
|---|---|
a |
Upvote (toggle) |
z |
Downvote (toggle) |
s |
Save/unsave (toggle) |
r |
Reply to comment |
e |
Edit own comment |
Enter |
Collapse/expand comment |
. |
Show advanced menu (toggle) |
Quick Navigation:
| Key | Action |
|---|---|
u |
Go to user profile (current tab) |
U |
Go to user profile (new tab) |
Implementation
I tried to take a least-intrusive approach and isolate the behavior to its own component, as well as to reduce code duplication. However, I may have over-engineered it a little, as this adds a lot more code in total than the predecessor PR.
On the other hand, a good chunk of the new code is comments and type declarations, which I hope contribute positively to overall complexity.
Testing
I've tested this manually on:
- Homepage post navigation (j/k) and pagination (n/p/P)
- Community page post navigation and pagination
- Post detail page with comments (j/k, J/K sibling nav, p/t parent/thread nav)
- User profile pages (mixed posts and comments) with pagination
- Empty pages (pagination shortcuts still work)
- Keyboard shortcuts while typing (properly ignored)
- Comment tree navigation (sibling, parent, thread root)
- First/last navigation (J/K in post lists)
Credits
Based on the implementation by @HamzahMansour in PR #1892.
Some code is by Claude (Sonnet 4.5).
In general the shortcuts are working well. Various things I noticed:
- The highlighted background color for posts should only be used after a shortcut is first pressed. For users on mobile or using mouse for navigation it should not be visible.
- Save post doesnt give any feedback so its not clear that it worked.
- Context menu for posts on homepage is broken,
.shortcut is also broken - It would make sense to close the current comment editor with
Escor similar. - A user guide on how to use these shortcuts would be nice. Either directly in the UI or in the documentation. Anyway this can be added later.
Thanks for testing!
The highlighted background color for posts should only be used after a shortcut is first pressed. For users on mobile or using mouse for navigation it should not be visible.
We can try this. In RES this is not a problem because you can explicitly enable or disable keyboard navigation.
Save post doesnt give any feedback so its not clear that it worked.
Agreed. I'll add a toast. Looks like we'll need new strings for localization after all.
Context menu for posts on homepage is broken,
.shortcut is also broken
For list mode, it looks like there is no context menu button at all. Since the . key simply clicks the button, we can't show it there in list mode.
It should be working OK in card and small card mode.
Do you think we should add the ⋮ button but hide it? Or we can add the button at all times, since it seems useful to have either way, but that's a little out of scope for this PR and more of an overall design question. Happy to do either.
A user guide on how to use these shortcuts would be nice. Either directly in the UI or in the documentation. Anyway this can be added later.
I have a draft for the documentation repository, let me upload it as well.
Save post doesnt give any feedback so its not clear that it worked.
Agreed. I'll add a toast. Looks like we'll need new strings for localization after all.
Another option would be to add a badge to the post / comment itself to visually indicate that the post is saved, but that's also a small design change.
Right it looks like the context menu from post listing was removed on main branch. Imo that should be added back. Inside the post view . doesnt work to open the context menu, it only highlights the menu button and then you need to click enter to open it.
The highlighted background color for posts should only be used after a shortcut is first pressed. For users on mobile or using mouse for navigation it should not be visible.
Done.
Save post doesnt give any feedback so its not clear that it worked.
Toast added. It seemed useful even when doing this with the mouse, so I made it show regardless of how the post is saved/unsaved.
Right it looks like the context menu from post listing was removed on main branch. Imo that should be added back.
OK, added some patches which do this.
A user guide on how to use these shortcuts would be nice. Either directly in the UI or in the documentation. Anyway this can be added later.
Here it is: https://github.com/LemmyNet/lemmy-docs/pull/395
Inside the post view
.doesnt work to open the context menu, it only highlights the menu button and then you need to click enter to open it.
I'm having quite a lot of trouble reproducing this. I managed to reproduce this once, and then no longer. I've tried multiple browsers and variations of the circumstances.
I suspect this has something to do with the Dropdown asset not being loaded at the time.
Do you have a way to reproduce this problem reliably?
Now the problem also disappeared for me, maybe it only happens on the first load or something like that?
It's possible, but this is stretching the limits of my front-end development knowledge, so I can't answer for sure.
If it's transient and the feature is not a commonly used one (I certainly have not used the RES equivalent personally), perhaps some flakiness is acceptable until we have more information? Up to you.
Yes thats fine if it cant be reproduced. Lets see what @dessalines says.
Adding a
KeyboardEventsreact component (doesn't even have to be generic, since all the functionality and events are so different), loaded at the screen level, IEhome,community,post.tsx, etc, not at the sub-component level.
Not sure how that would look like, since we have to deal with a good variety of objects (just posts on home/community page, post listing + comments on post pages, mixed items on the user page). Maybe have these expose a common interface? What do you have in mind?
- Instantianting keyboard navigation for every individual comment is overkill and doesn't make much sense.
For what it's worth, the approach I was going for was to separate actions (upvoting, saving, etc.) from navigation. Actions are handled by the object the action applies to, while navigation is handled by its container.
Not sure how that would look like, since we have to deal with a good variety of objects (just posts on home/community page, post listing + comments on post pages, mixed items on the user page). Maybe have these expose a common interface? What do you have in mind?
On the home.tsx page for instance, near the top of the page you'd do
<KeyboardCommentEvents
focusedComment={this.state.focusedComment}
onCommentSave={form => handleSaveComment(this, form)}
onVote={...}
onFocusNext={...}
onNavigateToComment={...}
...
/>
...
<KeyboardPostEvents
...
On the
home.tsxpage for instance, near the top of the page you'd do
Thanks. Would you mind illustrating how you envision this to work e.g. on the user page, which has both posts and comments? Would we have both KeyboardCommentEvents and KeyboardPostEvents, both registering keyboard handlers, but only acting if the currently selected item is of their preferred type? How would hand-over be done when navigating from a post to a comment, would these components share state about which item is selected?
Would you mind illustrating how you envision this to work e.g. on the user page, which has both posts and comments? Would we have both KeyboardCommentEvents and KeyboardPostEvents, both registering keyboard handlers, but only acting if the currently selected item is of their preferred type?
That does seem like the best way, although the navigation is beyond me. The personContentRes: RequestState<ListPersonContentResponse>; is an ordered array of items that are either comments or posts with a tagged type, so you could key off the index to decide which Events to be active.