Hacker News Apps Switcher

Open Hacker News links on the favorite apps

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==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()
})()