Description
Inspired from Support paging keyboard shortcuts in NAtOQ 10k-tools and Support paging keyboard shortcuts in Staging Ground. Currently several places that show a paged list of items do not have keyboard navigation. In some cases updates have made some keyboard shortcuts inaccessible, other times, they never really worked.
This userscript attempts to address all such places and either fix or add the capability to use the following:
- P L - to go to the last page
- P F - to go to the first page
- P N - to go to the next page
- P P - to go to the previous page
- P U - to select the first post (top)
- P J - to select the next post (down)
- P K - to select the previous post (up)
Additionally, it includes a white highlight for keyboard selected items (integrating this userstyle)from of selected items to make it that is active in
Regular dark mode:
High contrast dark mode:
Installation
(For instructions how to install userscripts: see the tag wiki here)
Code:
// ==UserScript==
// @name Keyboard navigation fixes
// @description Fix keyboard navigation shortcuts on most places to allow navigating to pages and going up/down the list.
// @author VLAZ
// @inject-into page
// @match https://stackoverflow.com/*
// @match https://serverfault.com/*
// @match https://superuser.com/*
// @match https://*.stackexchange.com/*
// @match https://askubuntu.com/*
// @match https://stackapps.com/*
// @match https://mathoverflow.net/*
// @match https://pt.stackoverflow.com/*
// @match https://ja.stackoverflow.com/*
// @match https://ru.stackoverflow.com/*
// @match https://es.stackoverflow.com/*
// @match https://meta.stackoverflow.com/*
// @match https://meta.serverfault.com/*
// @match https://meta.superuser.com/*
// @match https://meta.askubuntu.com/*
// @match https://meta.mathoverflow.net/*
// @match https://pt.meta.stackoverflow.com/*
// @match https://ja.meta.stackoverflow.com/*
// @match https://ru.meta.stackoverflow.com/*
// @match https://es.meta.stackoverflow.com/*
// @match https://stackoverflowteams.com/c/*
// @namespace https://github.com/PurpleMagick/
// @run-at document-end
// @grant GM_addStyle
// @grant GM.addStyle
// @version 1.0.0
// ==/UserScript==
/******/ (() => { // webpackBootstrap
;// ./src/fixes.js
/** appease all userscript managers */
const addStyle = (...args) => /* eslint-disable-next-line new-cap */
GM?.addStyle(...args) ?? GM_addStyle(...args);
/** make previous/next shortcuts work */
const fixPager = config => pager =>
pager.classList.add(...config.pagerClassесToAdd);
/** make "go to first page" work */
const fixFirstPage = config => pager => {
const firstPageButton = config.findFirstPage(pager);
firstPageButton?.setAttribute("title", config.titleForFirstPage);
};
/** make "go to last page" work */
const fixLastPage = config => pager => {
const currentPage = config.findCurrentPage(pager);
currentPage?.classList.add("current");
};
function fixAllPagers(config) {
config.findPagers().forEach(fixPager(config));
}
function fixAllFirstPages(config) {
config.findPagers().forEach(fixFirstPage(config));
}
function fixAllLastPages(config) {
config.findPagers().forEach(fixLastPage(config));
}
function reInitiKeybindings() {
//$.cache is missing some times which stops the destroy() call
$.cache ??= {};
$.cache[document[jQuery.expando]] ??= {};
$.cache[document[jQuery.expando]].events = {};
//turn it off and on again
StackExchange.keyboardShortcuts.destroy();
StackExchange.keyboardShortcuts.init();
}
/** make the keyboard selection highlight more visible in dark mode */
function fixHighlightInDarkmode() {
addStyle("body.theme-dark .keyboard-selected { box-shadow: 15px 15px 50px rgba(255, 255, 255, .2) inset; }");
}
;// ./src/pagePredicates.js
/** new answers to old questions tool*/
const isNATO = url =>
url.pathname.toLowerCase().contains("/new-answers-old-questions");
const isProtectedQuestions = url =>
url.pathname.toLowerCase().contains("/protected-questions");
const isSuggestedEdit10kTool = url =>
url.pathname.toLowerCase().startsWith("tools/suggested-edits");
/** questions page, custom filters, tag main page, tag search*/
const isQuestions = url =>
url.pathname.toLowerCase().startsWith("/questions");
/** regular search page */
const isSearch = url =>
url.pathname.toLowerCase().startsWith("/search");
const isAllUsers = url =>
url.pathname.toLowerCase() === "/users"
|| url.pathname.toLowerCase() === "/users/";
const isDiscussions = url =>
url.pathname.toLowerCase().contains("/discussions");
const isStagingGround = url =>
url.pathname.toLowerCase().startsWith("/staging-ground");
const userPage = page => url =>
url.pathname.toLowerCase().startsWith("/users/")
&& url.searchParams.get("tab")?.toLowerCase() === page;
const isUserAnswers = userPage("answers");
const isUserQuestions = userPage("questions");
const isUserActivity = userPage("activity");
;// ./src/lookups.js
function findPagers() {
return document.querySelectorAll(".s-pagination");
}
function findFirstPage(pager) {
const pages = pager.querySelectorAll("a[href]");
return [].find.call(pages, el => {
const searchParams = new URLSearchParams(el.search);
const isPage1 = searchParams.get("page") === "1"
|| searchParams.get("pg") === "1";
const noPage = !searchParams.has("page")
&& !searchParams.has("pg");
return isPage1 || noPage;
});
}
function findCurrentPage(pager) {
return pager.querySelector(".is-selected");
}
;// ./src/config.js
const ALL_CLASSES_FOR_PAGER = ["pager-questions", "pager-answers", "pager"];
const TITLE_FOR_FIRST_PAGE = "go to page 1";
const NO_CLASSES = [];
/** { predicate: Predicate, pagerClassесToAdd: string[], selectionFix: () => void } */
const PAGE_SPECIFIC_CONFIG = [
{
title: "10k tools - NATO",
predicate: isNATO,
pagerClassесToAdd: ["pager-answers"],
},
{
title: "10k tools - suggested edits stats",
predicate: isSuggestedEdit10kTool,
pagerClassесToAdd: ["pager"],
},
{
title: "10k tools - protected questions",
predicate: isProtectedQuestions,
pagerClassесToAdd: ["pager"],
},
{
title: "/questions",
predicate: isQuestions,
pagerClassесToAdd: NO_CLASSES,
},
{
title: "search pages",
predicate: isSearch,
pagerClassесToAdd: NO_CLASSES,
},
{
title: "/users",
predicate: isAllUsers,
pagerClassесToAdd: NO_CLASSES,
},
{
title: "User profile - activity page",
predicate: isUserActivity,
pagerClassесToAdd: NO_CLASSES,
},
{
title: "User profile - answers",
predicate: isUserAnswers,
pagerClassесToAdd: ["pager-answers"],
},
{
title: "User profile - questions",
predicate: isUserQuestions,
pagerClassесToAdd: ["pager-answers"],
},
{
title: "Discussions",
predicate: isDiscussions,
pagerClassесToAdd: NO_CLASSES,
selectionFix: () => {
document.querySelector(".s-post-summary")
.parentElement
.setAttribute("id", "questions");
}
},
{
title: "Staging ground",
predicate: isStagingGround,
pagerClassесToAdd: ["pager"],
selectionFix: () => {
document.querySelector(".js-listing-table-container")
.setAttribute("id", "questions");
},
}
];
/** set up script to run on the current page - select correct config */
function init() {
const url = new URL(location);
const config = PAGE_SPECIFIC_CONFIG
.find(({predicate}) => predicate(url)) ?? {};
//attach common config
config.pagerClassесToAdd ??= ALL_CLASSES_FOR_PAGER;
Object.assign(config, {
titleForFirstPage: TITLE_FOR_FIRST_PAGE,
findPagers: findPagers,
findFirstPage: findFirstPage,
findCurrentPage: findCurrentPage,
});
return config;
}
;// ./src/index.js
function main(config) {
fixAllPagers(config);
fixAllFirstPages(config);
fixAllLastPages(config);
fixHighlightInDarkmode(config);
if (config.selectionFix) {
config.selectionFix();
reInitiKeybindings(config);
}
}
const CONFIG = init();
main(CONFIG);
/******/ })();
Known issues
Page /tools/protected-questions:
- no keyboard selection
Page /tools/suggested-edits:
- no keyboard selection
Page user activity:
- no keyboard selection
- navigation shout re-initialise the previous/last
Page saves:
- no keyboard shortcuts at all - the script is not loaded (https://cdn.sstatic.net/Js/keyboard-shortcuts.en.js)
Page https://stackoverflow.com/jobs/companies:
- only first/last page work
- no previous/next page
- no first/next/previous post
If you find problems
Please mention the exact page where the issue is happening and what it is. Errors expected are:
- A keyboard shortcuts from the ones mentioned in the description does not work. The aim is to address list-related shortcuts.
- The pager section of a list seems wrong. Some HTML classes are added to pagers as it is required by the keyboard shortcuts but if the wrong class was added, the pagers may have odd sizes.