Jump to content

User:Veko/common.js

From Wikipedia, the free encyclopedia
This is an old revision of this page, as edited by Veko (talk | contribs) at 18:24, 12 April 2025 (bugs and bugs). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
mw.loader.using(['jquery', 'mediawiki.util']).then(function () {
    if (mw.config.get("wgDBname") !== "enwiki") return;

    const seen = new Map();
    const seenKey = "pendingChangesSeen";
    const storeKey = "pendingChangesList";
    let count = 0;
    let seenStore = new Set(JSON.parse(localStorage.getItem(seenKey) || "[]"));
    let storedContent = JSON.parse(localStorage.getItem(storeKey) || "[]");

    function formatTime(ts) {
        const d = new Date(ts);
        const now = Date.now();
        const diff = Math.floor((now - d.getTime()) / 1000);
        if (diff < 60) return `${diff}s ago`;
        if (diff < 3600) return `${Math.floor(diff / 60)}m ago`;
        if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`;
        return `${Math.floor(diff / 86400)}d ago`;
    }

    const toolbar = document.querySelector('#pt-notifications-alert, .mw-echo-notifications-badge');
    if (!toolbar) return;

    const bell = document.createElement("span");
    bell.id = "pending-alert-bell";
    bell.style.cssText = "position: relative; margin-left: 10px; cursor: pointer; display: inline-block; width: 24px; height: 24px;";
    bell.innerHTML = `
        <svg viewBox="0 0 24 24" width="20" height="20" fill="#54595d" xmlns="http://www.w3.org/2000/svg">
            <path d="M12 2a6 6 0 00-6 6v4.586l-.707.707A1 1 0 006 15h12a1 1 0 00.707-1.707L18 12.586V8a6 6 0 00-6-6zm0 20a2 2 0 001.995-1.85L14 20h-4a2 2 0 001.85 1.995L12 22z" />
        </svg>
        <span id='pending-alert-count' style='background:#d33;color:#fff;padding:2px 6px;border-radius:10px;font-size:11px;position:absolute;top:-6px;right:-6px;display:none;'>0</span>
    `;
    toolbar.parentElement.appendChild(bell);

    const dropdown = $("<div>").css({
        display: "none",
        position: "absolute",
        top: "30px",
        right: "0",
        width: "320px",
        maxHeight: "400px",
        overflowY: "auto",
        background: "#fff",
        border: "1px solid #ccc",
        borderRadius: "6px",
        boxShadow: "0 4px 12px rgba(0,0,0,0.15)",
        padding: "10px",
        zIndex: 9999
    });

    $(bell).on("click", () => dropdown.toggle());

    const countBadge = $("#pending-alert-count");

    const clear = $('<div>').text("Clear seen history").css({
        textAlign: "center",
        margin: "10px 0",
        cursor: "pointer",
        fontSize: "12px",
        color: "#0645ad",
        textDecoration: "underline"
    }).click(() => {
        seenStore.clear();
        storedContent = [];
        localStorage.removeItem(seenKey);
        localStorage.removeItem(storeKey);
        location.reload();
    });

    $("body").append(dropdown);

    function removeEntry(title) {
        const entry = seen.get(title);
        if (!entry) return;
        entry.element.remove();
        seen.delete(title);
        storedContent = storedContent.filter(i => i.title !== title);
        count--;
        countBadge.text(count);
        if (count === 0) countBadge.hide();
        localStorage.setItem(storeKey, JSON.stringify(storedContent));
    }

    function createItem(entry) {
        const time = $("<div>")
            .addClass("pending-timestamp")
            .data("timestamp", entry.timestamp)
            .text(formatTime(entry.timestamp))
            .css({ fontSize: "10px", color: "#666" });

        const remove = $('<span>').html(`
            <svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='#999' stroke-width='2' stroke-linecap='round'><path d='M18 6L6 18M6 6l12 12'/></svg>
        `).css({ float: "right", cursor: "pointer", marginLeft: "8px" }).attr("title", "Mark as reviewed").click(() => removeEntry(entry.title));

        const item = $("<div>").css({ marginBottom: "8px", paddingBottom: "8px", borderBottom: "1px solid #eee" }).append(
            $("<a>").attr("href", entry.url).attr("target", "_blank").text(entry.title).css({ fontWeight: "bold", color: "#0645ad" }),
            remove,
            time
        );

        dropdown.prepend(item);
        seen.set(entry.title, { ...entry, element: item });
        count++;
        countBadge.text(count).show();
    }

    function refreshTimestamps() {
        $(".pending-timestamp").each(function () {
            const ts = $(this).data("timestamp");
            $(this).text(formatTime(ts));
        });
    }

    function addEntry(title, url, revid, timestamp) {
        if (seen.has(title)) return;
        const entry = { title, url, revid, timestamp };
        storedContent.push(entry);
        if (storedContent.length > 100) storedContent = storedContent.slice(-100);
        localStorage.setItem(storeKey, JSON.stringify(storedContent));
        createItem(entry);
        mw.notify($("<a>").attr("href", url).attr("target", "_blank").text("Pending: " + title)[0], { title: "Pending Change Alert", autoHide: false });
    }

    function renderStored() {
        dropdown.empty();
        count = 0;
        storedContent.forEach(createItem);
        dropdown.append(clear);
        setInterval(refreshTimestamps, 60000);
    }

    function checkReviewed() {
        if (!seen.size) return;
        const titles = Array.from(seen.keys());
        const url = "https://en.wikipedia.org/w/api.php?action=query&prop=flagged|revisions&rvprop=ids&titles=" + encodeURIComponent(titles.join("|")) + "&format=json&origin=*";
        fetch(url).then(r => r.json()).then(data => {
            const pages = data.query.pages;
            for (const id in pages) {
                const page = pages[id];
                const stored = seen.get(page.title);
                if (!stored) continue;
                const reviewedRev = page.flagged?.revid || page.revisions?.[0]?.revid || 0;
                if (reviewedRev >= stored.revid) removeEntry(page.title);
            }
        });
    }

    function fetchPending() {
        fetch("https://en.wikipedia.org/w/api.php?action=query&list=oldreviewedpages&ornamespace=0&orlimit=50&format=json&origin=*")
            .then(r => r.json())
            .then(data => {
                const titles = (data.query.oldreviewedpages || []).map(p => p.title);
                if (!titles.length) return;
                const url = "https://en.wikipedia.org/w/api.php?action=query&prop=revisions&rvprop=ids|timestamp&titles=" + encodeURIComponent(titles.join("|")) + "&format=json&origin=*";
                fetch(url).then(r => r.json()).then(d => {
                    for (const id in d.query.pages) {
                        const page = d.query.pages[id];
                        const rev = page.revisions?.[0];
                        if (!rev) continue;
                        const diffUrl = `https://en.wikipedia.org/w/index.php?title=${encodeURIComponent(page.title)}&diff=${rev.revid}&oldid=prev`;
                        addEntry(page.title, diffUrl, rev.revid, rev.timestamp);
                    }
                });
            });
    }

    function pollRecentChanges() {
        const url = "https://en.wikipedia.org/w/api.php?action=query&list=recentchanges&rcprop=title|ids|tags|timestamp&rclimit=50&rcshow=!bot&format=json&origin=*";
        fetch(url).then(r => r.json()).then(data => {
            (data.query.recentchanges || []).forEach(change => {
                if (change.tags.includes("flaggedrevs-pending") && !seen.has(change.title)) {
                    const url = `https://en.wikipedia.org/w/index.php?title=${encodeURIComponent(change.title)}&diff=${change.revid}&oldid=prev`;
                    addEntry(change.title, url, change.revid, change.timestamp);
                }
            });
        });
    }

    renderStored();
    fetchPending();
    pollRecentChanges();
    setInterval(pollRecentChanges, 30000);
    setTimeout(() => setInterval(checkReviewed, 30000), 30000);
});