// ==UserScript==
// @name Hover Alternative Front-Ends
// @namespace HAFE
// @description Pops up a floating div when you hover over a link, containing alternative front-ends!
// @homepageURL https://greasyfork.org/en/scripts/437920-hover-alternative-front-ends
// @match *://*/*
// @grant none
// @run-at document-idle
// @version 1.2.3
// ==/UserScript==
// This script is a fork of 'Hover Preview':
// https://greasyfork.org/en/scripts/8042-hover-preview
const focusReactionTime = 300;
const unfocusReactionTime = 1500;
const domains = {
youtube: ["youtube.com","youtu.be","youtube-nocookie.com"],
twitter: ["twitter.com","twimg.com"],
reddit: ["reddit.com","i.redd.it"],
instagram: ["instagram.com"],
imgur: ["imgur.com"],
medium: ["medium.com"],
wikipedia: ["en.wikipedia.org"]
};
const youtubeFrontends = [{
name: "Invidious(Snopyta)",
address: "invidious.snopyta.org"
},{
name: "Invidious(Yewtube)",
address: "yewtu.be"
},{
name: "Invidious(Puffyan)",
address: "vid.puffyan.us"
},{
name: "Invidious(Seth)",
address: "invidious.sethforprivacy.com"
},{
name: "CloudTube",
address: "tube.cadence.moe"
},{
name: "Piped",
address: "piped.kavin.rocks",
}];
const twitterFrontends = [{
name: "Nitter",
address: "nitter.net"
},{
name: "Nitter(Snopyta)",
address: "nitter.snopyta.org"
},{
name: "Nitter(Puss)",
address: "nitter.pussthecat.org"
},{
name: "Nitter(42l)",
address: "nitter.42l.fr"
},{
name: "Nitter(Seth)",
address: "nitter.sethforprivacy.com"
},{
name: "Nitter(Action Sack)",
address: "nitter.actionsack.com"
}];
const redditFrontends = [{
name: "Teddit",
address: "teddit.net"
},{
name: "Teddit(Puss)",
address: "teddit.pussthecat.org"
},{
name: "Teddit(Seth)",
address: "teddit.sethforprivacy.com"
},{
name: "Libreddit",
address: "libredd.it"
},{
name: "Libreddit(Spike)",
address: "libreddit.spike.codes"
},{
name: "Libreddit(Puss)",
address: "libreddit.pussthecat.org"
}];
const instagramFrontends = [{
name: "Bibliogram",
address: "bibliogram.art"
},{
name: "Bibliogram(Snopyta)",
address: "bibliogram.snopyta.org"
},{
name: "Bibliogram(Puss)",
address: "bibliogram.pussthecat.org"
},{
name: "Bibliogram(Action Sack)",
address: "bib.actionsack.com"
},{
name: "Bibliogram(Hamster)",
address: "bibliogram.hamster.dance"
}];
const imgurFrontends = [{
name: "Imgin",
address: "imgin.voidnet.tech"
},{
name: "Ringu(Action Sack)",
address: "i.actionsack.com"
},{
name: "Kageurufu",
address: "imgur.kageurufu.net"
}];
const mediumFrontends = [{
name: "Scribe",
address: "scribe.rip"
},{
name: "Scribe(NixNet)",
address: "scribe.nixnet.services"
}];
const wikipediaFrontends = [{
name: "Wikiless",
address: "wikiless.org"
},{
name: "Wikiless(Seth)",
address: "wikiless.sethforprivacy.com"
},{
name: "Infogalactic",
address: "infogalactic.com"
}];
var focus = undefined;
var lastFocus = undefined;
var timer = null;
var hafePopup;
var hafeFrame;
var isOverPopup = false;
function checkFocus() {
if (focus) {
// if (focus == lastFocus) {
// User has definitely been here a while
showHAFEWindow(focus);
// } else {
// }
// lastFocus = focus;
}
}
function aMouseOver(evt) {
if (evt.currentTarget.tagName !== "A") {
alert(decodeURIComponent("not link"));
return;
}
if (!focus) {
focus = evt.currentTarget;
// setTimeout('checkFocus();',focusReactionTime);
// Hack to bring the popup back immediately if we've gone back to the same link.
if (hafeFrame && focus.href && hafeFrame.href == focus.href) {
showHAFEWindow(focus,evt);
} else {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(checkFocus,focusReactionTime);
}
} else {
window.status = "Already focused on a link wtf!";
}
}
function aMouseOut(evt) {
if (evt.currentTarget.tagName !== "A") {
return;
}
focus = undefined;
if (timer) {
clearTimeout(timer);
}
// TESTING: Don't hide the popup if mouse is currently over the popup!
timer = setTimeout(clearPopup,unfocusReactionTime);
}
function clearPopup(e) {
if (isOverPopup || focus)
return;
if (hafePopup) {
// hafePopup.parentNode.removeChild(hafePopup);
// hafePopup = undefined; // eww cache it!
hafePopup.style.display = 'none';
}
}
// DONE: If the user clicks a link, this isn't really a hover, so we should not
// activate and just let the user's click be processed!
function aClick(evt) {
focus = undefined;
}
function createPopup() {
// Create frame
hafePopup = document.createElement('DIV');
/** Seems style does not work for Konqueror this way. **/
hafePopup.innerHTML =
"<STYLE type='text/css'> .hafediv { background-color: #21242C; margin: 0px; padding: 2px; border: 1px solid dodgerblue; border-radius: 4px; text-align: center; box-sizing: border-box; } .hafediv a { font-family: Helvetica; font-size: 14px; color: white; text-decoration: none; padding: 0 5px; box-shadow: inset 0 0 0 0 #21242C; transition: all 0.4s ease-in-out 0s; border-radius: 3px; box-sizing: border-box; } .hafediv a:hover { box-shadow: inset 0 300px 0 0 dodgerblue; color: white; } </STYLE>"
+
"<DIV class='hafediv' width='" + (window.innerWidth * 0.75) + "' height='" + (window.innerHeight*0.75) + "' src='about:blank'></DIV>";
hafePopup.addEventListener("mouseover", function(evt) { isOverPopup=true; }, false);
hafePopup.addEventListener("mouseout", function(evt) { isOverPopup=false; setTimeout(clearPopup,unfocusReactionTime); }, false);
document.documentElement.appendChild(hafePopup);
hafePopup.style.position = "absolute";
hafePopup.style.zIndex = "10000";
hafeFrame = hafePopup.getElementsByTagName('DIV')[0];
}
function insertLink(linkAddress, linkText) {
var newLink = document.createElement('a');
newLink.href = linkAddress
newLink.textContent = linkText
hafeFrame.append(newLink);
hafeFrame.append(document.createTextNode(" "));
}
function insertLinks(link) {
hafeFrame.innerHTML = "";
var linkHost = link.href.match(/^https?\:\/\/([^\/?#]+)(?:[\/?#]|$)/i);
var linkPath = link.href.replace(linkHost[0], "");
for (var domain in domains) {
for (var target=0; target < domains[domain].length; target++) {
if (linkHost[0].includes(domains[domain][target])) {
switch (domain) {
case "youtube":
if (linkHost[1].includes("studio")) {
return false;
} else {
for (var index=0; index < youtubeFrontends.length; index++) {
insertLink(link.href.replace(linkHost[1], youtubeFrontends[index].address), youtubeFrontends[index].name);
}
}
break;
case "twitter":
if (linkHost[1].includes("pbs") || linkHost[1].includes("video")) {
for (var index=0; index < twitterFrontends.length; index++) {
insertLink(linkHost[0].replace(linkHost[1], twitterFrontends[index].address) + "pic/" + encodeURIComponent(link.href), twitterFrontends[index].name);
}
} else {
for (var index=0; index < twitterFrontends.length; index++) {
insertLink(link.href.replace(linkHost[1], twitterFrontends[index].address), twitterFrontends[index].name);
}
}
break;
case "reddit":
if (domains[domain][target] === "i.redd.it") {
for (var index=0; index < redditFrontends.length; index++) {
if (redditFrontends[index].name.includes("Libreddit")) {
insertLink(linkHost[0].replace(linkHost[1], redditFrontends[index].address) + "img/" + linkPath, redditFrontends[index].name);
}
}
} else {
for (var index=0; index < redditFrontends.length; index++) {
insertLink(link.href.replace(linkHost[1], redditFrontends[index].address), redditFrontends[index].name);
}
}
break;
case "instagram":
const instagramPaths = /\b(?:tv|reels?|u|p)\b/i;
const instagramIgnore = /\b(?:stories|accounts|explore|topics)\b/i;
var linkFolders = linkPath.split(/(?:\?|\/)+/);
if (linkHost[1].includes("about") || linkHost[1].includes("help") || linkFolders[0].match(instagramIgnore)) {
return false;
} else if (linkFolders.length > 0 && linkFolders[0].match(instagramPaths)) {
for (var index=0; index < instagramFrontends.length; index++) {
insertLink(link.href.replace(linkHost[1], instagramFrontends[index].address), instagramFrontends[index].name);
}
} else if (linkFolders.length > 1 && linkFolders[1].match(instagramPaths)) {
for (var index=0; index < instagramFrontends.length; index++) {
insertLink(linkHost[0].replace(linkHost[1], instagramFrontends[index].address) + linkPath.replace(linkFolders[0] + "/", ""), instagramFrontends[index].name);
}
} else {
for (var index=0; index < instagramFrontends.length; index++) {
insertLink(link.href.replace(linkHost[1], instagramFrontends[index].address + "/u"), instagramFrontends[index].name);
}
}
break;
case "imgur":
for (var index=0; index < imgurFrontends.length; index++) {
insertLink(link.href.replace(linkHost[1], imgurFrontends[index].address), imgurFrontends[index].name);
}
break;
case "medium":
for (var index=0; index < mediumFrontends.length; index++) {
insertLink(link.href.replace(linkHost[1], mediumFrontends[index].address), mediumFrontends[index].name);
}
break;
case "wikipedia":
for (var index=0; index < wikipediaFrontends.length; index++) {
if (wikipediaFrontends[index].name.includes("Infogalactic")) {
insertLink(linkHost[0].replace(linkHost[1], wikipediaFrontends[index].address) + linkPath.replace("wiki", "info"), wikipediaFrontends[index].name);
} else {
insertLink(link.href.replace(linkHost[1], wikipediaFrontends[index].address), wikipediaFrontends[index].name);
}
}
break;
default:
alert(decodeURIComponent(link.href));
}
return true;
}
}
}
return false;
}
function showHAFEWindow(link,evt) {
if (!hafeFrame) {
createPopup();
}
if (insertLinks(link)) {
hafePopup.style.display = '';
hafePopup.style.top = ((link.getBoundingClientRect().top + window.scrollY) - hafePopup.getBoundingClientRect().height) + "px";
hafePopup.style.left = link.getBoundingClientRect().left + "px";
} else {
hafePopup.style.display = 'none';
}
}
function init() {
for (var i=0; i < document.links.length; i++) {
var link = document.links[i];
/** Apparently deprecated. **/
// link.onmouseover = aMouseOver;
// link.onmouseout = aMouseOut;
/** The new way: **/
link.addEventListener("mouseover", aMouseOver, false);
link.addEventListener("mouseout", aMouseOut, false);
link.addEventListener("click", aClick, false);
//addEvents(link);
// link.addEventListener("mousemove", function(evt) { locate(evt); }, true);
}
}
init();
var observer = new MutationObserver(init);
observer.observe(document.body, { subtree: true, childList: true });
//observer.disconnect();
// window.document.checkFocus = checkFocus;