907 lines
26 KiB
JavaScript
907 lines
26 KiB
JavaScript
const ghReservedNames = require("github-reserved-names")
|
||
|
||
const util = require("./util")
|
||
|
||
const actions = {}
|
||
|
||
// Globally applicable actions
|
||
// ===========================
|
||
|
||
// URL Manipulation/querying
|
||
// -------------------------
|
||
actions.vimEditURL = () =>
|
||
Front.showEditor(util.getCurrentLocation(), (url) => {
|
||
actions.openLink(url)()
|
||
}, "url")
|
||
|
||
actions.getURLPath = ({ count = 0, domain = false } = {}) => {
|
||
let path = util.getCurrentLocation("pathname").slice(1)
|
||
if (count) {
|
||
path = path.split("/").slice(0, count).join("/")
|
||
}
|
||
if (domain) {
|
||
path = `${util.getCurrentLocation("hostname")}/${path}`
|
||
}
|
||
return path
|
||
}
|
||
|
||
actions.copyURLPath = ({ count, domain } = {}) => () =>
|
||
Clipboard.write(actions.getURLPath({ count, domain }))
|
||
|
||
actions.copyOrgLink = () =>
|
||
Clipboard.write(`[[${util.getCurrentLocation("href")}][${document.title}]]`)
|
||
|
||
actions.copyMarkdownLink = () =>
|
||
Clipboard.write(
|
||
// I mostly use this feature to paste links into my Neuron (github.com/srid/neuron) notes.
|
||
// Due to a bug in Neuron's markdown library (github.com/jgm/commonmark-hs/issues/52),
|
||
// the vertical bar character breaks lists and titles.
|
||
// As a workaround, we backslash-escape any vertical bar characters.
|
||
`[${document.title.replace("|", "\\|")}](${util.getCurrentLocation("href")})`,
|
||
)
|
||
|
||
actions.duplicateTab = () =>
|
||
actions.openLink(util.getCurrentLocation("href"), { newTab: true, active: false })()
|
||
|
||
// Site/Page Information
|
||
// ---------------------
|
||
const ddossierUrl = "http://centralops.net/co/DomainDossier.aspx"
|
||
|
||
actions.showWhois = ({ hostname = util.getCurrentLocation("hostname") } = {}) =>
|
||
() => actions.openLink(`${ddossierUrl}?dom_whois=true&addr=${hostname}`, { newTab: true })()
|
||
|
||
actions.showDns = ({ hostname = util.getCurrentLocation("hostname"), verbose = false } = {}) => () => {
|
||
let u = ""
|
||
if (verbose) {
|
||
u = `${ddossierUrl}?dom_whois=true&dom_dns=true&traceroute=true&net_whois=true&svc_scan=true&addr=${hostname}`
|
||
} else {
|
||
u = `${ddossierUrl}?dom_dns=true&addr=${hostname}`
|
||
}
|
||
actions.openLink(u, { newTab: true })()
|
||
}
|
||
|
||
const googleCacheUrl = "https://webcache.googleusercontent.com/search?q=cache:"
|
||
|
||
actions.showGoogleCache = ({ href = util.getCurrentLocation("href") } = {}) =>
|
||
() => actions.openLink(`${googleCacheUrl}${href}`, { newTab: true })()
|
||
|
||
const waybackUrl = "https://web.archive.org/web/*/"
|
||
|
||
actions.showWayback = ({ href = util.getCurrentLocation("href") } = {}) =>
|
||
() => actions.openLink(`${waybackUrl}${href}`, { newTab: true })()
|
||
|
||
const outlineUrl = "https://outline.com/"
|
||
|
||
actions.showOutline = ({ href = util.getCurrentLocation("href") } = {}) =>
|
||
() => actions.openLink(`${outlineUrl}${href}`, { newTab: true })()
|
||
|
||
// Site/Page Actions
|
||
const rssSubscribeUrl = "https://feedrabbit.com/subscriptions/new?url="
|
||
|
||
actions.rssSubscribe = ({ href = util.getCurrentLocation("href") } = {}) =>
|
||
() => actions.openLink(`${rssSubscribeUrl}${encodeURIComponent(href)}`, { newTab: true })()
|
||
|
||
actions.showSpeedReader = () => {
|
||
const script = document.createElement("script")
|
||
script.innerHTML = `(() => {
|
||
const sq = window.sq || {}
|
||
window.sq = sq
|
||
if (sq.script) {
|
||
sq.again()
|
||
} else if (sq.context !== "inner") {
|
||
sq.bookmarkletVersion = "0.3.0"
|
||
sq.iframeQueryParams = { host: "//squirt.io" }
|
||
sq.script = document.createElement("script")
|
||
sq.script.src = \`\${sq.iframeQueryParams.host}/bookmarklet/frame.outer.js\`
|
||
document.body.appendChild(sq.script)
|
||
}
|
||
})()`
|
||
document.body.appendChild(script)
|
||
}
|
||
|
||
actions.scrollToHash = (hash = null) => {
|
||
const h = (hash || document.location.hash).replace("#", "")
|
||
const e = document.getElementById(h) || document.querySelector(`[name="${h}"]`)
|
||
if (!e) {
|
||
return
|
||
}
|
||
e.scrollIntoView({ behavior: "smooth" })
|
||
}
|
||
|
||
// Surfingkeys-specific actions
|
||
// ----------------------------
|
||
actions.createHints = (selector, action) => () => {
|
||
if (typeof action === "undefined") {
|
||
// Use manual reassignment rather than a default arg so that we can lint/bundle without access
|
||
// to the Hints object
|
||
action = Hints.dispatchMouseClick // eslint-disable-line no-param-reassign
|
||
}
|
||
Hints.create(selector, action)
|
||
}
|
||
|
||
actions.openAnchor = ({ newTab = false, active = true, prop = "href" } = {}) => (a) => actions.openLink(a[prop], { newTab, active })()
|
||
|
||
actions.openLink = (url, { newTab = false, active = true } = {}) => () => {
|
||
if (newTab) {
|
||
RUNTIME("openLink", { tab: { tabbed: true, active }, url })
|
||
return
|
||
}
|
||
window.location.assign(url)
|
||
}
|
||
|
||
actions.editSettings = () => tabOpenLink(chrome.extension.getURL("/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.")
|
||
})
|
||
}
|
||
})
|
||
|
||
actions.previewLink = actions.createHints("a[href]", (a) =>
|
||
Front.showEditor(a.href, (url) => actions.openLink(url)(), "url"))
|
||
|
||
// FakeSpot
|
||
// --------
|
||
actions.fakeSpot = (url = util.getCurrentLocation("href")) =>
|
||
actions.openLink(`https://fakespot.com/analyze?ra=true&url=${url}`, { newTab: true, active: false })()
|
||
|
||
// Site-specific actions
|
||
// =====================
|
||
|
||
// Amazon
|
||
// -----
|
||
actions.az = {}
|
||
actions.az.viewProduct = () => {
|
||
const reHost = /^([-\w]+[.])*amazon.\w+$/
|
||
const rePath = /^(?:.*\/)*(?:dp|gp\/product)(?:\/(\w{10})).*/
|
||
const elements = {}
|
||
document.querySelectorAll("a[href]").forEach((a) => {
|
||
const u = new URL(a.href)
|
||
if (u.hash.length === 0 && reHost.test(u.hostname)) {
|
||
const rePathRes = rePath.exec(u.pathname)
|
||
if (rePathRes === null || rePathRes.length !== 2) {
|
||
return
|
||
}
|
||
if (!util.isElementInViewport(a)) {
|
||
return
|
||
}
|
||
|
||
const asin = rePathRes[1]
|
||
|
||
if (elements[asin] !== undefined) {
|
||
if (!(elements[asin].text.trim().length === 0 && a.text.trim().length > 0)) {
|
||
return
|
||
}
|
||
}
|
||
|
||
elements[asin] = a
|
||
}
|
||
})
|
||
Hints.create(Object.values(elements), Hints.dispatchMouseClick)
|
||
}
|
||
|
||
// Godoc
|
||
// -----
|
||
actions.viewGodoc = () => actions.openLink(`https://godoc.org/${actions.getURLPath({ count: 2, domain: true })}`, { newTab: true })()
|
||
|
||
// Google
|
||
actions.go = {}
|
||
actions.go.parseLocation = () => {
|
||
const u = new URL(util.getCurrentLocation())
|
||
const q = u.searchParams.get("q")
|
||
const p = u.pathname.split("/")
|
||
|
||
const res = {
|
||
type: "unknown",
|
||
url: u,
|
||
query: q,
|
||
}
|
||
|
||
if (u.hostname === "www.google.com") { // TODO: handle other ccTLDs
|
||
if (p.length <= 1) {
|
||
res.type = "home"
|
||
} else if (p[1] === "search") {
|
||
switch (u.searchParams.get("tbm")) {
|
||
case "vid":
|
||
res.type = "videos"
|
||
break
|
||
case "isch":
|
||
res.type = "images"
|
||
break
|
||
case "nws":
|
||
res.type = "news"
|
||
break
|
||
default:
|
||
res.type = "web"
|
||
}
|
||
} else if (p[1] === "maps") {
|
||
res.type = "maps"
|
||
if (p[2] === "search" && p[3] !== undefined) {
|
||
res.query = p[3] // eslint-disable-line prefer-destructuring
|
||
} else if (p[2] !== undefined) {
|
||
res.query = p[2] // eslint-disable-line prefer-destructuring
|
||
}
|
||
}
|
||
}
|
||
|
||
return res
|
||
}
|
||
|
||
actions.go.ddg = () => {
|
||
const g = actions.go.parseLocation()
|
||
|
||
const ddg = new URL("https://duckduckgo.com")
|
||
if (g.query) {
|
||
ddg.searchParams.set("q", g.query)
|
||
}
|
||
|
||
switch (g.type) {
|
||
case "videos":
|
||
ddg.searchParams.set("ia", "videos")
|
||
ddg.searchParams.set("iax", "videos")
|
||
break
|
||
case "images":
|
||
ddg.searchParams.set("ia", "images")
|
||
ddg.searchParams.set("iax", "images")
|
||
break
|
||
case "news":
|
||
ddg.searchParams.set("ia", "news")
|
||
ddg.searchParams.set("iar", "news")
|
||
break
|
||
case "maps":
|
||
ddg.searchParams.set("iaxm", "maps")
|
||
break
|
||
case "search":
|
||
case "home":
|
||
case "unknown":
|
||
default:
|
||
ddg.searchParams.set("ia", "web")
|
||
break
|
||
}
|
||
|
||
actions.openLink(ddg.href)()
|
||
}
|
||
|
||
// DuckDuckGo
|
||
actions.dg = {}
|
||
actions.dg.goog = () => {
|
||
let u
|
||
try {
|
||
u = new URL(util.getCurrentLocation())
|
||
} catch (e) {
|
||
return
|
||
}
|
||
const q = u.searchParams.get("q")
|
||
if (!q) {
|
||
return
|
||
}
|
||
|
||
const goog = new URL("https://google.com/search")
|
||
goog.searchParams.set("q", q)
|
||
|
||
const iax = u.searchParams.get("iax")
|
||
const iaxm = u.searchParams.get("iaxm")
|
||
const iar = u.searchParams.get("iar")
|
||
|
||
if (iax === "images") {
|
||
goog.searchParams.set("tbm", "isch")
|
||
} else if (iax === "videos") {
|
||
goog.searchParams.set("tbm", "vid")
|
||
} else if (iar === "news") {
|
||
goog.searchParams.set("tbm", "nws")
|
||
} else if (iaxm === "maps") {
|
||
goog.pathname = "/maps"
|
||
}
|
||
|
||
actions.openLink(goog.href)()
|
||
}
|
||
|
||
// GitHub
|
||
// ------
|
||
// TODO: This is a mess
|
||
actions.gh = {}
|
||
actions.gh.star = ({ toggle = false } = {}) => async () => {
|
||
const hasDisplayNoneParent = (e) =>
|
||
window.getComputedStyle(e).display === "none"
|
||
|| (e.parentElement ? hasDisplayNoneParent(e.parentElement) : false)
|
||
|
||
const starContainers = Array.from(document.querySelectorAll("div.starring-container"))
|
||
.filter((e) => !hasDisplayNoneParent(e))
|
||
|
||
if (starContainers.length === 0) return
|
||
|
||
let container
|
||
try {
|
||
container = starContainers.length > 1
|
||
? await util.createHintsAsync(starContainers, (c) => c)
|
||
: starContainers[0]
|
||
} catch (e) {
|
||
return
|
||
}
|
||
|
||
const repoUrl = container.parentElement.parentElement.matches("ul.pagehead-actions")
|
||
? util.getCurrentLocation("pathname")
|
||
: new URL(container.parentElement.querySelector("form").action).pathname
|
||
|
||
const status = container.classList.contains("on")
|
||
const repo = repoUrl.slice(1).split("/").slice(0, 2).join("/")
|
||
|
||
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, button.starred").click()
|
||
} else {
|
||
container.querySelector(".unstarred>button, button.unstarred").click()
|
||
}
|
||
}
|
||
|
||
Front.showBanner(`${star} Repository ${repo} ${verb} ${statusMsg}!`)
|
||
}
|
||
|
||
actions.gh.parseRepo = (url = util.getCurrentLocation(), rootOnly = false) => {
|
||
const u = url instanceof URL ? url : new URL(url)
|
||
const [user, repo, ...rest] = u.pathname.split("/").filter((s) => s !== "")
|
||
const isRoot = rest.length === 0
|
||
const cond = (
|
||
u.origin === util.getCurrentLocation("origin")
|
||
&& typeof user === "string"
|
||
&& user.length > 0
|
||
&& typeof repo === "string"
|
||
&& repo.length > 0
|
||
&& (isRoot || rootOnly === false)
|
||
&& /^([a-zA-Z0-9]+-?)+$/.test(user)
|
||
&& !ghReservedNames.check(user)
|
||
)
|
||
return cond
|
||
? {
|
||
type: "repo",
|
||
user,
|
||
repo,
|
||
owner: user,
|
||
name: repo,
|
||
href: url,
|
||
url: u,
|
||
repoBase: `/${user}/${repo}`,
|
||
repoRoot: isRoot,
|
||
repoPath: rest,
|
||
}
|
||
: null
|
||
}
|
||
|
||
actions.gh.parseUser = (url = util.getCurrentLocation(), rootOnly = false) => {
|
||
const u = url instanceof URL ? url : new URL(url)
|
||
const [user, ...rest] = u.pathname.split("/").filter((s) => s !== "")
|
||
const isRoot = rest.length === 0
|
||
const cond = (
|
||
u.origin === util.getCurrentLocation("origin")
|
||
&& typeof user === "string"
|
||
&& user.length > 0
|
||
&& (rootOnly === false || rest.length === 0)
|
||
&& /^([a-zA-Z0-9]+-?)+$/.test(user)
|
||
&& !ghReservedNames.check(user)
|
||
)
|
||
return cond
|
||
? {
|
||
type: "user",
|
||
name: user,
|
||
user,
|
||
href: url,
|
||
url: u,
|
||
userRoot: isRoot,
|
||
userPath: rest,
|
||
}
|
||
: null
|
||
}
|
||
|
||
actions.gh.parseFile = (url = util.getCurrentLocation()) => {
|
||
const u = url instanceof URL ? url : new URL(url)
|
||
const [user, repo, pathType, commitHash, ...rest] = u.pathname.split("/").filter((s) => s !== "")
|
||
const cond = (
|
||
u.origin === util.getCurrentLocation("origin")
|
||
&& typeof user === "string"
|
||
&& user.length > 0
|
||
&& typeof repo === "string"
|
||
&& repo.length > 0
|
||
&& typeof pathType === "string"
|
||
&& (pathType === "blob" || pathType === "tree")
|
||
&& typeof commitHash === "string"
|
||
&& commitHash.length > 0
|
||
&& /^([a-zA-Z0-9]+-?)+$/.test(user)
|
||
&& !ghReservedNames.check(user)
|
||
)
|
||
return cond
|
||
? {
|
||
type: "file",
|
||
user,
|
||
repo,
|
||
pathType,
|
||
commitHash,
|
||
href: url,
|
||
url: u,
|
||
filePath: rest,
|
||
repoBase: `/${user}/${repo}`,
|
||
}
|
||
: null
|
||
}
|
||
|
||
actions.gh.parseCommit = (url = util.getCurrentLocation()) => {
|
||
const u = url instanceof URL ? url : new URL(url)
|
||
const [user, repo, commit, commitHash] = u.pathname.split("/").filter((s) => s !== "")
|
||
const cond = (
|
||
u.origin === util.getCurrentLocation("origin")
|
||
&& typeof user === "string"
|
||
&& user.length > 0
|
||
&& typeof repo === "string"
|
||
&& repo.length > 0
|
||
&& typeof commit === "string"
|
||
&& commit === "commit"
|
||
&& typeof commitHash === "string"
|
||
&& commitHash.length > 0
|
||
&& /^([a-zA-Z0-9]+-?)+$/.test(user)
|
||
&& !ghReservedNames.check(user)
|
||
)
|
||
return cond
|
||
? {
|
||
type: "commit",
|
||
user,
|
||
repo,
|
||
commitHash,
|
||
href: url,
|
||
url: u,
|
||
}
|
||
: null
|
||
}
|
||
|
||
actions.gh.parseIssue = (url = util.getCurrentLocation()) => {
|
||
const u = url instanceof URL ? url : new URL(url)
|
||
const [user, repo, maybeIssues, ...rest] = u.pathname.split("/").filter((s) => s !== "")
|
||
const isRoot = rest.length === 0
|
||
const cond = (
|
||
u.origin === util.getCurrentLocation("origin")
|
||
&& typeof user === "string"
|
||
&& user.length > 0
|
||
&& typeof repo === "string"
|
||
&& repo.length > 0
|
||
&& maybeIssues === "issues"
|
||
&& /^([a-zA-Z0-9]+-?)+$/.test(user)
|
||
&& !ghReservedNames.check(user)
|
||
)
|
||
return cond
|
||
? {
|
||
href: url,
|
||
url: u,
|
||
...(isRoot ? {
|
||
type: "issues",
|
||
issuePath: rest,
|
||
} : {
|
||
type: "issue",
|
||
number: rest[0],
|
||
issuePath: rest,
|
||
}),
|
||
}
|
||
: null
|
||
}
|
||
|
||
actions.gh.parsePull = (url = util.getCurrentLocation()) => {
|
||
const u = url instanceof URL ? url : new URL(url)
|
||
const [user, repo, maybePulls, ...rest] = u.pathname.split("/").filter((s) => s !== "")
|
||
const isRoot = rest.length === 0
|
||
const cond = (
|
||
u.origin === util.getCurrentLocation("origin")
|
||
&& typeof user === "string"
|
||
&& user.length > 0
|
||
&& typeof repo === "string"
|
||
&& repo.length > 0
|
||
&& /^pulls?$/.test(maybePulls)
|
||
&& /^([a-zA-Z0-9]+-?)+$/.test(user)
|
||
&& !ghReservedNames.check(user)
|
||
)
|
||
return cond
|
||
? {
|
||
href: url,
|
||
url: u,
|
||
...(isRoot ? {
|
||
type: "pulls",
|
||
pullPath: rest,
|
||
} : {
|
||
type: "pull",
|
||
number: rest[0],
|
||
pullPath: rest,
|
||
}),
|
||
}
|
||
: null
|
||
}
|
||
|
||
actions.gh.isUser = (url = util.getCurrentLocation(), rootOnly = true) =>
|
||
actions.gh.parseUser(url, rootOnly) !== null
|
||
|
||
actions.gh.isRepo = (url = util.getCurrentLocation(), rootOnly = true) =>
|
||
actions.gh.parseRepo(url, rootOnly) !== null
|
||
|
||
actions.gh.isFile = (url = util.getCurrentLocation()) => actions.gh.parseFile(url) !== null
|
||
actions.gh.isCommit = (url = util.getCurrentLocation()) => actions.gh.parseCommit(url) !== null
|
||
actions.gh.isIssue = (url = util.getCurrentLocation()) => actions.gh.parseIssue(url) !== null
|
||
actions.gh.isPull = (url = util.getCurrentLocation()) => actions.gh.parsePull(url) !== null
|
||
|
||
actions.gh.openRepo = () => util.createHintsFiltered((a) => actions.gh.isRepo(a.href))
|
||
actions.gh.openUser = () => util.createHintsFiltered((a) => actions.gh.isUser(a.href))
|
||
actions.gh.openFile = () => util.createHintsFiltered((a) => actions.gh.isFile(a.href))
|
||
actions.gh.openCommit = () => util.createHintsFiltered((a) => actions.gh.isCommit(a.href))
|
||
actions.gh.openIssue = () => util.createHintsFiltered((a) => actions.gh.isIssue(a.href))
|
||
actions.gh.openPull = () => util.createHintsFiltered((a) => actions.gh.isPull(a.href))
|
||
|
||
actions.gh.openRepoPage = (repoPath) => () => {
|
||
const repo = actions.gh.parseRepo()
|
||
if (repo === null) return
|
||
actions.openLink(`${repo.repoBase}${repoPath}`)()
|
||
}
|
||
|
||
actions.gh.openRepoOwner = () => {
|
||
const repo = actions.gh.parseRepo()
|
||
if (repo === null) return
|
||
actions.openLink(`/${repo.owner}`)()
|
||
}
|
||
|
||
actions.gh.openProfile = () =>
|
||
actions.openLink(`/${document.querySelector("meta[name='user-login']").content}`)()
|
||
|
||
actions.gh.toggleLangStats = () =>
|
||
document.querySelector(".repository-lang-stats-graph").click()
|
||
|
||
actions.gh.goParent = () => {
|
||
const segments = util.getCurrentLocation("pathname")
|
||
.split("/").filter((s) => s !== "")
|
||
const newPath = (() => {
|
||
const [user, repo, pathType] = segments
|
||
switch (segments.length) {
|
||
case 0:
|
||
return false
|
||
case 4:
|
||
switch (pathType) {
|
||
case "blob":
|
||
case "tree":
|
||
return [user, repo]
|
||
case "pull":
|
||
return [user, repo, "pulls"]
|
||
default:
|
||
break
|
||
}
|
||
break
|
||
case 5:
|
||
if (pathType === "blob") {
|
||
return [user, repo]
|
||
}
|
||
break
|
||
default:
|
||
break
|
||
}
|
||
return segments.slice(0, segments.length - 1)
|
||
})()
|
||
if (newPath !== false) {
|
||
const u = `${util.getCurrentLocation("origin")}/${newPath.join("/")}`
|
||
actions.openLink(u)()
|
||
}
|
||
}
|
||
|
||
actions.gh.viewSourceGraph = () => {
|
||
const url = new URL("https://sourcegraph.com/github.com")
|
||
let page = null
|
||
// The following conditional expressions are indeed intended to be
|
||
// assignments, this is not a bug.
|
||
if ((page = actions.gh.parseFile(window.location.href)) !== null) {
|
||
const filePath = page.filePath.join("/")
|
||
url.pathname += `/${page.user}/${page.repo}@${page.commitHash}/-/${page.pathType}/${filePath}`
|
||
if (window.location.hash !== "") {
|
||
url.hash = window.location.hash
|
||
} else if (!util.isElementInViewport(document.querySelector("#L1"))) {
|
||
for (const e of document.querySelectorAll(".js-line-number")) {
|
||
if (util.isElementInViewport(e)) {
|
||
url.hash = e.id
|
||
break
|
||
}
|
||
}
|
||
}
|
||
} else if ((page = actions.gh.parseCommit(window.location.href)) !== null) {
|
||
url.pathname += `/${page.user}/${page.repo}@${page.commitHash}`
|
||
} else if ((page = actions.gh.parseRepo(window.location.href)) !== null) {
|
||
url.pathname += `/${page.user}/${page.repo}`
|
||
} else {
|
||
url.pathname = ""
|
||
}
|
||
|
||
actions.openLink(url.href, { newTab: true })()
|
||
}
|
||
|
||
actions.gh.viewRaw = () => {
|
||
const file = actions.gh.parseFile()
|
||
if (file === null) return
|
||
actions.openLink(`https://raw.githack.com/${file.user}/${file.repo}/${file.filePath.join("/")}`, { newTab: true })()
|
||
}
|
||
|
||
actions.gh.openRepoFromClipboard = async ({ newTab = true } = {}) =>
|
||
actions.openLink(`https://github.com/${await navigator.clipboard.readText()}`, { newTab })()
|
||
|
||
actions.gh.openFileFromClipboard = async ({ newTab = true } = {}) => {
|
||
const clip = await navigator.clipboard.readText()
|
||
if (typeof clip !== "string" || clip.length === 0) {
|
||
return
|
||
}
|
||
|
||
const loc = util.getCurrentLocation()
|
||
const dest = {
|
||
user: null,
|
||
repo: null,
|
||
commitHash: "master",
|
||
}
|
||
|
||
const file = actions.gh.parseFile(loc)
|
||
if (file !== null) {
|
||
dest.user = file.user
|
||
dest.repo = file.repo
|
||
dest.commitHash = file.commitHash
|
||
} else {
|
||
const commit = actions.gh.parseCommit(loc)
|
||
if (commit !== null) {
|
||
dest.user = commit.user
|
||
dest.repo = commit.repo
|
||
dest.commitHash = commit.commitHash
|
||
} else {
|
||
const repository = actions.gh.parseRepo(loc)
|
||
if (repository !== null) {
|
||
return
|
||
}
|
||
dest.user = repository.user
|
||
dest.repo = repository.repo
|
||
}
|
||
}
|
||
|
||
actions.openLink(
|
||
`https://github.com/${dest.user}/${dest.repo}/tree/${dest.commitHash}/${clip}`,
|
||
{ newTab },
|
||
)()
|
||
}
|
||
|
||
// GitLab
|
||
// ------
|
||
actions.gl = {}
|
||
actions.gl.star = () => {
|
||
const repo = util.getCurrentLocation("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}`)
|
||
}
|
||
|
||
// Twitter
|
||
// ------
|
||
actions.tw = {}
|
||
actions.tw.openUser = () =>
|
||
actions.createHints([].concat(
|
||
[...document.querySelectorAll("a[role='link'] img[src^='https://pbs.twimg.com/profile_images']")]
|
||
.map((e) => e.closest("a")),
|
||
[...document.querySelectorAll("a[role='link']")]
|
||
.filter((e) => e.text.match(/^@/)),
|
||
))()
|
||
|
||
// 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"
|
||
}
|
||
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
|
||
}
|
||
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(util.getCurrentLocation())
|
||
} catch (e) {
|
||
return
|
||
}
|
||
let page = u.searchParams.get("p")
|
||
if (page === null || page === "") {
|
||
page = "1"
|
||
}
|
||
const cur = parseInt(page, 10)
|
||
if (Number.isNaN(cur)) {
|
||
return
|
||
}
|
||
const dest = cur + dist
|
||
if (dest < 1) {
|
||
return
|
||
}
|
||
u.searchParams.set("p", dest)
|
||
actions.openLink(u.href)()
|
||
}
|
||
|
||
actions.hn.openLinkAndComments = (e) => {
|
||
const linkUrl = e.querySelector("a.storylink").href
|
||
const commentsUrl = e.nextElementSibling.querySelector("td > a[href*='item']:not(.storylink)").href
|
||
actions.openLink(commentsUrl, { newTab: true })()
|
||
actions.openLink(linkUrl, { newTab: true })()
|
||
}
|
||
|
||
// ProductHunt
|
||
// -----------
|
||
actions.ph = {}
|
||
actions.ph.openExternal = () => {
|
||
Hints.create("ul[class^='postsList_'] > li > div[class^='item_']", (p) => actions.openLink(
|
||
p.querySelector("div[class^='meta_'] > div[class^='actions_'] > div[class^='minorActions_'] > a:nth-child(1)").href,
|
||
{ newTab: true },
|
||
)())
|
||
}
|
||
|
||
// Dribbble
|
||
// --------
|
||
actions.dr = {}
|
||
actions.dr.attachment = (cb = (a) => actions.openLink(a, { newTab: true })()) => actions.createHints(".attachments .thumb", (a) => cb(a.src.replace("/thumbnail/", "/")))
|
||
|
||
// Wikipedia
|
||
// ---------
|
||
actions.wp = {}
|
||
actions.wp.toggleSimple = () => {
|
||
const u = new URL(util.getCurrentLocation("href"))
|
||
u.hostname = u.hostname.split(".")
|
||
.map((s, i) => {
|
||
if (i === 0) {
|
||
return s === "simple" ? "" : "simple"
|
||
}
|
||
return s
|
||
}).filter((s) => s !== "").join(".")
|
||
actions.openLink(u.href)()
|
||
}
|
||
|
||
actions.wp.viewWikiRank = () => {
|
||
const h = document.location.hostname.split(".")
|
||
const lang = h.length > 2 && h[0] !== "www" ? h[0] : "en"
|
||
const p = document.location.pathname.split("/")
|
||
if (p.length < 3 || p[1] !== "wiki") {
|
||
return
|
||
}
|
||
const article = p.slice(2).join("/")
|
||
actions.openLink(`https://wikirank.net/${lang}/${article}`, { newTab: true })()
|
||
}
|
||
|
||
// Nest Thermostat Controller
|
||
// --------------------------
|
||
actions.nt = {}
|
||
actions.nt.adjustTemp = (dir) => () =>
|
||
document.querySelector(
|
||
`button[data-test='thermozilla-controller-controls-${dir > 0 ? "in" : "de"}crement-button']`,
|
||
).click()
|
||
|
||
actions.nt.setMode = (mode) => async () => {
|
||
const selectMode = async (popover) => {
|
||
const query = () => !popover.isConnected
|
||
const q = query()
|
||
if (q) return q
|
||
popover.querySelector(`button[data-test='thermozilla-mode-switcher-${mode}-button']`).click()
|
||
return util.until(query)
|
||
}
|
||
|
||
const openPopover = async () => {
|
||
const query = () => document.querySelector("div[data-test='thermozilla-mode-popover']")
|
||
const q = query()
|
||
if (q) return q
|
||
document.querySelector("button[data-test='thermozilla-mode-button']").click()
|
||
return util.until(query)
|
||
}
|
||
|
||
const popover = await openPopover()
|
||
return selectMode(popover)
|
||
}
|
||
|
||
actions.nt.setFan = (desiredState) => async () => {
|
||
const startStopFan = async (startStop, popover) => {
|
||
const query = () => !popover.isConnected
|
||
const q = query()
|
||
if (q) return q
|
||
popover.querySelector(`div[data-test='thermozilla-fan-timer-${startStop}-button']`).click()
|
||
return util.until(query)
|
||
}
|
||
|
||
const selectFanTime = async (popover, listbox) => {
|
||
const query = () => !listbox.isConnected
|
||
const q = query()
|
||
if (q) return q
|
||
Hints.dispatchMouseClick(listbox.querySelector("div[role='option']:last-child"))
|
||
return util.until(query)
|
||
}
|
||
|
||
const openFanListbox = async (popover) => {
|
||
const query = () => popover.querySelector("div[role='listbox']")
|
||
const q = query()
|
||
if (q) return q
|
||
Hints.dispatchMouseClick(popover.querySelector("div[role='combobox']"))
|
||
return util.until(query)
|
||
}
|
||
|
||
const openPopover = async () => {
|
||
const query = () => document.querySelector("div[data-test='thermozilla-fan-timer-popover']")
|
||
const q = query()
|
||
if (q) return q
|
||
document.querySelector("button[data-test='thermozilla-fan-button']").click()
|
||
return util.until(query)
|
||
}
|
||
|
||
const fanRunning = () => document.querySelector("div[data-test='thermozilla-aag-fan-listcell-title']")
|
||
|
||
const startFan = async () => {
|
||
const popover = await openPopover()
|
||
const listbox = await openFanListbox(popover)
|
||
await selectFanTime(popover, listbox)
|
||
return startStopFan("start", popover)
|
||
}
|
||
|
||
const stopFan = async () => {
|
||
const popover = await openPopover()
|
||
await startStopFan("stop", popover)
|
||
await util.until(() => !fanRunning())
|
||
}
|
||
|
||
if (fanRunning()) {
|
||
await stopFan()
|
||
}
|
||
|
||
if (desiredState === 1) {
|
||
await startFan()
|
||
}
|
||
}
|
||
|
||
module.exports = actions
|