From f1535b2668d35ed428a4c94603631c98a8e05950 Mon Sep 17 00:00:00 2001 From: Maddison Hellstrom Date: Fri, 2 Nov 2018 17:21:44 -0700 Subject: [PATCH] refactor: add lots of stuff sorry I changed too much and don't wanna go back and split it all into separate commits..... --- actions.js | 187 +++++++++++++++++++++ completions.js | 57 ++----- conf.js | 348 ++------------------------------------ gulpfile.js | 2 +- help.js | 19 +++ keys.js | 442 +++++++++++++++++++++++++++++++++++++++++++++++++ util.js | 86 +++++++++- 7 files changed, 756 insertions(+), 385 deletions(-) create mode 100644 actions.js create mode 100644 help.js create mode 100644 keys.js 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(` -
${t}
-
${u}
- `, { url: u }) -} - -// ****** Helper Functions ****** // -function googleCxCallback(response) { +const googleCxCallback = (response) => { const res = JSON.parse(response.text).items return res.map(s => 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.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