diff --git a/src/pages/search.jsx b/src/pages/search.jsx index 764c97d8..90daee9d 100644 --- a/src/pages/search.jsx +++ b/src/pages/search.jsx @@ -24,6 +24,12 @@ const SHORT_LIMIT = 5; const LIMIT = 40; const emptySearchParams = new URLSearchParams(); +const scrollIntoViewOptions = { + block: 'nearest', + inline: 'center', + behavior: 'smooth', +}; + function Search({ columnMode, ...props }) { const params = columnMode ? {} : useParams(); const { masto, instance, authenticated } = api({ @@ -186,10 +192,79 @@ function Search({ columnMode, ...props }) { }, ); + const itemsSelector = '.timeline > li > a, .hashtag-list > li > a'; + const jRef = useHotkeys('j', () => { + const activeItem = document.activeElement.closest(itemsSelector); + const activeItemRect = activeItem?.getBoundingClientRect(); + const allItems = Array.from( + scrollableRef.current.querySelectorAll(itemsSelector), + ); + if ( + activeItem && + activeItemRect.top < scrollableRef.current.clientHeight && + activeItemRect.bottom > 0 + ) { + const activeItemIndex = allItems.indexOf(activeItem); + let nextItem = allItems[activeItemIndex + 1]; + if (nextItem) { + nextItem.focus(); + nextItem.scrollIntoView(scrollIntoViewOptions); + } + } else { + const topmostItem = allItems.find((item) => { + const itemRect = item.getBoundingClientRect(); + return itemRect.top >= 44 && itemRect.left >= 0; + }); + if (topmostItem) { + topmostItem.focus(); + topmostItem.scrollIntoView(scrollIntoViewOptions); + } + } + }); + + const kRef = useHotkeys('k', () => { + // focus on previous status after active item + const activeItem = document.activeElement.closest(itemsSelector); + const activeItemRect = activeItem?.getBoundingClientRect(); + const allItems = Array.from( + scrollableRef.current.querySelectorAll(itemsSelector), + ); + if ( + activeItem && + activeItemRect.top < scrollableRef.current.clientHeight && + activeItemRect.bottom > 0 + ) { + const activeItemIndex = allItems.indexOf(activeItem); + let prevItem = allItems[activeItemIndex - 1]; + if (prevItem) { + prevItem.focus(); + prevItem.scrollIntoView(scrollIntoViewOptions); + } + } else { + const topmostItem = allItems.find((item) => { + const itemRect = item.getBoundingClientRect(); + return itemRect.top >= 44 && itemRect.left >= 0; + }); + if (topmostItem) { + topmostItem.focus(); + topmostItem.scrollIntoView(scrollIntoViewOptions); + } + } + }); + const [filterBarParent] = useAutoAnimate(); return ( -