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:25, 12 April 2025 (Restored revision 1285266754 by Twistedmath (talk): Last working ver). 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 () {
    function startPendingChecker() {
        if (mw.config.get("wgDBname") !== "enwiki") return;

        var seen = new Map();
        var count = 0;
        var baseInterval = 30000;
        var minDelay = 5000;

        var localKey = "pendingChangesSeen";
        var contentKey = "pendingChangesList";
        var seenStore = new Set(JSON.parse(localStorage.getItem(localKey) || "[]"));
        var storedContent = JSON.parse(localStorage.getItem(contentKey) || "[]");

        function saveContentList() {
            localStorage.setItem(contentKey, JSON.stringify(storedContent));
        }

        function formatTimestamp(date) {
            const now = Date.now();
            const diff = Math.floor((now - date) / 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`;
        }

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

        var pendingBell = document.createElement("span");
        pendingBell.id = "pending-alert-bell";
        pendingBell.style.cssText = "position: relative; margin-left: 10px; cursor: pointer; display: inline-block; width: 24px; height: 24px;";
        pendingBell.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 class='mw-echo-notifications-badgeItem' style='background:#d33;color:#fff;padding:2px 6px;border-radius:10px;font-size:11px;position:absolute;top:-6px;right:-6px;display:none;' id='pending-alert-count'>0</span>
        `;

        toolbar.parentElement.appendChild(pendingBell);

        var notifDropdown = $("<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
        });

        $(pendingBell).on("click", function () {
            notifDropdown.toggle();
        });

        var notifCount = $("#pending-alert-count");

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

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

        function updateTimestamps() {
            $(".pending-timestamp").each(function () {
                var ts = $(this).data("timestamp");
                $(this).text(formatTimestamp(new Date(ts)));
            });
        }

        function removeEntry(title) {
            const info = seen.get(title);
            if (info) {
                info.element.remove();
                seen.delete(title);
                storedContent = storedContent.filter(item => item.title !== title);
                count--;
                notifCount.text(count);
                if (count === 0) notifCount.hide();
                localStorage.setItem(contentKey, JSON.stringify(storedContent));
            }
        }

        function createRemoveIcon(title) {
            return $("<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(title));
        }

        function renderStoredNotifications() {
            count = 0;
            notifDropdown.empty();
            for (const item of storedContent) {
                const timeDiv = $("<div>")
                    .addClass("pending-timestamp")
                    .data("timestamp", item.timestamp)
                    .text(formatTimestamp(new Date(item.timestamp)))
                    .css({ fontSize: "10px", color: "#666" });

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

                notifDropdown.prepend(element);
                seen.set(item.title, { url: item.url, revid: item.revid, element });
                count++;
            }
            notifDropdown.append(clearBtn);
            notifCount.text(count);
            if (count > 0) notifCount.show();
            setInterval(updateTimestamps, 60000);
        }

        function addNotification(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);
            }
            saveContentList();

            const timeAgo = formatTimestamp(new Date(timestamp));

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

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

            notifDropdown.prepend(item);
            notifDropdown.append(clearBtn);
            notifCount.text(++count);
            if (count > 0) notifCount.show();

            seen.set(title, { url: url, revid: revid, element: item });

            mw.notify(
                $("<a>")
                    .attr("href", url)
                    .attr("target", "_blank")
                    .text("Pending: " + title)[0],
                {
                    title: "Pending Change Alert",
                    autoHide: false
                }
            );
        }

        function cleanupReviewed() {
            if (seen.size === 0) return;

            const titles = Array.from(seen.keys());
            const url = "https://en.wikipedia.org/w/api.php?action=query&prop=flagged&titles=" + encodeURIComponent(titles.join("|")) + "&format=json&origin=*";

            fetch(url)
                .then(res => res.json())
                .then(data => {
                    const pages = data.query.pages;
                    for (const id in pages) {
                        const page = pages[id];
                        if (!page.flagged || page.flagged.revid >= seen.get(page.title).revid) {
                            removeEntry(page.title);
                        }
                    }
                });
        }

        function fetchExistingPending() {
            const url = "https://en.wikipedia.org/w/api.php?action=query&list=oldreviewedpages&ornamespace=0&orlimit=50&format=json&origin=*";
            fetch(url)
                .then(res => res.json())
                .then(data => {
                    const pages = (data && data.query && data.query.oldreviewedpages) || [];
                    const titles = pages.map(page => page.title);

                    if (titles.length === 0) return;

                    const revUrl = "https://en.wikipedia.org/w/api.php?action=query&prop=revisions&rvprop=ids|timestamp&titles=" + encodeURIComponent(titles.join("|")) + "&format=json&origin=*";

                    fetch(revUrl)
                        .then(res => res.json())
                        .then(data2 => {
                            const pagesData = data2.query.pages;
                            Object.keys(pagesData).forEach(pageId => {
                                const page = pagesData[pageId];
                                if (!page.revisions || !page.revisions[0]) return;
                                const rev = page.revisions[0];
                                const revid = rev.revid;
                                const timestamp = rev.timestamp;
                                const title = page.title;
                                const url = "https://en.wikipedia.org/w/index.php?title=" + encodeURIComponent(title) + "&diff=" + revid + "&oldid=prev";
                                if (!seen.has(title)) {
                                    addNotification(title, url, revid, timestamp);
                                }
                            });
                        });
                })
                .catch(err => {
                    console.error("Error loading oldreviewedpages:", err);
                });
        }

        function checkRecentChanges() {
            const start = performance.now();
            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(res => res.json())
                .then(data => {
                    const changes = (data && data.query && data.query.recentchanges) || [];
                    for (let i = 0; i < changes.length; i++) {
                        const change = changes[i];
                        if (change.tags && change.tags.indexOf("flaggedrevs-pending") !== -1 && !seen.has(change.title)) {
                            const title = change.title;
                            const revid = change.revid;
                            const timestamp = change.timestamp;
                            const url = "https://en.wikipedia.org/w/index.php?title=" + encodeURIComponent(title) + "&diff=" + revid + "&oldid=prev";
                            addNotification(title, url, revid, timestamp);
                        }
                    }
                })
                .catch(e => {
                    console.error("Pending changes check failed:", e);
                })
                .finally(() => {
                    const duration = performance.now() - start;
                    const delay = Math.max(minDelay, baseInterval - duration);
                    setTimeout(checkRecentChanges, delay);
                    setTimeout(cleanupReviewed, 10000);
                });
        }

        renderStoredNotifications();
        fetchExistingPending();
        checkRecentChanges();
    }

    startPendingChecker();
});