Skip to content

Instantly share code, notes, and snippets.

@rf5860
Last active June 10, 2024 12:28
Show Gist options
  • Save rf5860/e1f0ca962b000a3effc239ff9176dfcc to your computer and use it in GitHub Desktop.
Save rf5860/e1f0ca962b000a3effc239ff9176dfcc to your computer and use it in GitHub Desktop.
Alternative Hover Tooltips for Fandom Wiki's using the MediaWiki API
// ==UserScript==
// @name WikiHover
// @namespace https://fandom.com
// @version 1.2
// @description Inline wiki preview on hover for Fandom wikis
// @author rjf89
// @match *://warhammer-40000-space-wolf.fandom.com/*
// @match *://*.lexicanum.com/*
// @match *://bleach.fandom.com/*
// @match *://andromeda.fandom.com/*
// @match *://lastepoch.fandom.com/*
// @match *://monster-train.fandom.com/*
// @match *://warhammer40k.fandom.com/*
// @match *://wildermyth.com/*
// @require http://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.js
// @updateURL https://gist.github.com/rf5860/e1f0ca962b000a3effc239ff9176dfcc/raw/WikiHover.user.js
// @downloadURL https://gist.github.com/rf5860/e1f0ca962b000a3effc239ff9176dfcc/raw/WikiHover.user.js
// @grant GM_xmlhttpRequest
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_listValues
// ==/UserScript==
const KnownEndpoints = {
"ageofsigmar.lexicanum.com": "mediawiki/",
"wildermyth.com": "w/",
}
const Main = document.querySelector('#content, #mw-content, main');
const ComputedStyle = getComputedStyle(Main);
const Color = ComputedStyle ? ComputedStyle.color : "";
const Background = ComputedStyle ? ComputedStyle.background : "";
const BorderImage = ComputedStyle ? ComputedStyle.borderImage : "";
const Border = ComputedStyle ? ComputedStyle.border : "1px solid black";
const $content = $('*[id*=content]').first();
let $popup = $('<div id="wiki-popup"></div>').appendTo('body');
$popup.css({
"position": 'absolute',
"display": 'none',
"maxWidth": '350px',
"border": '1px solid black',
"padding": '10px',
"borderRadius": '5px',
"zIndex": '9999999',
"background": Background,
"borderImage": BorderImage,
"color": Color
});
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
const getPosition = e => {
const scrollTop = document.documentElement.scrollTop || window.scrollY || document.body.scrollTop;
const scrollLeft = document.documentElement.scrollLeft || window.scrollX || document.body.scrollLeft;
let left
let top
// Check if the tooltip goes off the right of the viewport
if (e.pageX + $popup.width() + 10 > viewportWidth) {
left = `${e.pageX - $popup.width() - 10}px`;
} else {
left = `${e.pageX + 10}px`;
}
// Check if the tooltip goes off the bottom of the viewport
if (e.pageY + $popup.height() + 10 > viewportHeight) {
top = `${e.pageY - $popup.height() - 10}px`;
} else {
top = `${e.pageY + 10}px`;
}
return { left, top };
}
let shouldDisplayPopup = true;
const displayPopup = (htmlContent, event) => {
if (shouldDisplayPopup) {
$popup.html(htmlContent);
$popup.css(getPosition(event))
$popup.show();
}
};
const hidePopup = () => {
shouldDisplayPopup = false;
$popup.hide();
};
async function handleHover(event) {
shouldDisplayPopup = true;
let content = await getWikiContent($(this).text());
if (this.className === 'mw-redirect') {
const link = $($.parseHTML(content)).find('a');
const otherWiki = link[0].href.split('/')[2];
const otherTitle = decodeURIComponent(link[0].href.split('/')[4]);
console.log(`Redirecting to ${otherWiki} ${otherTitle}`);
content = await getWikiContent(otherTitle, `https://${otherWiki}`);
}
displayPopup(content, event);
}
const cache = {};
const getWikiContent = (pageTitle, host = '') => new Promise((resolve, reject) => {
const cacheKey = `${host}:${pageTitle}`;
if (cache[cacheKey]) {
resolve(cache[cacheKey]);
return;
}
const apiUrl = `${host}/${KnownEndpoints[window.location.host] ?? ""}api.php`;
const params = {
"action": 'parse',
"page": pageTitle,
"prop": 'text',
"section": 0,
"format": 'json',
}
const urlParams = new URLSearchParams(params).toString();
GM_xmlhttpRequest({
method: "GET",
url: apiUrl + '?' + urlParams,
onload: response => {
const content = JSON.parse(response.responseText).parse.text['*'];
cache[cacheKey] = content;
GM_setValue(cacheKey, content);
resolve(content);
},
onerror: error => reject(error)
});
});
$(document).ready(() => {
// Load cached data from storage
for (const key in GM_listValues()) {
cache[key] = GM_getValue(key);
}
$('a').hover(handleHover, hidePopup);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment