您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
选择其他 HN 网站打开 Hacker News 链接
// ==UserScript== // @name Hacker News Apps Switcher // @name:zh-CN Hacker News 网站切换器 // @namespace https://www.pipecraft.net/ // @homepage https://github.com/dev-topics-only/hacker-news-apps-switcher#readme // @supportURL https://github.com/dev-topics-only/hacker-news-apps-switcher/issues // @version 0.0.4 // @description Open Hacker News links on the favorite apps // @description:zh-CN 选择其他 HN 网站打开 Hacker News 链接 // @icon https://icons.pipecraft.net/favicons/64/news.ycombinator.com/favicon.ico // @author Pipecraft // @license MIT // @match https://*/* // @match http://*/* // @grant GM_setValue // @grant GM_getValue // @grant GM_addValueChangeListener // ==/UserScript== // ;(() => { "use strict" var doc = document var $ = (element, selectors) => element && typeof element === "object" ? element.querySelector(selectors) : doc.querySelector(element) var $$ = (element, selectors) => element && typeof element === "object" ? [...element.querySelectorAll(selectors)] : [...doc.querySelectorAll(element)] var createElement = (tagName, attributes) => { const element = doc.createElement(tagName) if (attributes) { for (const name in attributes) { if (Object.hasOwn(attributes, name)) { const value = attributes[name] if (name === "textContent") { element[name] = value } else if (name === "style") { setStyle(element, value) } else { setAttribute(element, name, value) } } } } return element } var addEventListener = (element, type, listener, options) => { if (!element) { return () => 0 } if (typeof type === "object") { const removers = [] for (const type1 in type) { if (Object.hasOwn(type, type1)) { element.addEventListener(type1, type[type1]) removers.push(() => element.removeEventListener(type1, type[type1])) } } return () => { for (const remover of removers) remover() } } if (typeof type === "string" && typeof listener === "function") { element.addEventListener(type, listener, options) return () => { element.removeEventListener(type, listener, options) } } } var getAttribute = (element, name) => element ? element.getAttribute(name) : null var setAttribute = (element, name, value) => element ? element.setAttribute(name, value) : void 0 var setStyle = (element, values, overwrite) => { if (!element) { return } const style = element.style if (typeof values === "string") { style.cssText = overwrite ? values : style.cssText + ";" + values return } if (overwrite) { style.cssText = "" } for (const key in values) { if (Object.hasOwn(values, key)) { style[key] = values[key].replace("!important", "") } } } var toStyleMap = (styleText) => { styleText = noStyleSpace(styleText) const map = {} const keyValues = styleText.split("}") for (const keyValue of keyValues) { const kv = keyValue.split("{") if (kv[0] && kv[1]) { map[kv[0]] = kv[1] } } return map } var noStyleSpace = (text) => text.replace(/\s*([^\w-!])\s*/gm, "$1") var createSetStyle = (styleText) => { const styleMap = toStyleMap(styleText) return (element, value, overwrite) => { if (typeof value === "object") { setStyle(element, value, overwrite) } else if (typeof value === "string") { const key = noStyleSpace(value) const value2 = styleMap[key] setStyle(element, value2 || value, overwrite) } } } if (typeof Object.hasOwn !== "function") { Object.hasOwn = (instance, prop) => Object.prototype.hasOwnProperty.call(instance, prop) } var content_default = ".hnas_wrapper { display: inline-block;}.hnas_wrapper > div.hnas_tooltip { min-width: 250px; display: none; position: absolute; top: 0px; left: 0px; box-sizing: border-box; padding: 10px 15px; background-color: white; z-index: 100000; border-radius: 5px; -webkit-box-shadow: 0px 10px 39px 10px rgba(62, 66, 66, 0.22); -moz-box-shadow: 0px 10px 39px 10px rgba(62, 66, 66, 0.22); box-shadow: 0px 10px 39px 10px rgba(62, 66, 66, 0.22);}.hnas_wrapper > div.hnas_tooltip > div { display: flex; flex-direction: column;}.hnas_wrapper > div.hnas_tooltip > div > a { text-decoration: none; color: black; padding: 5px; border-radius: 5px; border: none; font-weight: normal; font-size: 1rem; line-height: 1.25rem;}.hnas_wrapper > div.hnas_tooltip > div > a:hover { text-decoration: underline; color: black !important; background-color: #f3f4f6;}" var apps = [ "https://news.ycombinator.com/item?id=1234", "https://hn.svelte.dev/item/1234", "https://hn-redesign.vercel.app/items/1234", "https://insin.github.io/react-hn/#/story/1234", "https://lotusreader.netlify.app/item/1234", "https://hackernewsmobile.com/#/comments/1234", "https://hackerweb.app/#/item/1234", "https://hn.premii.com/#/comments/1234", "https://whnex.com/items/1234", "https://hack.ernews.info/comments-for/1234", "https://hacker-news.news/post/1234", "Close", ] var setStyle2 = createSetStyle(content_default) var tooltip = null function toSiteName(url) { return /\/([^/]+)\//.exec(url)[1] } var handler = (event) => { let target = event.target const tooltip2 = $(".hnas_tooltip") if (tooltip2) { while (target !== tooltip2 && target) { target = target.parentNode } if (target === tooltip2) { event.preventDefault() return } tooltip2.style.display = "none" } document.removeEventListener("click", handler) } function displayTooltip(id, wrapper) { if (!tooltip) { tooltip = createElement("div") setStyle2(tooltip, ".hnas_wrapper > div.hnas_tooltip") setAttribute(tooltip, "class", "hnas_tooltip") const list = createElement("div") setStyle2(list, ".hnas_wrapper > div.hnas_tooltip > div") for (const app of apps) { const link = createElement("a") setStyle2(link, ".hnas_wrapper > div.hnas_tooltip > div > a") link.dataset.hnas_link = "1" if (app === "Close") { link.innerHTML = "Close" setStyle2(link, "color: #217dfc; cursor: pointer;") } else { setAttribute(link, "href", app) setAttribute(link, "target", "_blank") link.innerHTML = toSiteName(app) } addEventListener(link, { click(event) { const tooltip2 = $(".hnas_tooltip") if (tooltip2) { tooltip2.style.display = "none" } document.removeEventListener("click", handler) if (link.innerHTML === "Close") { event.preventDefault() } }, mouseover() { setStyle2( link, "text-decoration: underline; background-color: #f3f4f6; color: black !important;" ) if (app === "Close") { setStyle2(link, "color: #217dfc; cursor: pointer;") } }, mouseout() { setStyle2(link, ".hnas_wrapper > div.hnas_tooltip > div > a", true) if (app === "Close") { setStyle2(link, "color: #217dfc; cursor: pointer;") } }, }) list.append(link) } tooltip.append(list) } if (tooltip.style.display === "block" && tooltip.parentNode === wrapper) { return } for (const link of $$(tooltip, "div a")) { const href = getAttribute(link, "href") if (href) { setAttribute(link, "href", href.replace(/\d+/, id)) } } const linkElement = wrapper.previousSibling const width = linkElement.offsetWidth const height = linkElement.offsetHeight const top = linkElement.offsetTop const left = linkElement.offsetLeft wrapper.append(tooltip) setStyle2(tooltip, { display: "block", top: String(top + height) + "px", left: String(left) + "px", width: String(width) + "px", }) document.removeEventListener("click", handler) setTimeout(() => { addEventListener(document, "click", handler) }, 100) } function updateLinks() { const links = $$( 'a[href^="https://news.ycombinator.com/item?id="],a[href^="http://news.ycombinator.com/item?id="]' ) for (const link of links) { if (link.dataset.hnas_binded || link.dataset.hnas_link) { continue } link.dataset.hnas_binded = "1" const wrapper = createElement("span") setAttribute(wrapper, "class", "hnas_wrapper") link.after(wrapper) const id = /id=(\d+)/.exec(getAttribute(link, "href"))[1] if (id) { addEventListener(link, "click", (event) => { event.preventDefault() displayTooltip(id, wrapper) }) } } } function main() { if (!document.body) { return } setInterval(updateLinks, 1e3) updateLinks() } main() })()