Experimental j,k,o,esc,backspace shortcuts

This commit is contained in:
Lim Chee Aun 2022-12-31 09:52:31 +08:00
parent 36a33e488b
commit b12b0c588d
3 changed files with 92 additions and 0 deletions

View file

@ -41,6 +41,7 @@ a.mention span {
overflow-x: hidden;
transition: opacity 0.1s ease-in-out;
overscroll-behavior: contain;
scroll-behavior: smooth;
}
.deck-container[hidden] {
display: block;

View file

@ -1,5 +1,6 @@
import { Link } from 'preact-router/match';
import { useEffect, useRef, useState } from 'preact/hooks';
import { useHotkeys } from 'react-hotkeys-hook';
import { InView } from 'react-intersection-observer';
import { useSnapshot } from 'valtio';
@ -71,6 +72,88 @@ function Home({ hidden }) {
const scrollableRef = useRef();
useHotkeys('j', () => {
// focus on next status after active status
// Traverses .timeline li .status-link, focus on .status-link
const activeStatus = document.activeElement.closest('.status-link');
const activeStatusRect = activeStatus?.getBoundingClientRect();
if (
activeStatus &&
activeStatusRect.top < scrollableRef.current.clientHeight &&
activeStatusRect.bottom > 0
) {
const nextStatus = activeStatus.parentElement.nextElementSibling;
if (nextStatus) {
const statusLink = nextStatus.querySelector('.status-link');
if (statusLink) {
statusLink.focus();
}
}
} else {
// If active status is not in viewport, get the topmost status-link in viewport
const statusLinks = document.querySelectorAll(
'.timeline li .status-link',
);
let topmostStatusLink;
for (const statusLink of statusLinks) {
const statusLinkRect = statusLink.getBoundingClientRect();
if (statusLinkRect.top >= 44) {
// 44 is the magic number for header height, not real
topmostStatusLink = statusLink;
break;
}
}
if (topmostStatusLink) {
topmostStatusLink.focus();
}
}
});
useHotkeys('k', () => {
// focus on previous status after active status
// Traverses .timeline li .status-link, focus on .status-link
const activeStatus = document.activeElement.closest('.status-link');
const activeStatusRect = activeStatus?.getBoundingClientRect();
if (
activeStatus &&
activeStatusRect.top < scrollableRef.current.clientHeight &&
activeStatusRect.bottom > 0
) {
const prevStatus = activeStatus.parentElement.previousElementSibling;
if (prevStatus) {
const statusLink = prevStatus.querySelector('.status-link');
if (statusLink) {
statusLink.focus();
}
}
} else {
// If active status is not in viewport, get the topmost status-link in viewport
const statusLinks = document.querySelectorAll(
'.timeline li .status-link',
);
let topmostStatusLink;
for (const statusLink of statusLinks) {
const statusLinkRect = statusLink.getBoundingClientRect();
if (statusLinkRect.top >= 44) {
// 44 is the magic number for header height, not real
topmostStatusLink = statusLink;
break;
}
}
if (topmostStatusLink) {
topmostStatusLink.focus();
}
}
});
useHotkeys(['enter', 'o'], () => {
// open active status
const activeStatus = document.activeElement.closest('.status-link');
if (activeStatus) {
activeStatus.click();
}
});
return (
<div
id="home-page"
@ -165,6 +248,8 @@ function Home({ hidden }) {
onChange={(inView) => {
if (inView) loadStatuses();
}}
root={scrollableRef.current}
rootMargin="100px 0px"
>
<Status skeleton />
</InView>

View file

@ -1,6 +1,7 @@
import './status.css';
import debounce from 'just-debounce-it';
import { route } from 'preact-router';
import { Link } from 'preact-router/match';
import {
useEffect,
@ -9,6 +10,7 @@ import {
useRef,
useState,
} from 'preact/hooks';
import { useHotkeys } from 'react-hotkeys-hook';
import { InView } from 'react-intersection-observer';
import { useSnapshot } from 'valtio';
@ -278,6 +280,10 @@ function StatusPage({ id }) {
return top > 0 ? 'down' : 'up';
}, [heroInView]);
useHotkeys(['esc', 'backspace'], () => {
route(closeLink);
});
return (
<div class="deck-backdrop">
<Link href={closeLink}></Link>