// 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);