Skip to content

Instantly share code, notes, and snippets.

@cemiu
Last active November 3, 2025 12:11
Show Gist options
  • Save cemiu/3a0847a2783b1898fc5815db772bf84f to your computer and use it in GitHub Desktop.
Save cemiu/3a0847a2783b1898fc5815db772bf84f to your computer and use it in GitHub Desktop.
surfingkeys config
// permalink:
// https://gist.github.com/cemiu/3a0847a2783b1898fc5815db772bf84f/raw/surfingkeys_config.js
// /*
api.unmapAllExcept([
'i', 'gi', // text box
'f', 'gf', // click link (active tab; non-active tab)
'e', 'd', 'j', 'k', // navigate page
'/', 'n', 'N', 'T',
';e',
// '?',
], null);
// */
api.RUNTIME('updateSettings', {settings: {"noPdfViewer": 1}});
settings.digitForRepeat = false;
settings.hintShiftNonActive = true;
// Disable Surfingkeys (will keep =a / =c enabled) //
api.unmapAllExcept([], /excalidraw.com/);
api.unmapAllExcept([], /outlook.office.com/);
api.unmapAllExcept([], /docs.google.com/);
api.unmapAllExcept([], /mail.notion.so/);
api.unmapAllExcept([], /maps.google.com/);
api.unmapAllExcept([], /sharepoint.com/);
api.unmapAllExcept([], /jup:/);
api.unmapAllExcept([], /cemiu.net/);
api.unmapAllExcept([], /vim-hero.com/);
api.unmap("f", /youtube.com/);
/////////////////////////////
// MORE OPTIONS (specific) //
/////////////////////////////
// util functions (bastardised version of https://github.com/b0o/surfingkeys-conf )
const [util, actions] = [{}, new Proxy({}, { get: (o, k) => o[k] || (o[k] = {}) })];
function registerMaps(maps, domain = null) { maps.forEach(({key, desc, action}) => { const options = domain ? {domain} : {}; api.mapkey(key, desc, action, options); }); }
util.defaultSelector = "a[href]:not([href^=javascript])";
util.createHints = (selector = util.defaultSelector, action = api.Hints.dispatchMouseClick, attrs = {}) => new Promise(resolve => { api.Hints.create(selector, (...args) => { resolve(...args); if (typeof action === "function") action(...args); }, attrs); });
util.isRectVisibleInViewport = rect => rect.height > 0 && rect.width > 0 && rect.bottom >= 0 && rect.right >= 0 && rect.top <= (window.innerHeight || document.documentElement.clientHeight) && rect.left <= (window.innerWidth || document.documentElement.clientWidth);
util.isElementInViewport = e => e.offsetHeight > 0 && e.offsetWidth > 0 && !e.getAttribute("disabled") && util.isRectVisibleInViewport(e.getBoundingClientRect());
util.doiSelector = "a[href*='doi.org/10.']";
actions.openAnchor = ({ newTab = false, active = true, prop = "href" } = {}) => (a) => actions.openLink(a[prop], { newTab, active });
actions.openLink = (url, { newTab = false, active = true } = {}) => { if (newTab) { api.RUNTIME("openLink", { tab: { tabbed: true, active }, url: url instanceof URL ? url.href : url, }); return; } window.location.assign(url) };
actions.getGoogleCacheUrl = ({ href = window.location.href } = {}) => `https://webcache.googleusercontent.com/search?q=cache:${href}`;
// actions.getWaybackUrl = ({ href = window.location.href } = {}) => `https://web.archive.org/web/*/${href}`;
actions.getArchiveTodayUrl = ({ href = window.location.href } = {}) => `https://archive.today/?run=1&url=${href.split("#")[0]}`;
actions.openInScidb = (href) => { const id = href.replace(/^https?:\/\/(dx\.)?doi\.org\//i, ""); actions.openLink(`https://annas-archive.org/scidb/${id}/`, { newTab: true }); };
actions.pagination.clickElementWithText = (targetTexts) => {
const candidates = Array.from(document.querySelectorAll('a, span')).filter(el => el.innerText && targetTexts.includes(el.innerText.trim()) && !el.hasAttribute('disabled'));
const elToClick = candidates.find(el => util.isElementInViewport(el)) || candidates[0];
elToClick ? elToClick.click() : api.Front.showBanner(`No suitable element found for: ${targetTexts.join(' / ')}`);
};
actions.pagination.clickPrevious = () => actions.pagination.clickElementWithText(["Previous"]);
actions.pagination.clickNext = () => actions.pagination.clickElementWithText(["Next", "More"]);
// adapted from https://github.com/t-mart/kill-sticky
actions.toggleSticky=(()=>{const ID="__killStickyCSS__",FIX="data-nosticky",OFL="data-nooverflow";return()=>{const s=document.getElementById(ID);if(s){document.querySelectorAll(`[${FIX}],[${OFL}]`).forEach(e=>{e.removeAttribute(FIX);e.removeAttribute(OFL)});s.remove();return}const css=`[${FIX}]{position:static!important;top:auto!important;bottom:auto!important}[${OFL}],html[${OFL}]{overflow:visible!important;overflow-x:visible!important;overflow-y:visible!important}`,st=document.createElement("style");st.id=ID;st.textContent=css;document.documentElement.appendChild(st);document.querySelectorAll("body *").forEach(n=>{const c=getComputedStyle(n);(c.position==="fixed"||c.position==="sticky")&&n.setAttribute(FIX,"");(c.overflow==="hidden"||c.overflowX==="hidden"||c.overflowY==="hidden")&&n.setAttribute(OFL,"")});const ch=getComputedStyle(document.documentElement);(ch.overflow==="hidden"||ch.overflowX==="hidden"||ch.overflowY==="hidden")&&document.documentElement.setAttribute(OFL,"")}})();
/////// KEYMAPS START HERE ////////
// All Pages //
const generalMaps = [
{key: "=c", desc: "Show Google's cached version of page", action: () => actions.openLink(actions.getGoogleCacheUrl(), { newTab: true })},
{key: "=a", desc: "Archive.today", action: () => actions.openLink(actions.getArchiveTodayUrl(), { newTab: true })},
{key: "=s", desc: "Open first DOI in Anna's Archive", action: () => { const a = document.querySelector(util.doiSelector); a ? actions.openInScidb(a.href) : api.Front.showBanner("No DOI link found"); }},
// {key: "=s", desc: "Open selected DOI in Anna's Archive", action: () => util.createHints(util.doiSelector, a => actions.openInScidb(a.href))},
{key: "}", desc: "Click 'Next' or 'More' link/button", action: actions.pagination.clickNext},
{key: "{", desc: "Click 'Previous' link/button", action: actions.pagination.clickPrevious},
{key: "`", desc: "Toggle kill sticky", action: () => actions.toggleSticky()},
];
registerMaps(generalMaps);
// Google //
const googleSearchResultSelector = "a:has(>h3),h3 a,a[href^='/search']:not(.fl):not(#pnnext,#pnprev):not([role]):not(.hide-focus-ring),g-scrolling-carousel a,.rc > div:nth-child(2) a,.kno-rdesc a,.kno-fv a,.isv-r > a:first-child,.dbsr > a:first-child,.X5OiLe,.WlydOe,.fl";
const googleMaps = [
{ key: "a", desc: "Open search result", action: () => util.createHints(googleSearchResultSelector) },
{ key: "A", desc: "Open search result (new tab)", action: () => util.createHints(googleSearchResultSelector, actions.openAnchor({ newTab: true, active: false })) },
// { key: "d", desc: "Open search in DuckDuckGo", action: actions.go.ddg }
];
registerMaps(googleMaps, /www.google.com/i);
// Hacker News //
actions.hn.goParent = () => { const par = document.querySelector(".navs>a[href^='item']"); if (!par) return; actions.openLink(par.href); };
actions.hn.collapseNextComment = () => { const vis = Array.from(document.querySelectorAll("a.togg")).filter(e => e.innerText === "[–]" && util.isElementInViewport(e)); if (vis.length > 0) vis[0].click(); };
actions.hn.goPage = (dist = 1) => { let u; try { u = new URL(window.location.href); } catch (e) { return; } let page = u.searchParams.get("p") || "1"; const cur = parseInt(page, 10); if (Number.isNaN(cur) || cur + dist < 1) return; u.searchParams.set("p", cur + dist); actions.openLink(u.href); };
actions.hn.openLinkAndComments = (e) => { const linkUrl = e.querySelector(".titleline>a").href; const commentsUrl = e.nextElementSibling.querySelector("a[href^='item']:not(.titlelink)").href; actions.openLink(commentsUrl, { newTab: true }); actions.openLink(linkUrl, { newTab: true }); };
const hnMaps = [
{key: "x", desc: "Collapse comment", action: () => util.createHints(".togg")},
{key: "X", desc: "Collapse next comment", action: actions.hn.collapseNextComment},
{key: "s", desc: "Upvote", action: () => util.createHints(".votearrow[title='upvote']")},
{key: "S", desc: "Downvote", action: () => util.createHints(".votearrow[title='downvote']")},
{key: "a", desc: "View post (link)", action: () => util.createHints(".titleline>a")},
{key: "A", desc: "View post (link and comments)", action: () => util.createHints(".athing", actions.hn.openLinkAndComments)},
{key: "c", desc: "View post (comments)", action: () => util.createHints(".subline>a[href^='item']")},
{key: "C", desc: "View post (comments) (non-active new tab)", action: () => util.createHints(".subline>a[href^='item']", actions.openAnchor({ newTab: true, active: false }))},
{key: "e", desc: "View external link", action: () => util.createHints("a[rel=nofollow]")},
{key: "gp", desc: "Go to parent", action: actions.hn.goParent},
{key: "]]", desc: "Next page", action: () => actions.hn.goPage(1)},
{key: "[[", desc: "Prev page", action: () => actions.hn.goPage(-1)},
];
registerMaps(hnMaps, /news.ycombinator.com/i);
// Wikipedia //
actions.wp._switchLang = (langCode) => {
if (document.documentElement.lang === langCode) return;
const selector = `.vector-menu-content-list a.interlanguage-link-target[hreflang='${langCode}'], #p-lang ul li a[hreflang='${langCode}']`;
const link = document.querySelector(selector);
link ? actions.openLink(link.href) : api.Front.showBanner(`Wikipedia page in '${langCode}' not found.`);
};
actions.wp.switchToDe = () => actions.wp._switchLang('de');
actions.wp.switchToEn = () => actions.wp._switchLang('en');
const wikipediaMaps = [
{ key: "ld", desc: "Switch to German (de) Wikipedia page", action: actions.wp.switchToDe },
{ key: "le", desc: "Switch to English (en) Wikipedia page", action: actions.wp.switchToEn }
];
registerMaps(wikipediaMaps, /.*\.wikipedia\.org/i);
// Reddit //
actions.re.collapseNextComment = () => {
const vis = Array.from(
document.querySelectorAll(".noncollapsed.comment")
).filter((e) => util.isElementInViewport(e))
if (vis.length > 0) {
vis[0].querySelector(".expand").click()
}
}
const redditMaps = [
{key: "x", desc: "Collapse comment", action: () => util.createHints(".expand")},
{key: "X", desc: "Collapse next comment", action: actions.re.collapseNextComment},
];
registerMaps(redditMaps, /reddit\.com/i);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment