[Glitch] Make post/feed navigation by hotkey more robust

Port 09689103390d10d77de5146483cbbb8abb5c94c9 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
This commit is contained in:
diondiondion 2026-06-05 10:39:23 +02:00 committed by Claire
parent a147932ce6
commit f0c48790a8
2 changed files with 69 additions and 19 deletions

View File

@ -89,7 +89,7 @@ import {
Quotes,
} from './util/async-components';
import { ColumnsContextProvider } from './util/columns_context';
import { focusColumn, getFocusedItemIndex, focusItemSibling, focusFirstItem } from './util/focusUtils';
import { focusColumn, getFocusedItemIndex, focusItemSibling, focusFirstItem, getFocusedColumnIndex } from './util/focusUtils';
import { WrappedSwitch, WrappedRoute } from './util/react_router_helpers';
import { CustomHomepage } from 'flavours/glitch/features/custom_homepage';
@ -552,20 +552,18 @@ class UI extends PureComponent {
handleMoveUp = () => {
const currentItemIndex = getFocusedItemIndex();
if (currentItemIndex === -1) {
focusColumn(1);
return focusColumn(getFocusedColumnIndex());
} else {
const wasHandled = focusItemSibling(currentItemIndex, -1);
return wasHandled;
return focusItemSibling(currentItemIndex, -1);
}
};
handleMoveDown = () => {
const currentItemIndex = getFocusedItemIndex();
if (currentItemIndex === -1) {
focusColumn(1);
return focusColumn(getFocusedColumnIndex());
} else {
const wasHandled = focusItemSibling(currentItemIndex, 1);
return wasHandled;
return focusItemSibling(currentItemIndex, 1);
}
};

View File

@ -62,18 +62,17 @@ export function focusColumn(index = 1) {
function fallback() {
focusColumnTitle(index + indexOffset, isMultiColumnLayout);
return false;
}
if (!column) {
fallback();
return;
return fallback();
}
const container = column.querySelector('.scrollable');
if (!container) {
fallback();
return;
return fallback();
}
const focusableItems = Array.from(
@ -86,8 +85,7 @@ export function focusColumn(index = 1) {
const itemToFocus = findFirstVisibleWithRect(focusableItems);
if (!itemToFocus) {
fallback();
return;
return fallback();
}
const viewportWidth =
@ -110,6 +108,7 @@ export function focusColumn(index = 1) {
itemToFocus.item.scrollIntoView(true);
}
itemToFocus.item.focus();
return true;
}
/**
@ -126,6 +125,18 @@ export function getFocusedItemIndex() {
return items.indexOf(focusedItem);
}
/**
* Get the index of the column that contains the user's focus
*/
export function getFocusedColumnIndex() {
const columnWithFocus = document.activeElement?.closest('.column');
if (!columnWithFocus) return 1;
const allColumns = Array.from(document.querySelectorAll('.column'));
return allColumns.indexOf(columnWithFocus) + 1;
}
/**
* Focus the topmost item of the column that currently has focus,
* or the first column if none
@ -159,12 +170,7 @@ export function focusItemSibling(index: number, direction: 1 | -1) {
);
if (!siblingItem) {
return false;
}
// If sibling element is empty, we skip it
if (siblingItem.matches(':empty')) {
return focusItemSibling(index + direction, direction);
return focusListSibling(direction);
}
// Check if the sibling is a post or a 'follow suggestions' widget
@ -177,6 +183,52 @@ export function focusItemSibling(index: number, direction: 1 | -1) {
targetElement = siblingItem;
}
// If sibling element is empty, we skip it
if (!targetElement || siblingItem.matches(':empty')) {
return focusItemSibling(index + direction, direction);
}
targetElement.scrollIntoView({
block: 'start',
});
targetElement.focus();
return true;
}
/**
* Finds the next or previous .item-list on the page or in the column,
* and focuses its first or last item.
*/
function focusListSibling(direction: 1 | -1) {
const container = document.activeElement?.closest('.item-list');
if (!container) {
return false;
}
// Get all item lists in the current column or page
const currentColumn = container.closest('.column') ?? document;
const columnItemLists = Array.from(
currentColumn.querySelectorAll('.item-list'),
);
const currentListIndex = columnItemLists.indexOf(container);
// Find the next or previous item-list
const listSibling = columnItemLists[currentListIndex + direction];
// Depending on the direction, find the first or last focusable in the list
let targetElement: HTMLElement | null | undefined;
if (direction > 0) {
targetElement = listSibling?.querySelector<HTMLElement>('.focusable');
} else {
const allFocusables =
listSibling?.querySelectorAll<HTMLElement>('.focusable');
targetElement = allFocusables?.[allFocusables.length - 1];
}
if (targetElement) {
targetElement.scrollIntoView({
block: 'start',