Jump to content

MediaWiki:Gadget-firstedit-test.js

From testwiki, a Wikimedia project for testing

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/**
 * Wiki Edit Anniversary Script along with the notification
 * Highlights a user's first edit anniversary and allows posting a detailed congratulatory message.
 * It displays the user's first edit date at the top of their user or user talk page.
 * If the anniversary date is within ±30 days, a dialog box will appear.
 * A prefilled message — based on the user's first edit date will be shown, which you can modify before posting.
 * Upon confirmation, the message will be posted on the user's talk page.
 * Released under the GPL-3.0+ License by [[:m:User:Gnoeee|Gnoeee]] as part of the thought process for [[:m:User:Gnoeee/Wikipedia@25]].
 */

$(function () {
    const ns = mw.config.get('wgNamespaceNumber');
    if (![2, 3].includes(ns) || mw.config.get('wgAction') !== 'view') return;

    const username = mw.config.get('wgTitle').split('/')[0];
    const currentUser = mw.config.get('wgUserName');
    if (!currentUser || username === currentUser) return;

    const markup = `
        <div id="firstedit-info" style="font-size:95%; line-height:1.5em; margin:10px 0; text-align:center;">
            <span id="firstedit_result">Fetching first edit anniversary…</span>
        </div>
    `;
    $(markup).insertBefore('#mw-content-subtitle');
    const $result = $('#firstedit_result');

    const api = new mw.Api();

    api.get({
        action: 'query',
        list: 'usercontribs',
        ucuser: username,
        ucdir: 'newer',
        uclimit: 1,
        format: 'json'
    }).done(async function (data) {
        const contribs = data && data.query && data.query.usercontribs;
        if (!contribs || contribs.length === 0) {
            $result.text('No edit history found for this user.');
            return;
        }

        const firstEditDate = new Date(contribs[0].timestamp);
        const today = new Date();
        const currentYear = today.getFullYear();

        const anniversary = new Date(firstEditDate);
        anniversary.setFullYear(currentYear);

        const diffDays = Math.floor((anniversary - today) / (1000 * 60 * 60 * 24));
        const diffYears = today.getFullYear() - firstEditDate.getFullYear();

        let message = `🕓 First edit: <b>${firstEditDate.toDateString()}</b><br>`;
        let showBox = false;

		if (diffYears >= 1) { // Only celebrate if 1 year or more has passed
		    if (diffDays === 0) {
		        message += `<b style="font-size:115%; color:#d63384;">🎉 Today marks ${diffYears} year${diffYears !== 1 ? "s" : ""} since ${username}'s first edit! 🎉</b>`;
		        addGlitterStyle();
		        startConfetti();
		        showBox = true;
		    } else if (diffDays > 0 && diffDays <= 30) {
		        message += `✨ ${diffDays} day${diffDays !== 1 ? 's' : ''} until ${username}'s next first edit anniversary!`;
		        showBox = true;
		    } else if (diffDays < 0 && Math.abs(diffDays) <= 30) {
		        message += `💫 ${Math.abs(diffDays)} day${Math.abs(diffDays) !== 1 ? 's' : ''} since ${username}'s first edit anniversary.`;
		        showBox = true;
		    }
		} else {
		    message += `(Less than a year since their first edit)`;
		}

        $result.html(message);

        if (!showBox) return;

        // Fetch edit count for richer message text
        const userInfo = await api.get({
            action: 'query',
            list: 'users',
            ususers: username,
            usprop: 'editcount',
            format: 'json'
        });
        const editCount = userInfo && userInfo.query && userInfo.query.users && userInfo.query.users[0] && userInfo.query.users[0].editcount || 0;

        const daysUntilAnniv = diffDays < 0 ? 365 - Math.abs(diffDays) : diffDays;
        const whenText = diffDays === 0
            ? "is today"
            : diffDays < 0
                ? `was ${Math.abs(diffDays)} day${Math.abs(diffDays) !== 1 ? "s" : ""} ago`
            	: `is in ${diffDays} day${diffDays !== 1 ? "s" : ""}`;
            	
        const wikiSignature = "-~~" + "~~";

        const fullMessage =
`Hey @[[User:${username}|${username}]]. Your wiki edit anniversary ${whenText}, marking ${diffYears} year${diffYears !== 1 ? "s" : ""} of dedicated contributions to this Wikimedia project. Your passion for sharing knowledge and your remarkable contributions have not only enriched the project, but also inspired countless others to contribute. With over ${editCount.toLocaleString()} edits, your dedication is an inspiration to the community. Thank you for your amazing contributions. Wishing you all the best for the year ahead :) ${wikiSignature}`;

        showAnniversaryBox(username, fullMessage);
    }).fail(() => {
        $result.text('Error fetching first edit date.');
    });

    function showAnniversaryBox(user, msgText) {
        const summaryText = `Wishing first edit anniversary :) ([[:testwiki:User:Gnoeee/firstedit.js|Firstedit v1.1]]).`;

        if ($('#anniv-review-box').length) return;

        const box = $(`
            <div id="anniv-review-box" style="
                position: fixed; bottom: 10px; right: 10px; width: 340px;
                background: #fff; border: 2px solid #d63384; border-radius: 8px;
                padding: 15px; z-index: 99999; box-shadow: 0 0 10px rgba(0,0,0,0.3);
                font-size: 14px; line-height: 1.4em;
            ">
                <h4 style="margin:0 0 10px 0; color:#d63384;">Anniversary Message Preview</h4>
                <textarea id="anniv-msg-text" style="width:100%; height:160px;">${msgText}</textarea>
                <br>
                <label style="font-size:12px;">Edit summary:</label>
                <input type="text" id="anniv-msg-summary" style="width:100%;" value="${summaryText}">
                <div style="text-align:right; margin-top:10px;">
                    <button id="anniv-msg-ok" style="background:#d63384; color:white; border:none; padding:5px 20px; border-radius:4px; cursor:pointer;">Post</button>
                    <button id="anniv-msg-cancel" style="margin-left:200px; padding:5px 10px; cursor:pointer;">Cancel</button>
                </div>
            </div>
        `);

        $('body').append(box);

        $('#anniv-msg-ok').on('click', function () {
            const text = $('#anniv-msg-text').val();
            const summary = $('#anniv-msg-summary').val();

            const api = new mw.Api();
            api.postWithEditToken({
                action: 'edit',
                title: `User talk:${user}`,
                section: 'new',
                sectiontitle: `Happy First Edit Anniversary ${user} 🎉`,
                text: text,
                summary: summary
            }).then(() => {
                alert(`✅ Message successfully posted on ${user}'s talk page`);
                box.remove();
            }).catch(err => {
                console.error(err);
                alert('❌ Failed to post the message.');
            });
        });

        $('#anniv-msg-cancel').on('click', function () {
            box.remove();
        });
    }

    // Glitter effect
    function addGlitterStyle() {
        const style = `
            @keyframes glitter {
                0% {opacity: 0.8; transform: scale(1);}
                50% {opacity: 1; transform: scale(1.1);}
                100% {opacity: 0.8; transform: scale(1);}
            }
            .glitter {
                display: inline-block;
                animation: glitter 1s infinite ease-in-out;
                color: #ff69b4;
            }
        `;
        $('head').append(`<style>${style}</style>`);
    }

    // Confetti effect
    function startConfetti() {
        const canvas = document.createElement('canvas');
        canvas.id = 'confetti-canvas';
        Object.assign(canvas.style, {
            position: 'fixed',
            top: 0,
            left: 0,
            pointerEvents: 'none',
            width: '100%',
            height: '100%',
            zIndex: 9999
        });
        document.body.appendChild(canvas);

        const ctx = canvas.getContext('2d');
        const W = window.innerWidth;
        const H = window.innerHeight;
        canvas.width = W;
        canvas.height = H;

        const confetti = [];
        const colors = ['#ff4081', '#ffd740', '#69f0ae', '#40c4ff', '#ffab40'];
        for (let i = 0; i < 120; i++) {
            confetti.push({
                x: Math.random() * W,
                y: Math.random() * H - H,
                r: Math.random() * 6 + 4,
                d: Math.random() * 20 + 10,
                color: colors[Math.floor(Math.random() * colors.length)],
                tilt: Math.random() * 10 - 10
            });
        }

        function drawConfetti() {
            ctx.clearRect(0, 0, W, H);
            confetti.forEach(c => {
                ctx.beginPath();
                ctx.fillStyle = c.color;
                ctx.moveTo(c.x + c.tilt + c.r, c.y);
                ctx.lineTo(c.x + c.tilt, c.y + c.r);
                ctx.lineTo(c.x + c.tilt - c.r, c.y);
                ctx.fill();
            });
            updateConfetti();
        }

        function updateConfetti() {
            confetti.forEach(c => {
                c.y += 2 + Math.random() * 2;
                c.x += Math.sin(c.d);
                c.tilt += Math.random() * 0.5 - 0.25;
                if (c.y > H) {
                    c.y = -10;
                    c.x = Math.random() * W;
                }
            });
        }

        const interval = setInterval(drawConfetti, 30);
        setTimeout(() => {
            clearInterval(interval);
            canvas.remove();
        }, 8000);
    }
});