Scoped keyboard shortcuts

This commit is contained in:
Lim Chee Aun 2023-02-06 23:47:58 +08:00
parent 4520822f1f
commit db428c04d1

View file

@ -21,7 +21,6 @@ function Home({ hidden }) {
useTitle('Home', '/'); useTitle('Home', '/');
const { masto, instance } = api(); const { masto, instance } = api();
const snapStates = useSnapshot(states); const snapStates = useSnapshot(states);
const isHomeLocation = snapStates.currentLocation === '/';
const [uiState, setUIState] = useState('default'); const [uiState, setUIState] = useState('default');
const [showMore, setShowMore] = useState(false); const [showMore, setShowMore] = useState(false);
@ -149,121 +148,103 @@ function Home({ hidden }) {
const scrollableRef = useRef(); const scrollableRef = useRef();
useHotkeys( const jRef = useHotkeys('j, shift+j', (_, handler) => {
'j, shift+j', // focus on next status after active status
(_, handler) => { // Traverses .timeline li .status-link, focus on .status-link
// focus on next status after active status const activeStatus = document.activeElement.closest(
// Traverses .timeline li .status-link, focus on .status-link '.status-link, .status-boost-link',
const activeStatus = document.activeElement.closest( );
const activeStatusRect = activeStatus?.getBoundingClientRect();
const allStatusLinks = Array.from(
scrollableRef.current.querySelectorAll(
'.status-link, .status-boost-link', '.status-link, .status-boost-link',
); ),
const activeStatusRect = activeStatus?.getBoundingClientRect(); );
const allStatusLinks = Array.from( if (
scrollableRef.current.querySelectorAll( activeStatus &&
'.status-link, .status-boost-link', activeStatusRect.top < scrollableRef.current.clientHeight &&
), activeStatusRect.bottom > 0
); ) {
if ( const activeStatusIndex = allStatusLinks.indexOf(activeStatus);
activeStatus && let nextStatus = allStatusLinks[activeStatusIndex + 1];
activeStatusRect.top < scrollableRef.current.clientHeight && if (handler.shift) {
activeStatusRect.bottom > 0 // get next status that's not .status-boost-link
) { nextStatus = allStatusLinks.find(
const activeStatusIndex = allStatusLinks.indexOf(activeStatus); (statusLink, index) =>
let nextStatus = allStatusLinks[activeStatusIndex + 1]; index > activeStatusIndex &&
if (handler.shift) { !statusLink.classList.contains('status-boost-link'),
// get next status that's not .status-boost-link );
nextStatus = allStatusLinks.find(
(statusLink, index) =>
index > activeStatusIndex &&
!statusLink.classList.contains('status-boost-link'),
);
}
if (nextStatus) {
nextStatus.focus();
nextStatus.scrollIntoViewIfNeeded?.();
}
} else {
// If active status is not in viewport, get the topmost status-link in viewport
const topmostStatusLink = allStatusLinks.find((statusLink) => {
const statusLinkRect = statusLink.getBoundingClientRect();
return statusLinkRect.top >= 44 && statusLinkRect.left >= 0; // 44 is the magic number for header height, not real
});
if (topmostStatusLink) {
topmostStatusLink.focus();
topmostStatusLink.scrollIntoViewIfNeeded?.();
}
} }
}, if (nextStatus) {
{ nextStatus.focus();
enabled: isHomeLocation, nextStatus.scrollIntoViewIfNeeded?.();
}, }
); } else {
// If active status is not in viewport, get the topmost status-link in viewport
const topmostStatusLink = allStatusLinks.find((statusLink) => {
const statusLinkRect = statusLink.getBoundingClientRect();
return statusLinkRect.top >= 44 && statusLinkRect.left >= 0; // 44 is the magic number for header height, not real
});
if (topmostStatusLink) {
topmostStatusLink.focus();
topmostStatusLink.scrollIntoViewIfNeeded?.();
}
}
});
useHotkeys( const kRef = useHotkeys('k, shift+k', (_, handler) => {
'k, shift+k', // focus on previous status after active status
(_, handler) => { // Traverses .timeline li .status-link, focus on .status-link
// focus on previous status after active status const activeStatus = document.activeElement.closest(
// Traverses .timeline li .status-link, focus on .status-link '.status-link, .status-boost-link',
const activeStatus = document.activeElement.closest( );
const activeStatusRect = activeStatus?.getBoundingClientRect();
const allStatusLinks = Array.from(
scrollableRef.current.querySelectorAll(
'.status-link, .status-boost-link', '.status-link, .status-boost-link',
); ),
const activeStatusRect = activeStatus?.getBoundingClientRect(); );
const allStatusLinks = Array.from( if (
scrollableRef.current.querySelectorAll( activeStatus &&
'.status-link, .status-boost-link', activeStatusRect.top < scrollableRef.current.clientHeight &&
), activeStatusRect.bottom > 0
); ) {
if ( const activeStatusIndex = allStatusLinks.indexOf(activeStatus);
activeStatus && let prevStatus = allStatusLinks[activeStatusIndex - 1];
activeStatusRect.top < scrollableRef.current.clientHeight && if (handler.shift) {
activeStatusRect.bottom > 0 // get prev status that's not .status-boost-link
) { prevStatus = allStatusLinks.findLast(
const activeStatusIndex = allStatusLinks.indexOf(activeStatus); (statusLink, index) =>
let prevStatus = allStatusLinks[activeStatusIndex - 1]; index < activeStatusIndex &&
if (handler.shift) { !statusLink.classList.contains('status-boost-link'),
// get prev status that's not .status-boost-link );
prevStatus = allStatusLinks.findLast(
(statusLink, index) =>
index < activeStatusIndex &&
!statusLink.classList.contains('status-boost-link'),
);
}
if (prevStatus) {
prevStatus.focus();
prevStatus.scrollIntoViewIfNeeded?.();
}
} else {
// If active status is not in viewport, get the topmost status-link in viewport
const topmostStatusLink = allStatusLinks.find((statusLink) => {
const statusLinkRect = statusLink.getBoundingClientRect();
return statusLinkRect.top >= 44 && statusLinkRect.left >= 0; // 44 is the magic number for header height, not real
});
if (topmostStatusLink) {
topmostStatusLink.focus();
topmostStatusLink.scrollIntoViewIfNeeded?.();
}
} }
}, if (prevStatus) {
{ prevStatus.focus();
enabled: isHomeLocation, prevStatus.scrollIntoViewIfNeeded?.();
}, }
); } else {
// If active status is not in viewport, get the topmost status-link in viewport
const topmostStatusLink = allStatusLinks.find((statusLink) => {
const statusLinkRect = statusLink.getBoundingClientRect();
return statusLinkRect.top >= 44 && statusLinkRect.left >= 0; // 44 is the magic number for header height, not real
});
if (topmostStatusLink) {
topmostStatusLink.focus();
topmostStatusLink.scrollIntoViewIfNeeded?.();
}
}
});
useHotkeys( const oRef = useHotkeys(['enter', 'o'], () => {
['enter', 'o'], // open active status
() => { const activeStatus = document.activeElement.closest(
// open active status '.status-link, .status-boost-link',
const activeStatus = document.activeElement.closest( );
'.status-link, .status-boost-link', if (activeStatus) {
); activeStatus.click();
if (activeStatus) { }
activeStatus.click(); });
}
},
{
enabled: isHomeLocation,
},
);
const { const {
scrollDirection, scrollDirection,
@ -306,6 +287,12 @@ function Home({ hidden }) {
const [showUpdatesButton, setShowUpdatesButton] = useState(false); const [showUpdatesButton, setShowUpdatesButton] = useState(false);
useEffect(() => { useEffect(() => {
const isNewAndTop = snapStates.homeNew.length > 0 && reachStart; const isNewAndTop = snapStates.homeNew.length > 0 && reachStart;
console.log(
'isNewAndTop',
isNewAndTop,
snapStates.homeNew.length,
reachStart,
);
setShowUpdatesButton(isNewAndTop); setShowUpdatesButton(isNewAndTop);
}, [snapStates.homeNew.length]); }, [snapStates.homeNew.length]);
@ -315,7 +302,12 @@ function Home({ hidden }) {
id="home-page" id="home-page"
class="deck-container" class="deck-container"
hidden={hidden} hidden={hidden}
ref={scrollableRef} ref={(node) => {
scrollableRef.current = node;
jRef.current = node;
kRef.current = node;
oRef.current = node;
}}
tabIndex="-1" tabIndex="-1"
> >
<div class="timeline-deck deck"> <div class="timeline-deck deck">