Greasy Fork镜像 还支持 简体中文。

Hover Alternative Front-Ends

Pops up a floating div when you hover over a link, containing alternative front-ends!

  1. // ==UserScript==
  2. // @name Hover Alternative Front-Ends
  3. // @namespace HAFE
  4. // @description Pops up a floating div when you hover over a link, containing alternative front-ends!
  5. // @homepageURL https://gf.qytechs.cn/en/scripts/437920-hover-alternative-front-ends
  6. // @match *://*/*
  7. // @grant none
  8. // @run-at document-idle
  9. // @version 1.2.3
  10. // ==/UserScript==
  11.  
  12. // This script is a fork of 'Hover Preview':
  13. // https://gf.qytechs.cn/en/scripts/8042-hover-preview
  14.  
  15. const focusReactionTime = 300;
  16. const unfocusReactionTime = 1500;
  17.  
  18. const domains = {
  19. youtube: ["youtube.com","youtu.be","youtube-nocookie.com"],
  20. twitter: ["twitter.com","twimg.com"],
  21. reddit: ["reddit.com","i.redd.it"],
  22. instagram: ["instagram.com"],
  23. imgur: ["imgur.com"],
  24. medium: ["medium.com"],
  25. wikipedia: ["en.wikipedia.org"]
  26. };
  27.  
  28. const youtubeFrontends = [{
  29. name: "Invidious(Snopyta)",
  30. address: "invidious.snopyta.org"
  31. },{
  32. name: "Invidious(Yewtube)",
  33. address: "yewtu.be"
  34. },{
  35. name: "Invidious(Puffyan)",
  36. address: "vid.puffyan.us"
  37. },{
  38. name: "Invidious(Seth)",
  39. address: "invidious.sethforprivacy.com"
  40. },{
  41. name: "CloudTube",
  42. address: "tube.cadence.moe"
  43. },{
  44. name: "Piped",
  45. address: "piped.kavin.rocks",
  46. }];
  47.  
  48. const twitterFrontends = [{
  49. name: "Nitter",
  50. address: "nitter.net"
  51. },{
  52. name: "Nitter(Snopyta)",
  53. address: "nitter.snopyta.org"
  54. },{
  55. name: "Nitter(Puss)",
  56. address: "nitter.pussthecat.org"
  57. },{
  58. name: "Nitter(42l)",
  59. address: "nitter.42l.fr"
  60. },{
  61. name: "Nitter(Seth)",
  62. address: "nitter.sethforprivacy.com"
  63. },{
  64. name: "Nitter(Action Sack)",
  65. address: "nitter.actionsack.com"
  66. }];
  67.  
  68. const redditFrontends = [{
  69. name: "Teddit",
  70. address: "teddit.net"
  71. },{
  72. name: "Teddit(Puss)",
  73. address: "teddit.pussthecat.org"
  74. },{
  75. name: "Teddit(Seth)",
  76. address: "teddit.sethforprivacy.com"
  77. },{
  78. name: "Libreddit",
  79. address: "libredd.it"
  80. },{
  81. name: "Libreddit(Spike)",
  82. address: "libreddit.spike.codes"
  83. },{
  84. name: "Libreddit(Puss)",
  85. address: "libreddit.pussthecat.org"
  86. }];
  87.  
  88. const instagramFrontends = [{
  89. name: "Bibliogram",
  90. address: "bibliogram.art"
  91. },{
  92. name: "Bibliogram(Snopyta)",
  93. address: "bibliogram.snopyta.org"
  94. },{
  95. name: "Bibliogram(Puss)",
  96. address: "bibliogram.pussthecat.org"
  97. },{
  98. name: "Bibliogram(Action Sack)",
  99. address: "bib.actionsack.com"
  100. },{
  101. name: "Bibliogram(Hamster)",
  102. address: "bibliogram.hamster.dance"
  103. }];
  104.  
  105. const imgurFrontends = [{
  106. name: "Imgin",
  107. address: "imgin.voidnet.tech"
  108. },{
  109. name: "Ringu(Action Sack)",
  110. address: "i.actionsack.com"
  111. },{
  112. name: "Kageurufu",
  113. address: "imgur.kageurufu.net"
  114. }];
  115.  
  116. const mediumFrontends = [{
  117. name: "Scribe",
  118. address: "scribe.rip"
  119. },{
  120. name: "Scribe(NixNet)",
  121. address: "scribe.nixnet.services"
  122. }];
  123.  
  124. const wikipediaFrontends = [{
  125. name: "Wikiless",
  126. address: "wikiless.org"
  127. },{
  128. name: "Wikiless(Seth)",
  129. address: "wikiless.sethforprivacy.com"
  130. },{
  131. name: "Infogalactic",
  132. address: "infogalactic.com"
  133. }];
  134.  
  135. var focus = undefined;
  136. var lastFocus = undefined;
  137. var timer = null;
  138.  
  139. var hafePopup;
  140. var hafeFrame;
  141.  
  142. var isOverPopup = false;
  143.  
  144. function checkFocus() {
  145. if (focus) {
  146. // if (focus == lastFocus) {
  147. // User has definitely been here a while
  148. showHAFEWindow(focus);
  149. // } else {
  150. // }
  151. // lastFocus = focus;
  152. }
  153. }
  154.  
  155. function aMouseOver(evt) {
  156. if (evt.currentTarget.tagName !== "A") {
  157. alert(decodeURIComponent("not link"));
  158. return;
  159. }
  160. if (!focus) {
  161. focus = evt.currentTarget;
  162. // setTimeout('checkFocus();',focusReactionTime);
  163. // Hack to bring the popup back immediately if we've gone back to the same link.
  164. if (hafeFrame && focus.href && hafeFrame.href == focus.href) {
  165. showHAFEWindow(focus,evt);
  166. } else {
  167. if (timer) {
  168. clearTimeout(timer);
  169. }
  170. timer = setTimeout(checkFocus,focusReactionTime);
  171. }
  172. } else {
  173. window.status = "Already focused on a link wtf!";
  174. }
  175. }
  176.  
  177. function aMouseOut(evt) {
  178. if (evt.currentTarget.tagName !== "A") {
  179. return;
  180. }
  181. focus = undefined;
  182. if (timer) {
  183. clearTimeout(timer);
  184. }
  185. // TESTING: Don't hide the popup if mouse is currently over the popup!
  186. timer = setTimeout(clearPopup,unfocusReactionTime);
  187. }
  188.  
  189. function clearPopup(e) {
  190. if (isOverPopup || focus)
  191. return;
  192. if (hafePopup) {
  193. // hafePopup.parentNode.removeChild(hafePopup);
  194. // hafePopup = undefined; // eww cache it!
  195. hafePopup.style.display = 'none';
  196. }
  197. }
  198.  
  199. // DONE: If the user clicks a link, this isn't really a hover, so we should not
  200. // activate and just let the user's click be processed!
  201. function aClick(evt) {
  202. focus = undefined;
  203. }
  204.  
  205. function createPopup() {
  206. // Create frame
  207. hafePopup = document.createElement('DIV');
  208. /** Seems style does not work for Konqueror this way. **/
  209. hafePopup.innerHTML =
  210. "<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>"
  211. +
  212. "<DIV class='hafediv' width='" + (window.innerWidth * 0.75) + "' height='" + (window.innerHeight*0.75) + "' src='about:blank'></DIV>";
  213. hafePopup.addEventListener("mouseover", function(evt) { isOverPopup=true; }, false);
  214. hafePopup.addEventListener("mouseout", function(evt) { isOverPopup=false; setTimeout(clearPopup,unfocusReactionTime); }, false);
  215. document.documentElement.appendChild(hafePopup);
  216. hafePopup.style.position = "absolute";
  217. hafePopup.style.zIndex = "10000";
  218. hafeFrame = hafePopup.getElementsByTagName('DIV')[0];
  219. }
  220.  
  221. function insertLink(linkAddress, linkText) {
  222. var newLink = document.createElement('a');
  223. newLink.href = linkAddress
  224. newLink.textContent = linkText
  225. hafeFrame.append(newLink);
  226. hafeFrame.append(document.createTextNode(" "));
  227. }
  228.  
  229. function insertLinks(link) {
  230. hafeFrame.innerHTML = "";
  231.  
  232. var linkHost = link.href.match(/^https?\:\/\/([^\/?#]+)(?:[\/?#]|$)/i);
  233. var linkPath = link.href.replace(linkHost[0], "");
  234.  
  235. for (var domain in domains) {
  236. for (var target=0; target < domains[domain].length; target++) {
  237. if (linkHost[0].includes(domains[domain][target])) {
  238. switch (domain) {
  239. case "youtube":
  240. if (linkHost[1].includes("studio")) {
  241. return false;
  242. } else {
  243. for (var index=0; index < youtubeFrontends.length; index++) {
  244. insertLink(link.href.replace(linkHost[1], youtubeFrontends[index].address), youtubeFrontends[index].name);
  245. }
  246. }
  247. break;
  248.  
  249. case "twitter":
  250. if (linkHost[1].includes("pbs") || linkHost[1].includes("video")) {
  251. for (var index=0; index < twitterFrontends.length; index++) {
  252. insertLink(linkHost[0].replace(linkHost[1], twitterFrontends[index].address) + "pic/" + encodeURIComponent(link.href), twitterFrontends[index].name);
  253. }
  254. } else {
  255. for (var index=0; index < twitterFrontends.length; index++) {
  256. insertLink(link.href.replace(linkHost[1], twitterFrontends[index].address), twitterFrontends[index].name);
  257. }
  258. }
  259. break;
  260.  
  261. case "reddit":
  262. if (domains[domain][target] === "i.redd.it") {
  263. for (var index=0; index < redditFrontends.length; index++) {
  264. if (redditFrontends[index].name.includes("Libreddit")) {
  265. insertLink(linkHost[0].replace(linkHost[1], redditFrontends[index].address) + "img/" + linkPath, redditFrontends[index].name);
  266. }
  267. }
  268. } else {
  269. for (var index=0; index < redditFrontends.length; index++) {
  270. insertLink(link.href.replace(linkHost[1], redditFrontends[index].address), redditFrontends[index].name);
  271. }
  272. }
  273. break;
  274.  
  275. case "instagram":
  276. const instagramPaths = /\b(?:tv|reels?|u|p)\b/i;
  277. const instagramIgnore = /\b(?:stories|accounts|explore|topics)\b/i;
  278. var linkFolders = linkPath.split(/(?:\?|\/)+/);
  279.  
  280. if (linkHost[1].includes("about") || linkHost[1].includes("help") || linkFolders[0].match(instagramIgnore)) {
  281. return false;
  282. } else if (linkFolders.length > 0 && linkFolders[0].match(instagramPaths)) {
  283. for (var index=0; index < instagramFrontends.length; index++) {
  284. insertLink(link.href.replace(linkHost[1], instagramFrontends[index].address), instagramFrontends[index].name);
  285. }
  286. } else if (linkFolders.length > 1 && linkFolders[1].match(instagramPaths)) {
  287. for (var index=0; index < instagramFrontends.length; index++) {
  288. insertLink(linkHost[0].replace(linkHost[1], instagramFrontends[index].address) + linkPath.replace(linkFolders[0] + "/", ""), instagramFrontends[index].name);
  289. }
  290. } else {
  291. for (var index=0; index < instagramFrontends.length; index++) {
  292. insertLink(link.href.replace(linkHost[1], instagramFrontends[index].address + "/u"), instagramFrontends[index].name);
  293. }
  294. }
  295. break;
  296.  
  297. case "imgur":
  298. for (var index=0; index < imgurFrontends.length; index++) {
  299. insertLink(link.href.replace(linkHost[1], imgurFrontends[index].address), imgurFrontends[index].name);
  300. }
  301. break;
  302.  
  303. case "medium":
  304. for (var index=0; index < mediumFrontends.length; index++) {
  305. insertLink(link.href.replace(linkHost[1], mediumFrontends[index].address), mediumFrontends[index].name);
  306. }
  307. break;
  308.  
  309. case "wikipedia":
  310. for (var index=0; index < wikipediaFrontends.length; index++) {
  311. if (wikipediaFrontends[index].name.includes("Infogalactic")) {
  312. insertLink(linkHost[0].replace(linkHost[1], wikipediaFrontends[index].address) + linkPath.replace("wiki", "info"), wikipediaFrontends[index].name);
  313. } else {
  314. insertLink(link.href.replace(linkHost[1], wikipediaFrontends[index].address), wikipediaFrontends[index].name);
  315. }
  316. }
  317. break;
  318.  
  319. default:
  320. alert(decodeURIComponent(link.href));
  321. }
  322. return true;
  323. }
  324. }
  325. }
  326. return false;
  327. }
  328.  
  329. function showHAFEWindow(link,evt) {
  330. if (!hafeFrame) {
  331. createPopup();
  332. }
  333. if (insertLinks(link)) {
  334. hafePopup.style.display = '';
  335. hafePopup.style.top = ((link.getBoundingClientRect().top + window.scrollY) - hafePopup.getBoundingClientRect().height) + "px";
  336. hafePopup.style.left = link.getBoundingClientRect().left + "px";
  337. } else {
  338. hafePopup.style.display = 'none';
  339. }
  340. }
  341.  
  342. function init() {
  343. for (var i=0; i < document.links.length; i++) {
  344. var link = document.links[i];
  345. /** Apparently deprecated. **/
  346. // link.onmouseover = aMouseOver;
  347. // link.onmouseout = aMouseOut;
  348. /** The new way: **/
  349. link.addEventListener("mouseover", aMouseOver, false);
  350. link.addEventListener("mouseout", aMouseOut, false);
  351. link.addEventListener("click", aClick, false);
  352. //addEvents(link);
  353. // link.addEventListener("mousemove", function(evt) { locate(evt); }, true);
  354. }
  355. }
  356.  
  357. init();
  358.  
  359. var observer = new MutationObserver(init);
  360.  
  361. observer.observe(document.body, { subtree: true, childList: true });
  362. //observer.disconnect();
  363.  
  364. // window.document.checkFocus = checkFocus;
  365.  

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址