diff --git a/actions.js b/actions.js
new file mode 100644
index 0000000..21624ad
--- /dev/null
+++ b/actions.js
@@ -0,0 +1,187 @@
+const util = require("./util")
+
+const actions = {}
+
+// Globally applicable actions
+// ===========================
+
+// URL Manipulation/querying
+// -------------------------
+actions.vimEditURL = () => Front
+ .showEditor(window.location.href, (data) => {
+ window.location.href = data
+ }, "url")
+
+actions.getURLPath = ({ count = 0, domain = false } = {}) => {
+ let path = window.location.pathname.slice(1)
+ if (count) {
+ path = path.split("/").slice(0, count).join("/")
+ }
+ if (domain) {
+ path = `${window.location.hostname}/${path}`
+ }
+ return path
+}
+
+actions.copyURLPath = ({ count, domain } = {}) =>
+ () => Clipboard.write(actions.getURLPath({ count, domain }))
+
+// Whois/DNS lookup
+// ----------------
+const domainDossierUrl = "http://centralops.net/co/DomainDossier.aspx"
+
+actions.showWhois = ({ hostname = window.location.hostname } = {}) =>
+ () => tabOpenLink(`${domainDossierUrl}?dom_whois=true&addr=${hostname}`)
+
+actions.showDns = ({ hostname = window.location.hostname, verbose = false } = {}) =>
+ () => {
+ if (verbose) {
+ tabOpenLink(`${domainDossierUrl}?dom_whois=true&dom_dns=true&traceroute=true&net_whois=true&svc_scan=true&addr=${hostname}`)
+ } else {
+ tabOpenLink(`${domainDossierUrl}?dom_dns=true&addr=${hostname}`)
+ }
+ }
+
+// Surfingkeys-specific actions
+// ----------------------------
+actions.createHint = (selector, action = Hints.dispatchMouseClick) =>
+ () => Hints.create(selector, action)
+
+actions.open = ({ newTab = false, prop = "href" } = {}) => a => window.open(a[prop], newTab ? "_BLANK" : undefined)
+
+actions.editSettings = () => tabOpenLink("/pages/options.html")
+
+actions.togglePdfViewer = () =>
+ chrome.storage.local.get("noPdfViewer", (resp) => {
+ if (!resp.noPdfViewer) {
+ chrome.storage.local.set({ noPdfViewer: 1 }, () => {
+ Front.showBanner("PDF viewer disabled.")
+ })
+ } else {
+ chrome.storage.local.remove("noPdfViewer", () => {
+ Front.showBanner("PDF viewer enabled.")
+ })
+ }
+ })
+
+// Site-specific actions
+// =====================
+
+// FakeSpot
+// --------
+actions.fakeSpot = (url = window.location.href) => tabOpenLink(`http://fakespot.com/analyze?url=${url}`)
+
+// Godoc
+// -----
+actions.viewGodoc = () =>
+ tabOpenLink(`https://godoc.org/${actions.getURLPath({ count: 2, domain: true })}`)
+
+// GitHub
+// ------
+actions.gh = {}
+actions.gh.star = ({ toggle = false } = {}) => () => {
+ const repo = window.location.pathname.slice(1).split("/").slice(0, 2).join("/")
+ const container = document.querySelector("div.starring-container")
+ const status = container.classList.contains("on")
+
+ let star = "★"
+ let statusMsg = "starred"
+ let verb = "is"
+
+ if ((status && toggle) || (!status && !toggle)) {
+ statusMsg = `un${statusMsg}`
+ star = "☆"
+ }
+
+ if (toggle) {
+ verb = "has been"
+ if (status) {
+ container.querySelector(".starred>button").click()
+ } else {
+ container.querySelector(".unstarred>button").click()
+ }
+ }
+
+ Front.showBanner(`${star} Repository ${repo} ${verb} ${statusMsg}!`)
+}
+
+// GitLab
+// ------
+actions.gl = {}
+actions.gl.star = () => {
+ const repo = window.location.pathname.slice(1).split("/").slice(0, 2).join("/")
+ const btn = document.querySelector(".btn.star-btn > span")
+ btn.click()
+ const action = `${btn.textContent.toLowerCase()}red`
+ let star = "☆"
+ if (action === "starred") {
+ star = "★"
+ }
+ Front.showBanner(`${star} Repository ${repo} ${action}`)
+}
+
+// Reddit
+// ------
+actions.re = {}
+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()
+ }
+}
+
+// Unfortunately, this does not work - Reddit will only load the first
+// Expando
+actions.re.toggleVisibleExpandos = (dir = 0) => () => {
+ let sel = ".expando-button"
+ if (dir === -1) {
+ sel += ".expanded"
+ } else if (dir === 1) {
+ sel += ".collapsed"
+ }
+ console.log(sel)
+ Array.from(document.querySelectorAll(sel))
+ .filter(e => util.isElementInViewport(e))
+ .forEach(e => e.click())
+}
+
+// HackerNews
+// ----------
+actions.hn = {}
+actions.hn.goParent = () => {
+ const par = document.querySelector(".par>a")
+ if (!par) {
+ return
+ }
+ window.location.assign(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()
+ }
+}
+
+// Dribbble
+// --------
+actions.dr = {}
+actions.dr.attachment = (cb = a => tabOpenLink(a)) =>
+ actions.createHint(".attachments .thumb", a => cb(a.src.replace("/thumbnail/", "/")))
+
+// Wikipedia
+// ---------
+actions.wp = {}
+actions.wp.toggleSimple = () => {
+ window.location.hostname = window.location.hostname.split(".")
+ .map((s, i) => {
+ if (i === 0) {
+ return s === "simple" ? "" : "simple"
+ }
+ return s
+ }).filter(s => s !== "").join(".")
+}
+
+module.exports = actions
diff --git a/completions.js b/completions.js
index 7c49647..7750d6b 100644
--- a/completions.js
+++ b/completions.js
@@ -1,39 +1,20 @@
const { keys } = require("./conf.priv.js")
+const { escape, createSuggestionItem, createURLItem } = require("./util")
-function escape(str) {
- return String(str).replace(/[&<>"'`=/]/g, s => ({
- "&": "&",
- "<": "<",
- ">": ">",
- "\"": """,
- "'": "'",
- "/": "/",
- "`": "`",
- "=": "=",
- }[s]))
+const completions = {}
+
+// Helper functions for Google Custom Search Engine completions
+const googleCxURL = (alias) => {
+ const key = `google_cx_${alias}`
+ return `https://www.googleapis.com/customsearch/v1?key=${keys.google_cs}&cx=${keys[key]}&q=`
}
-function createSuggestionItem(html, props = {}) {
- const li = document.createElement("li")
- li.innerHTML = html
- return { html: li.outerHTML, props }
+const googleCxPublicURL = (alias) => {
+ const key = `google_cx_${alias}`
+ return `https://cse.google.com/cse/publicurl?cx=${keys[key]}&q=`
}
-function createURLItem(title, url, sanitize = true) {
- let t = title
- let u = url
- if (sanitize) {
- t = escape(t)
- u = new URL(u).toString()
- }
- return createSuggestionItem(`
-
@@ -43,19 +24,6 @@ function googleCxCallback(response) {
`, { url: s.link }))
}
-function googleCxURL(alias) {
- const key = `google_cx_${alias}`
- return `https://www.googleapis.com/customsearch/v1?key=${keys.google_cs}&cx=${keys[key]}&q=`
-}
-
-function googleCxPublicURL(alias) {
- const key = `google_cx_${alias}`
- return `https://cse.google.com/cse/publicurl?cx=${keys[key]}&q=`
-}
-
-// ****** Completions ****** //
-const completions = {}
-
// ****** Arch Linux ****** //
// Arch Linux official repos
@@ -330,13 +298,14 @@ const wpNoimg = "data:image/svg+xml,%3C%3Fxml%20version%3D%221.0%22%20encoding%3
completions.wp.callback = response => Object.values(JSON.parse(response.text).query.pages)
.map((p) => {
const img = p.thumbnail ? p.thumbnail.source : wpNoimg
+ const desc = p.description ? p.description : ""
return createSuggestionItem(
`
${p.title}
-
${p.description}
+
${desc}
`,
diff --git a/conf.js b/conf.js
index 799cc8d..ea4de9a 100644
--- a/conf.js
+++ b/conf.js
@@ -1,33 +1,6 @@
+const util = require("./util")
+const keys = require("./keys")
const completions = require("./completions")
-const { isElementInViewport } = require("./util")
-
-// Unmap undesired defaults
-const unmaps = [
- "sb", "sw", "ob",
- "ow", "cp", ";cp",
- ";ap", "spa", "spb",
- "spd", "sps", "spc",
- "spi", "sfr", "zQ",
- "zz", "zR", "ab",
- "Q", "q", "ag",
- "af", ";s", "yp",
-]
-
-unmaps.forEach((u) => {
- unmap(u)
-})
-
-const rmSearchAliases =
- {
- s: ["g", "d", "b",
- "w", "s", "h"],
- }
-
-Object.keys(rmSearchAliases).forEach((k) => {
- rmSearchAliases[k].forEach((v) => {
- removeSearchAliasX(v, k)
- })
-})
// ---- Settings ----//
settings.hintAlign = "left"
@@ -45,314 +18,15 @@ settings.theme = `
}
`
-// ---- Maps ----//
-// Left-hand aliases
-// Movement
-map("w", "k")
-map("s", "j")
-
-// Right-hand aliases
-// Tab Navigation
-map("J", "E")
-map("K", "R")
-
-// History
-map("H", "S")
-map("L", "D")
-
-
-// ---- Functions ----//
-
-const vimEditURL = () => Front
- .showEditor(window.location.href, (data) => {
- window.location.href = data
- }, "url")
-
-const domainDossier = "http://centralops.net/co/DomainDossier.aspx"
-
-const whois = () =>
- tabOpenLink(`${domainDossier}?dom_whois=true&addr=${window.location.hostname}`)
-
-const dns = () =>
- tabOpenLink(`${domainDossier}?dom_dns=true&addr=${window.location.hostname}`)
-
-const dnsVerbose = () =>
- tabOpenLink(`${domainDossier}?dom_whois=true&dom_dns=true&traceroute=true&net_whois=true&svc_scan=true&addr=${window.location.hostname}`)
-
-const togglePdfViewer = () =>
- chrome.storage.local.get("noPdfViewer", (resp) => {
- if (!resp.noPdfViewer) {
- chrome.storage.local.set({ noPdfViewer: 1 }, () => {
- Front.showBanner("PDF viewer disabled.")
- })
- } else {
- chrome.storage.local.remove("noPdfViewer", () => {
- Front.showBanner("PDF viewer enabled.")
- })
- }
- })
-
-const getURLPath = (count, domain) => {
- let path = window.location.pathname.slice(1)
- if (count) {
- path = path.split("/").slice(0, count).join("/")
- }
- if (domain) {
- path = `${window.location.hostname}/${path}`
- }
- return path
-}
-
-const copyURLPath = (count, domain) => () => Clipboard.write(getURLPath(count, domain))
-
-const editSettings = () => tabOpenLink("/pages/options.html")
-
-const Hint = (selector, action = Hints.dispatchMouseClick) => () => Hints.create(selector, action)
-
-// ---- Mapkeys ----//
-const ri = { repeatIgnore: true }
-
-// --- Global mappings ---//
-// 0: Help
-// 1: Mouse Click
-// 2: Scroll Page / Element
-// 3: Tabs
-// 4: Page Navigation
-mapkey("gi", "#4Edit current URL with vim editor", vimEditURL, ri)
-mapkey("gI", "#4View image in new tab", Hint("img", i => tabOpenLink(i.src)), ri)
-// 5: Sessions
-// 6: Search selected with
-// 7: Clipboard
-mapkey("yp", "#7Copy URL path of current page", copyURLPath(), ri)
-mapkey("yI", "#7Copy Image URL", Hint("img", i => Clipboard.write(i.src)), ri)
-// 8: Omnibar
-// 9: Visual Mode
-// 10: vim-like marks
-// 11: Settings
-mapkey(";se", "#11Edit Settings", editSettings, ri)
-// 12: Chrome URLs
-mapkey("gS", "#12Open Chrome settings", () => tabOpenLink("chrome://settings/"))
-// 13: Proxy
-// 14: Misc
-mapkey("=w", "#14Lookup whois information for domain", whois, ri)
-mapkey("=d", "#14Lookup dns information for domain", dns, ri)
-mapkey("=D", "#14Lookup all information for domain", dnsVerbose, ri)
-mapkey(";pd", "#14Toggle PDF viewer from SurfingKeys", togglePdfViewer, ri)
-// 15: Insert Mode
-
-// --- Site-specific mappings ---//
+// Leader for site-specific mappings
const siteleader = "
"
-function mapsitekey(domainRegex, key, desc, f, opts = {}) {
- const o = Object.assign({}, {
- leader: siteleader,
- }, opts)
- mapkey(`${o.leader}${key}`, desc, f, { domain: domainRegex })
-}
+// Leader for OmniBar searchEngines
+const searchleader = "a"
-function mapsitekeys(d, maps, opts = {}) {
- const domain = d.replace(".", "\\.")
- const domainRegex = new RegExp(`^http(s)?://(([a-zA-Z0-9-_]+\\.)*)(${domain})(/.*)?`)
- maps.forEach((map) => {
- const [
- key,
- desc,
- f,
- subOpts = {},
- ] = map
- mapsitekey(domainRegex, key, desc, f, Object.assign({}, opts, subOpts))
- })
-}
-
-const fakeSpot = () => tabOpenLink(`http://fakespot.com/analyze?url=${window.location.href}`)
-
-mapsitekeys("amazon.com", [
- ["fs", "Fakespot", fakeSpot],
-])
-
-mapsitekeys("yelp.com", [
- ["fs", "Fakespot", fakeSpot],
-])
-
-const ytFullscreen = () => document
- .querySelector(".ytp-fullscreen-button.ytp-button")
- .click()
-
-mapsitekeys("youtube.com", [
- ["A", "Open video", Hint("*[id='video-title']")],
- ["C", "Open channel", Hint("*[id='byline']")],
- ["gH", "Goto homepage", () => window.location.assign("https://www.youtube.com/feed/subscriptions?flow=2")],
- ["F", "Toggle fullscreen", ytFullscreen],
- ["", "Play/pause", Hint(".ytp-play-button")],
-], { leader: "" })
-
-
-const vimeoFullscreen = () => document
- .querySelector(".fullscreen-icon")
- .click()
-
-mapsitekeys("vimeo.com", [
- ["F", "Toggle fullscreen", vimeoFullscreen],
-])
-
-const ghStar = toggle => () => {
- const repo = window.location.pathname.slice(1).split("/").slice(0, 2).join("/")
- const container = document.querySelector("div.starring-container")
- const status = container.classList.contains("on")
-
- let star = "★"
- let statusMsg = "starred"
- let verb = "is"
-
- if ((status && toggle) || (!status && !toggle)) {
- statusMsg = `un${statusMsg}`
- star = "☆"
- }
-
- if (toggle) {
- verb = "has been"
- if (status) {
- container.querySelector(".starred>button").click()
- } else {
- container.querySelector(".unstarred>button").click()
- }
- }
-
- Front.showBanner(`${star} Repository ${repo} ${verb} ${statusMsg}!`)
-}
-
-const viewGodoc = () => tabOpenLink(`https://godoc.org/${getURLPath(2, true)}`)
-
-mapsitekeys("github.com", [
- ["s", "Toggle Star", ghStar(true)],
- ["S", "Check Star", ghStar(false)],
- ["y", "Copy Project Path", copyURLPath(2)],
- ["Y", "Copy Project Path (including domain)", copyURLPath(2, true)],
- ["D", "View GoDoc for Project", viewGodoc],
-])
-
-const glToggleStar = () => {
- const repo = window.location.pathname.slice(1).split("/").slice(0, 2).join("/")
- const btn = document.querySelector(".btn.star-btn > span")
- btn.click()
- const action = `${btn.textContent.toLowerCase()}red`
- let star = "☆"
- if (action === "starred") {
- star = "★"
- }
- Front.showBanner(`${star} Repository ${repo} ${action}`)
-}
-
-mapsitekeys("gitlab.com", [
- ["s", "Toggle Star", glToggleStar],
- ["y", "Copy Project Path", copyURLPath(2)],
- ["Y", "Copy Project Path (including domain)", copyURLPath(2, true)],
- ["D", "View GoDoc for Project", viewGodoc],
-])
-
-mapsitekeys("twitter.com", [
- ["f", "Follow user", Hint(".follow-button")],
- ["s", "Like tweet", Hint(".js-actionFavorite")],
- ["R", "Retweet", Hint(".js-actionRetweet")],
- ["c", "Comment/Reply", Hint(".js-actionReply")],
- ["t", "New tweet", Hint(".js-global-new-tweet")],
- ["T", "Tweet to", Hint(".NewTweetButton")],
- ["r", "Load new tweets", Hint(".new-tweets-bar")],
- ["g", "Goto user", Hint(".js-user-profile-link")],
-])
-
-const redditCollapseNextComment = () => {
- const vis = Array.from(document.querySelectorAll(".noncollapsed.comment"))
- .filter(e => isElementInViewport(e))
- if (vis.length > 0) {
- vis[0].querySelector(".expand").click()
- }
-}
-
-mapsitekeys("reddit.com", [
- ["x", "Collapse comment", Hint(".expand")],
- ["X", "Collapse next comment", redditCollapseNextComment],
- ["s", "Upvote", Hint(".arrow.up")],
- ["S", "Downvote", Hint(".arrow.down")],
- ["e", "Expand expando", Hint(".expando-button")],
- ["a", "View post (link)", Hint(".title")],
- ["c", "View post (comments)", Hint(".comments")],
-])
-
-const hnGoParent = () => {
- const par = document.querySelector(".par>a")
- if (!par) {
- return
- }
- window.location.assign(par.href)
-}
-
-const hnCollapseNextComment = () => {
- const vis = Array.from(document.querySelectorAll("a.togg"))
- .filter(e => e.innerText === "[-]" && isElementInViewport(e))
- if (vis.length > 0) {
- vis[0].click()
- }
-}
-
-mapsitekeys("news.ycombinator.com", [
- ["x", "Collapse comment", Hint(".togg")],
- ["X", "Collapse next comment", hnCollapseNextComment],
- ["s", "Upvote", Hint(".votearrow[title='upvote']")],
- ["S", "Downvote", Hint(".votearrow[title='downvote']")],
- ["a", "View post (link)", Hint(".storylink")],
- ["c", "View post (comments)", Hint("td > a[href*='item']:not(.storylink)")],
- ["p", "Go to parent", hnGoParent],
-])
-
-const dribbbleAttachment = cb =>
- Hint(".attachments .thumb", a => cb(a.src.replace("/thumbnail/", "/")))
-
-mapsitekeys("dribbble.com", [
- ["s", "Heart Shot", Hint(".toggle-fav, .like-shot")],
- ["a", "View shot", Hint(".dribbble-over, .gif-target, .more-thumbs a")],
- ["A", "View shot", Hint(".dribbble-over, .gif-target, .more-thumbs a")],
- ["v", "View attachment image", dribbbleAttachment(a => tabOpenLink(a))],
- ["V", "Yank attachment image source URL", dribbbleAttachment(a => Clipboard.write(a))],
- ["z", "Zoom shot", Hint(".single-img picture, .detail-shot img")],
-])
-
-const behanceAddToCollection = () => document.querySelector(".qa-action-collection").click()
-
-mapsitekeys("behance.net", [
- ["s", "Appreciate project", Hint(".appreciation-button")],
- ["b", "Add project to collection", behanceAddToCollection],
- ["a", "View project", Hint(".rf-project-cover__title")],
- ["A", "View project", Hint(".rf-project-cover__title")],
-])
-
-const wpToggleSimple = () => {
- window.location.hostname = window.location.hostname.split(".")
- .map((s, i) => {
- if (i === 0) {
- return s === "simple" ? "" : "simple"
- }
- return s
- }).filter(s => s !== "").join(".")
-}
-
-mapsitekeys("wikipedia.org", [
- ["s", "Toggle simple version of current article", wpToggleSimple],
-])
-
-// ---- Search & completion ----//
-// Search leader
-const sl = "a"
-
-// Register Search Engine Completions
-// The `completions` variable is defined in `completions.js` and
-// is prepended to this file by gulp-concat.
-Object.keys(completions).forEach((k) => {
- const s = completions[k] // Search Engine object
- const la = sl + s.alias // Search leader + alias
-
- addSearchAliasX(s.alias, s.name, s.search, sl, s.compl, s.callback)
- mapkey(la, `#8Search ${s.name}`, () => Front.openOmnibar({ type: "SearchEngine", extra: s.alias }))
-})
-
-// vim: set ft=javascript expandtab:
+// Process mappings and completions
+// See ./keys.js and ./completions.js
+util.rmMaps(keys.unmaps.mappings)
+util.rmSearchAliases(keys.unmaps.searchAliases)
+util.processMaps(keys.maps, siteleader)
+util.processCompletions(completions, searchleader)
diff --git a/gulpfile.js b/gulpfile.js
index 240f5b3..5122c90 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -11,7 +11,7 @@ const { spawn } = require("child_process")
const { URL } = require("url")
const paths = {
- scripts: ["conf.priv.js", "completions.js", "conf.js"],
+ scripts: ["conf.priv.js", "completions.js", "conf.js", "actions.js", "help.js", "keys.js", "util.js"],
entry: "conf.js",
gulpfile: ["gulpfile.js"],
readme: ["README.tmpl.md"],
diff --git a/help.js b/help.js
new file mode 100644
index 0000000..d9bcacd
--- /dev/null
+++ b/help.js
@@ -0,0 +1,19 @@
+// Surfingkeys help categories
+module.exports.categories = {
+ help: 0,
+ mouseClick: 1,
+ scroll: 2,
+ tabs: 3,
+ pageNav: 4,
+ sessions: 5,
+ searchSelectedWith: 6,
+ clipboard: 7,
+ omnibar: 8,
+ visualMode: 9,
+ vimMarks: 10,
+ settings: 11,
+ chromeURLs: 12,
+ proxy: 13,
+ misc: 14,
+ insertMode: 15,
+}
diff --git a/keys.js b/keys.js
new file mode 100644
index 0000000..1f85a63
--- /dev/null
+++ b/keys.js
@@ -0,0 +1,442 @@
+const actions = require("./actions")
+const { categories } = require("./help")
+
+// Remove undesired default mappings
+const unmaps = {
+ mappings: [
+ "sb", "sw", "ob",
+ "ow", "cp", ";cp",
+ ";ap", "spa", "spb",
+ "spd", "sps", "spc",
+ "spi", "sfr", "zQ",
+ "zz", "zR", "ab",
+ "Q", "q", "ag",
+ "af", ";s", "yp",
+ ],
+ searchAliases: {
+ s: ["g", "d", "b",
+ "w", "s", "h"],
+ },
+}
+
+const maps = {
+ global: [
+ {
+ alias: "w",
+ map: "k",
+ category: categories.scroll,
+ description: "Scroll up",
+ },
+ {
+ alias: "s",
+ map: "j",
+ category: categories.scroll,
+ description: "Scroll down",
+ },
+ {
+ alias: "gi",
+ category: categories.pageNav,
+ description: "Edit current URL with vim editor",
+ callback: actions.vimEditURL,
+ },
+ {
+ alias: "gi",
+ category: categories.pageNav,
+ description: "Edit current URL with vim editor",
+ callback: actions.vimEditURL,
+ },
+ {
+ alias: "gI",
+ category: categories.pageNav,
+ description: "View image in new tab",
+ callback: actions.createHint("img", i => tabOpenLink(i.src)),
+ },
+ {
+ alias: "yp",
+ category: categories.clipboard,
+ description: "Copy URL path of current page",
+ callback: actions.copyURLPath(),
+ },
+ {
+ alias: "yI",
+ category: categories.clipboard,
+ description: "Copy Image URL",
+ callback: actions.createHint("img", i => Clipboard.write(i.src)),
+ },
+ {
+ alias: ";se",
+ category: categories.settings,
+ description: "Edit Settings",
+ callback: actions.editSettings,
+ },
+ {
+ alias: "gS",
+ category: categories.chromeURLs,
+ description: "Open Chrome settings",
+ },
+ {
+ alias: "=w",
+ category: categories.misc,
+ description: "Lookup whois information for domain",
+ callback: actions.showWhois,
+ },
+ {
+ alias: "=d",
+ category: categories.misc,
+ description: "Lookup dns information for domain",
+ callback: actions.showDns(),
+ },
+ {
+ alias: "=D",
+ category: categories.misc,
+ description: "Lookup all information for domain",
+ callback: actions.showDns({ verbose: true }),
+ },
+ {
+ alias: ";pd",
+ category: categories.misc,
+ description: "Toggle PDF viewer from SurfingKeys",
+ callback: actions.togglePdfViewer,
+ },
+ ],
+
+ "amazon.com": [
+ {
+ alias: "fs",
+ description: "Fakespot",
+ callback: actions.fakeSpot,
+ },
+ ],
+
+ "google.com": [
+ {
+ alias: "a",
+ description: "Open search result",
+ callback: actions.createHint(".r>a"),
+ },
+ {
+ alias: "A",
+ description: "Open search result (new tab)",
+ callback: actions.createHint(".r>a", actions.open({ newTab: true })),
+ },
+ ],
+
+
+ "yelp.com": [
+ {
+ alias: "fs",
+ description: "Fakespot",
+ callback: actions.fakeSpot,
+ },
+ ],
+
+ "youtube.com": [
+ {
+ leader: "",
+ alias: "A",
+ description: "Open video",
+ callback: actions.createHint("*[id='video-title']", actions.open({ newTab: true })),
+ },
+ {
+ leader: "",
+ alias: "C",
+ description: "Open channel",
+ callback: actions.createHint("*[id='byline']"),
+ },
+ {
+ leader: "",
+ alias: "gH",
+ description: "Goto homepage",
+ callback: () => window.location.assign("https://www.youtube.com/feed/subscriptions?flow=2"),
+ },
+ {
+ leader: "",
+ alias: "F",
+ description: "Toggle fullscreen",
+ callback: () => document.querySelector(".ytp-fullscreen-button.ytp-button").click(),
+ },
+ {
+ leader: "",
+ alias: "",
+ description: "Play/pause",
+ callback: actions.createHint(".ytp-play-button"),
+ },
+ ],
+
+ "vimeo.com": [
+ {
+ alias: "F",
+ description: "Toggle fullscreen",
+ callback: () => document.querySelector(".fullscreen-icon").click(),
+ },
+ ],
+
+ "github.com": [
+ {
+ alias: "s",
+ description: "Toggle Star",
+ callback: actions.gh.star({ toggle: true }),
+ },
+ {
+ alias: "S",
+ description: "Check Star",
+ callback: actions.gh.star({ toggle: false }),
+ },
+ {
+ alias: "y",
+ description: "Copy Project Path",
+ callback: actions.copyURLPath({ count: 2 }),
+ },
+ {
+ alias: "Y",
+ description: "Copy Project Path (including domain)",
+ callback: actions.copyURLPath({ count: 2, domain: true }),
+ },
+ {
+ alias: "D",
+ description: "View GoDoc for Project",
+ callback: actions.viewGodoc,
+ },
+ ],
+
+ "gitlab.com": [
+ {
+ alias: "s",
+ description: "Toggle Star",
+ callback: actions.gl.star,
+ },
+ {
+ alias: "y",
+ description: "Copy Project Path",
+ callback: actions.copyURLPath({ count: 2 }),
+ },
+ {
+ alias: "Y",
+ description: "Copy Project Path (including domain)",
+ callback: actions.copyURLPath({ count: 2, domain: true }),
+ },
+ {
+ alias: "D",
+ description: "View GoDoc for Project",
+ callback: actions.viewGodoc,
+ },
+ ],
+
+ "twitter.com": [
+ {
+ alias: "f",
+ description: "Follow user",
+ callback: actions.createHint(".follow-button"),
+ },
+ {
+ alias: "s",
+ description: "Like tweet",
+ callback: actions.createHint(".js-actionFavorite"),
+ },
+ {
+ alias: "R",
+ description: "Retweet",
+ callback: actions.createHint(".js-actionRetweet"),
+ },
+ {
+ alias: "c",
+ description: "Comment/Reply",
+ callback: actions.createHint(".js-actionReply"),
+ },
+ {
+ alias: "t",
+ description: "New tweet",
+ callback: actions.createHint(".js-global-new-tweet"),
+ },
+ {
+ alias: "T",
+ description: "Tweet to",
+ callback: actions.createHint(".NewTweetButton"),
+ },
+ {
+ alias: "r",
+ description: "Load new tweets",
+ callback: actions.createHint(".new-tweets-bar"),
+ },
+ {
+ alias: "g",
+ description: "Goto user",
+ callback: actions.createHint(".js-user-profile-link"),
+ },
+ ],
+
+ "reddit.com": [
+ {
+ alias: "x",
+ description: "Collapse comment",
+ callback: actions.createHint(".expand"),
+ },
+ {
+ alias: "X",
+ description: "Collapse next comment",
+ callback: actions.re.collapseNextComment,
+ },
+ {
+ alias: "s",
+ description: "Upvote",
+ callback: actions.createHint(".arrow.up"),
+ },
+ {
+ alias: "S",
+ description: "Downvote",
+ callback: actions.createHint(".arrow.down"),
+ },
+ {
+ alias: "e",
+ description: "Expand expando",
+ callback: actions.createHint(".expando-button"),
+ },
+ {
+ alias: "a",
+ description: "View post (link)",
+ callback: actions.createHint(".title"),
+ },
+ {
+ alias: "A",
+ description: "View post (link) (new tab)",
+ callback: actions.createHint(".title", actions.open({ newTab: true })),
+ },
+ {
+ alias: "c",
+ description: "View post (comments)",
+ callback: actions.createHint(".comments"),
+ },
+ {
+ alias: "C",
+ description: "View post (comments) (new tab)",
+ callback: actions.createHint(".comments", actions.open({ newTab: true })),
+ },
+ ],
+
+ "news.ycombinator.com": [
+ {
+ alias: "x",
+ description: "Collapse comment",
+ callback: actions.createHint(".togg"),
+ },
+ {
+ alias: "X",
+ description: "Collapse next comment",
+ callback: actions.hn.collapseNextComment,
+ },
+ {
+ alias: "s",
+ description: "Upvote",
+ callback: actions.createHint(".votearrow[title='upvote']"),
+ },
+ {
+ alias: "S",
+ description: "Downvote",
+ callback: actions.createHint(".votearrow[title='downvote']"),
+ },
+ {
+ alias: "a",
+ description: "View post (link)",
+ callback: actions.createHint(".storylink"),
+ },
+ {
+ alias: "A",
+ description: "View post (link) (new tab)",
+ callback: actions.createHint(".storylink", actions.open({ newTab: true })),
+ },
+ {
+ alias: "c",
+ description: "View post (comments)",
+ callback: actions.createHint("td > a[href*='item']:not(.storylink)"),
+ },
+ {
+ alias: "C",
+ description: "View post (comments) (new tab)",
+ callback: actions.createHint("td > a[href*='item']:not(.storylink)", actions.open({ newTab: true })),
+ },
+ {
+ alias: "p",
+ description: "Go to parent",
+ callback: actions.hn.goParent,
+ },
+ ],
+
+ "dribbble.com": [
+ {
+ alias: "s",
+ description: "Heart Shot",
+ callback: actions.createHint(".toggle-fav, .like-shot"),
+ },
+ {
+ alias: "a",
+ description: "View shot",
+ callback: actions.createHint(".dribbble-over, .gif-target, .more-thumbs a"),
+ },
+ {
+ alias: "A",
+ description: "View shot (new tab)",
+ callback: actions.createHint(".dribbble-over, .gif-target, .more-thumbs a", actions.open({ newTab: true })),
+ },
+ {
+ alias: "v",
+ description: "View attachment image",
+ callback: actions.dr.attachment(),
+ },
+ {
+ alias: "V",
+ description: "Yank attachment image source URL",
+ callback: actions.dr.attachment(a => Clipboard.write(a)),
+ },
+ {
+ alias: "z",
+ description: "Zoom shot",
+ callback: actions.createHint(".single-img picture, .detail-shot img"),
+ },
+ ],
+
+ "behance.net": [
+ {
+ alias: "s",
+ description: "Appreciate project",
+ callback: actions.createHint(".appreciation-button"),
+ },
+ {
+ alias: "b",
+ description: "Add project to collection",
+ callback: () => document.querySelector(".qa-action-collection").click(),
+ },
+ {
+ alias: "a",
+ description: "View project",
+ callback: actions.createHint(".rf-project-cover__title"),
+ },
+ {
+ alias: "A",
+ description: "View project (new tab)",
+ callback: actions.createHint(".rf-project-cover__title", actions.open({ newTab: true })),
+ },
+ ],
+
+ "fonts.adobe.com": [
+ {
+ alias: "a",
+ description: "Activate font",
+ callback: actions.createHint(".spectrum-ToggleSwitch-input"),
+ },
+ {
+ alias: "s",
+ description: "Favorite font",
+ callback: actions.createHint(".favorite-toggle-icon"),
+ },
+ ],
+
+ "wikipedia.org": [
+ {
+ alias: "s",
+ description: "Toggle simple version of current article",
+ callback: actions.wp.toggleSimple,
+ },
+ ],
+}
+
+module.exports = { unmaps, maps }
diff --git a/util.js b/util.js
index ec2edb9..7d4fafb 100644
--- a/util.js
+++ b/util.js
@@ -1,4 +1,39 @@
-const isRectVisibleInViewport = rect => (
+const { categories } = require("./help")
+
+const util = {}
+
+util.escape = str => String(str).replace(/[&<>"'`=/]/g, s => ({
+ "&": "&",
+ "<": "<",
+ ">": ">",
+ "\"": """,
+ "'": "'",
+ "/": "/",
+ "`": "`",
+ "=": "=",
+}[s]))
+
+util.createSuggestionItem = (html, props = {}) => {
+ const li = document.createElement("li")
+ li.innerHTML = html
+ return { html: li.outerHTML, props }
+}
+
+util.createURLItem = (title, url, sanitize = true) => {
+ let t = title
+ let u = url
+ if (sanitize) {
+ t = util.escape(t)
+ u = new URL(u).toString()
+ }
+ return util.createSuggestionItem(`
+ ${t}
+ ${u}
+ `, { url: u })
+}
+
+// Determine if the given rect is visible in the viewport
+util.isRectVisibleInViewport = rect => (
rect.height > 0 &&
rect.width > 0 &&
rect.bottom >= 0 &&
@@ -7,6 +42,51 @@ const isRectVisibleInViewport = rect => (
rect.left <= (window.innerWidth || document.documentElement.clientWidth)
)
-const isElementInViewport = e => isRectVisibleInViewport(e.getBoundingClientRect())
+// Determine if the given element is visible in the viewport
+util.isElementInViewport = e => util.isRectVisibleInViewport(e.getBoundingClientRect())
-module.exports = { isRectVisibleInViewport, isElementInViewport }
+// Process Unmaps
+util.rmMaps = a => a.forEach(u => unmap(u))
+
+util.rmSearchAliases = a => Object.entries(a).forEach(([leader, items]) =>
+ items.forEach(v =>
+ removeSearchAliasX(v, leader)))
+
+// Process Mappings
+util.processMaps = (maps, siteleader) =>
+ Object.entries(maps).forEach(([domain, domainMaps]) =>
+ domainMaps.forEach(((mapObj) => {
+ const {
+ alias,
+ callback,
+ leader = (domain === "global") ? "" : siteleader,
+ category = categories.misc,
+ description = "",
+ } = mapObj
+ const opts = {}
+
+
+ const key = `${leader}${alias}`
+
+ // Determine if it's a site-specific mapping
+ if (domain !== "global") {
+ const d = domain.replace(".", "\\.")
+ opts.domain = new RegExp(`^http(s)?://(([a-zA-Z0-9-_]+\\.)*)(${d})(/.*)?`)
+ }
+
+ const fullDescription = `#${category} ${description}`
+
+ if (mapObj.map !== undefined) {
+ map(alias, mapObj.map)
+ } else {
+ mapkey(key, fullDescription, callback, opts)
+ }
+ })))
+
+// process completions
+util.processCompletions = (completions, searchleader) => Object.values(completions).forEach((s) => {
+ addSearchAliasX(s.alias, s.name, s.search, searchleader, s.compl, s.callback)
+ mapkey(`${searchleader}${s.alias}`, `#8Search ${s.name}`, () => Front.openOmnibar({ type: "SearchEngine", extra: s.alias }))
+})
+
+module.exports = util