Wikipedia link preview

Show preview of Wikipedia articles when hovering over a link.

// ==UserScript==
// @name         Wikipedia link preview
// @namespace    http://tampermonkey.net/
// @version      2025-09-06
// @description  Show preview of Wikipedia articles when hovering over a link.
// @author       You
// @match        https://*.wikipedia.org/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=wikipedia.org
// @grant        none
// @license      MIT
// ==/UserScript==

(async function() {
    'use strict';

    const {computePosition, autoUpdate, flip, shift} = await import('https://cdn.jsdelivr.net/npm/@floating-ui/[email protected]/+esm');

    const content = document.getElementById("mw-content-text");
    const links = content.querySelectorAll(`a[href*="wikipedia.org" i], a[href^="/wiki/" i]`);
    links.forEach(link => {
        const hovercard = document.createElement("div");
        hovercard.classList.add("hovercard");
        hovercard.dataset.link = link.href;
        hovercard.style.display = "none";
        hovercard.style.position = "absolute";
        hovercard.style.top = "0";
        hovercard.style.left = "0";
        hovercard.style.width = "fit-content";
        hovercard.style.maxWidth = "400px";
        hovercard.style.height = "fit-content";
        hovercard.style.maxHeight = "300px";
        hovercard.style.zIndex = "9999999";
        hovercard.style.background = "#ffffff";
        hovercard.style.padding = "1rem";
        hovercard.style.border = "1px solid #aaa";
        hovercard.style.borderRadius = "6px";

        function update() {
            computePosition(link, hovercard, {
                placement: 'bottom-end',
                middleware: [flip(), shift()],
            }).then(({x, y, placement, middlewareData}) => {
                Object.assign(hovercard.style, {
                    left: `${x}px`,
                    top: `${y}px`,
                });
            });
        }

        function showHovercard() {
            hovercard.style.display = `block`;
            document.body.append(hovercard);
            update();
        }

        function hideHovercard() {
            hovercard.style.display = `none`;
            hovercard.remove();
        }

        [
            ['pointerenter', showHovercard],
            ['pointerleave', hideHovercard],
            ['focus', showHovercard],
            ['blur', hideHovercard],
        ].forEach(([event, listener]) => {
            link.addEventListener(event, listener);
        });

        link.addEventListener("pointerenter", async () => {
            if (hovercard.dataset.loaded === "true") {
                return;
            }

            const res = await fetch(link.href);
            const body = await res.text();
            const parser = new DOMParser();
            const doc = parser.parseFromString(body, "text/html");

            const title = doc.getElementById("firstHeading").textContent;
            const contentDiv = doc.getElementById("mw-content-text");
            const paragraphs = contentDiv.querySelectorAll(`p:nth-of-type(-n+5)`);
            let content = ``;
            paragraphs.forEach(p => {
                if (content.length <= 760) {
                    if (p.textContent.length < 760 - content.length) {
                        content += `<p style="font-size: 0.875rem;">${p.innerHTML}</p>`;
                    } else {
                        content += `<p style="font-size: 0.875rem;">${p.innerHTML.substring(0, 760 - content.length)}...</p>`;
                        return;
                    }
                }
            });

            hovercard.innerHTML = `<h3>${title}</h3>${content}`;
            hovercard.dataset.loaded = "true";
        });
    });
})();