MediaWiki:Gadget-firstedit-test.js
Appearance
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);
}
});