AQW Wiki Link Preview

Adds image previews for links on the official AQW Wiki

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name        AQW Wiki Link Preview
// @description Adds image previews for links on the official AQW Wiki
// @namespace   Lamp
// @license     GNU GPLv3
// @include     *aqwwiki.wikidot.com/*
// @include     *wqa.wikidot.com/*
// @version     2.0.1
// ==/UserScript==




let currentMousePos = { x: -1, y: -1 };
let hoverTimeout = null;
let lastHoveredElement = null;

document.addEventListener("mousemove", (event) => {
    currentMousePos.x = event.pageX;
    currentMousePos.y = event.pageY;
});

const excludedTerms = [
    "image-tags",
    "classes-skills",
    "AQWPassive",
    "quantserve",
    "statcounter",
    "avatar.php"
];

function isNotHidden(img) {
    let currentElement = img.parentElement;
    while (currentElement) {
        const inlineStyle = currentElement.getAttribute("style");
        if (inlineStyle && inlineStyle.includes("display:none")) {
            return false;
        }
        currentElement = currentElement.parentElement;
    }
    return true;
}

function isValidImageElement(img) {
    const src = img.getAttribute("src");

    if (!src || excludedTerms.some(term => src.includes(term))) {
        return false;
    }

    return true;
}

async function fetchAndParse(url) {
    const response = await fetch(url);
    const text = await response.text();
    const parser = new DOMParser();
    const doc = parser.parseFromString(text, "text/html");

    return doc;
}

function extractImages(doc) {
    const images = [];
    const hasFemale = Array.from(doc.querySelectorAll("em")).some(em => em.textContent.trim() === "Female");

    if (hasFemale) {
        const maleContainers = doc.querySelectorAll("#wiki-tab-0-0");
        const femaleContainers = doc.querySelectorAll("#wiki-tab-0-1");

        let maleImage = null;
        let femaleImage = null;

        for (const maleContainer of maleContainers) {
            const img = maleContainer.querySelector("img");
            if (isValidImageElement(img)) {
                maleImage = img;
                break;
            }
        }

        for (const femaleContainer of femaleContainers) {
            const img = femaleContainer.querySelector("img");
            if (isValidImageElement(img)) {
                femaleImage = img;
                break;
            }
        }

        if (maleImage) {
            images.push(maleImage.getAttribute("src"));
        }
        if (femaleImage) {
            images.push(femaleImage.getAttribute("src"));
        }
    }

    if (images.length === 0) {
        const allImages = Array.from(doc.querySelectorAll("img"));
        for (const img of allImages) {
            if (isValidImageElement(img) && isNotHidden(img)) {
                images.push(img.getAttribute("src"));
                break;
            }
        }
    }

    return images;
}

function createPreviewElement(images) {
    const preview = document.createElement("div");
    preview.id = "preview";
    preview.style.position = "absolute";
    preview.style.zIndex = "9999";
    preview.style.backgroundColor = "transparent";
    preview.style.display = "flex";

    let maxHeight = "400px";
    if (images.length === 2) {
        maxHeight = "600px";
        preview.style.width = "auto";
    }

    images.forEach((src) => {
        const img = document.createElement("img");
        img.src = src;
        img.style.maxHeight = maxHeight;
        img.style.display = "block";
        preview.appendChild(img);
    });

    return preview;
}

function removeAllPreviews() {
    document.querySelectorAll("#preview").forEach(preview => preview.remove());
    if (hoverTimeout) {
        clearTimeout(hoverTimeout);
        hoverTimeout = null;
    }
    lastHoveredElement = null;
}

document.addEventListener("mouseenter", (event) => {
    if (event.target.matches("a.item, div.item > div > a, div.title > a, div.list-pages-item > p > a, div.collapsible-block-content > a, div.collapsible-block-content > p > a, div.yui-content > div > a, div.yui-content > div > p > a, div.list-pages-box > p > a, #page-content > ul > li > a, #page-content > ul > li > span > a, #page-content > ul > li > ul > li > a, div.yui-content > div > ul > li > ul > li > a, #page-content > a, #page-content > p > a, tr > td > a, .yui-content > div > ul > li > a")) {
        removeAllPreviews();

        const url = event.target.href;
        lastHoveredElement = event.target;

        hoverTimeout = setTimeout(async () => {
            if (lastHoveredElement !== event.target) return;

            const doc = await fetchAndParse(url);
            const images = extractImages(doc);
            const preview = createPreviewElement(images);

            document.body.appendChild(preview);
            preview.style.top = `${currentMousePos.y - 200}px`;
            preview.style.left = `${currentMousePos.x + 100}px`;

            const removePreview = () => removeAllPreviews();
            event.target.addEventListener("mouseleave", removePreview, { once: true });
            preview.addEventListener("mouseleave", removePreview, { once: true });
        }, 250);
    }
}, true);