MediaWiki:Common.js: Difference between revisions
Appearance
Sonja says (talk | contribs) No edit summary |
Sonja says (talk | contribs) No edit summary |
||
| Line 1: | Line 1: | ||
/* Any JavaScript here will be loaded for all users on every page load. */ | /* Any JavaScript here will be loaded for all users on every page load. */ | ||
// | /* ========================================================= | ||
$(document).ready(function() { | Tabs (unchanged) | ||
========================================================= */ | |||
$(document).ready(function () { | |||
$('.wiki-tabs-container').each(function () { | |||
var $container = $(this); | |||
var $firstButton = $container.find('.wiki-tab-button').first(); | |||
var firstTabId = $firstButton.data('tab'); | |||
$firstButton.addClass('active'); | |||
$container.find('#' + firstTabId).addClass('active').show(); | |||
}); | |||
$('.wiki-tab-button').on('click', function () { | |||
var $button = $(this); | |||
var tabId = $button.data('tab'); | |||
var $container = $button.closest('.wiki-tabs-container'); | |||
$container.find('.wiki-tab-button').removeClass('active'); | |||
$container.find('.wiki-tab-pane').removeClass('active').hide(); | |||
$button.addClass('active'); | |||
$container.find('#' + tabId).addClass('active').show(); | |||
}); | |||
}); | }); | ||
/* ========================================================= | |||
Sticky Header Widgets (Discord + Clock | Game Date | Countdown) | |||
CLEAN + FIXED: | |||
- No CSS inside JS (your old paste had CSS at the bottom of JS) | |||
- No spacing hacks (spacing should be CSS) | |||
- One shared container for the 3 widgets | |||
========================================================= */ | |||
(function () { | |||
const DISCORD_URL = 'https://discord.gg/t2Rp2dRvze'; | |||
// Countdown target: Sat, 18 Apr 2026 00:00 UTC | |||
const COUNTDOWN_TARGET_UTC = Date.parse('2026-04-18T00:00:00Z'); | |||
// Clock timezone (same for everyone) | |||
const CLOCK_TIMEZONE = 'Etc/UTC'; // change if desired | |||
// Game time anchor (real UTC -> game time) | |||
// At 2026-02-19 14:00:00 UTC, game time was Jan 21 YR2 | |||
const GAME_ANCHOR_REAL_UTC = Date.parse('2026-02-19T14:00:00Z'); | |||
const GAME_ANCHOR_MONTH_INDEX = 0; // Jan | |||
const GAME_ANCHOR_DAY = 21; // 1..24 | |||
const GAME_ANCHOR_YEAR = 2; // YR2 | |||
const GAME_MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul']; | |||
const pad2 = (n) => String(n).padStart(2, '0'); | |||
function ensureDiscordLink(icons) { | |||
if (document.getElementById('custom-sticky-link')) return; | if (document.getElementById('custom-sticky-link')) return; | ||
const link = document.createElement('a'); | const link = document.createElement('a'); | ||
link.id = 'custom-sticky-link'; | link.id = 'custom-sticky-link'; | ||
link.href = | link.href = DISCORD_URL; | ||
link.target = '_blank'; | link.target = '_blank'; | ||
link.className = 'cdx-button cdx-button--fake-button cdx-button--fake-button--enabled cdx-button--weight-quiet'; | link.className = | ||
'cdx-button cdx-button--fake-button cdx-button--fake-button--enabled cdx-button--weight-quiet'; | |||
link.textContent = 'Join Us on Discord!'; | link.textContent = 'Join Us on Discord!'; | ||
link.style.marginLeft = '10px'; | link.style.marginLeft = '10px'; | ||
link.style.fontWeight = 'bold'; | link.style.fontWeight = 'bold'; | ||
icons.prepend(link); | icons.prepend(link); | ||
} | } | ||
// | // Container that will hold ONLY the three widgets (not Discord, not default icons) | ||
function getStickyTimeContainer() { | |||
const icons = document.querySelector('.vector-sticky-header-icons'); | const icons = document.querySelector('.vector-sticky-header-icons'); | ||
if (!icons) return; | if (!icons) return null; | ||
let mid = document.getElementById('sticky-time-widgets'); | |||
if (mid) return mid; | |||
// Make sure Discord is created first so we can insert after it | |||
ensureDiscordLink(icons); | |||
mid = document.createElement('span'); | |||
mid.id = 'sticky-time-widgets'; | |||
mid.className = 'sticky-time-widgets'; | |||
const discord = document.getElementById('custom-sticky-link'); | |||
if (discord && discord.parentNode === icons) { | |||
discord.insertAdjacentElement('afterend', mid); | |||
} else { | |||
icons.prepend(mid); | |||
} | |||
return mid; | |||
} | |||
function ensureClock(mid) { | |||
if (document.getElementById('sticky-clock')) return; | if (document.getElementById('sticky-clock')) return; | ||
const wrap = document.createElement('span'); | const wrap = document.createElement('span'); | ||
wrap.id = 'sticky-clock'; | wrap.id = 'sticky-clock'; | ||
wrap.className = 'sticky-clock'; | wrap.className = 'sticky-clock header-widget'; | ||
const label = document.createElement('span'); | const label = document.createElement('span'); | ||
label.className = ' | label.className = 'header-widget__label'; | ||
label.textContent = 'UTC:'; | label.textContent = (CLOCK_TIMEZONE === 'Etc/UTC') ? 'UTC:' : 'Time:'; | ||
const value = document.createElement('span'); | const value = document.createElement('span'); | ||
value.className = ' | value.className = 'header-widget__value'; | ||
value.dataset.role = 'clock'; | |||
wrap.appendChild(label); | wrap.appendChild(label); | ||
wrap.appendChild(value); | wrap.appendChild(value); | ||
mid.appendChild(wrap); | |||
} | |||
const | function ensureGame(mid) { | ||
if (document.getElementById('sticky-game-time')) return; | |||
const wrap = document.createElement('span'); | |||
wrap.id = 'sticky-game-time'; | |||
wrap.className = 'sticky-game-time header-widget'; | |||
const | const value = document.createElement('span'); | ||
value.className = 'header-widget__value'; | |||
value.dataset.role = 'game'; | |||
wrap.appendChild(value); | |||
mid.appendChild(wrap); | |||
} | |||
function ensureCountdown(mid) { | |||
if (document.getElementById('sticky-countdown')) return; | |||
const wrap = document.createElement('span'); | |||
wrap.id = 'sticky-countdown'; | |||
wrap.className = 'sticky-countdown header-widget'; | |||
const label = document.createElement('span'); | |||
label.className = 'header-widget__label'; | |||
label.textContent = 'Age ends in:'; | |||
const value = document.createElement('span'); | |||
value.className = 'header-widget__value'; | |||
value.dataset.role = 'countdown'; | |||
' | |||
' | |||
wrap.appendChild(label); | |||
wrap.appendChild(value); | |||
mid.appendChild(wrap); | |||
} | } | ||
function | function formatRemaining(ms) { | ||
if (ms <= 0) return '00:00:00'; | |||
const totalSeconds = Math.floor(ms / 1000); | |||
const days = Math.floor(totalSeconds / 86400); | |||
const hours = Math.floor((totalSeconds % 86400) / 3600); | |||
const minutes = Math.floor((totalSeconds % 3600) / 60); | |||
const seconds = totalSeconds % 60; | |||
if (days > 0) return `${days}d ${pad2(hours)}:${pad2(minutes)}:${pad2(seconds)}`; | |||
return `${pad2(hours)}:${pad2(minutes)}:${pad2(seconds)}`; | |||
} | } | ||
function | function computeGameString(nowMs) { | ||
const hoursPassed = Math.floor((nowMs - GAME_ANCHOR_REAL_UTC) / 3600000); | |||
// Day increments every hour | |||
const dayIndex = (GAME_ANCHOR_DAY - 1) + hoursPassed; | |||
const dayNumber = ((dayIndex % 24) + 24) % 24 + 1; | |||
// Month increments every 24 hours | |||
const daysPassed = Math.floor(dayIndex / 24); | |||
// Month cycles (Jan..Jul) | |||
const totalMonthIndex = GAME_ANCHOR_MONTH_INDEX + daysPassed; | |||
const monthIndex = ((totalMonthIndex % 7) + 7) % 7; | |||
// Year increments every 7 real days | |||
const yearsPassed = Math.floor(totalMonthIndex / 7); | |||
const year = GAME_ANCHOR_YEAR + yearsPassed; | |||
return `Current Game Date: ${GAME_MONTHS[monthIndex]} ${dayNumber} YR${year}`; | |||
} | } | ||
const clockFmt = new Intl.DateTimeFormat('en-GB', { | |||
timeZone: CLOCK_TIMEZONE, | |||
hour: '2-digit', | |||
} | minute: '2-digit', | ||
second: '2-digit', | |||
hour12: false | |||
}); | |||
function updateAll() { | |||
const now = Date.now(); | |||
const | |||
const clockVal = document.querySelector('#sticky-time-widgets [data-role="clock"]'); | |||
if (clockVal) clockVal.textContent = clockFmt.format(new Date(now)); | |||
const | const gameVal = document.querySelector('#sticky-time-widgets [data-role="game"]'); | ||
if (gameVal) gameVal.textContent = computeGameString(now); | |||
const | const countdownVal = document.querySelector('#sticky-time-widgets [data-role="countdown"]'); | ||
if (countdownVal) countdownVal.textContent = formatRemaining(COUNTDOWN_TARGET_UTC - now); | |||
} | |||
if ( | |||
mw.hook('wikipage.content').add(function () { | |||
mw.hook('wikipage.content').add(function () { | |||
const icons = document.querySelector('.vector-sticky-header-icons'); | const icons = document.querySelector('.vector-sticky-header-icons'); | ||
if (!icons) return; | if (!icons) return; | ||
const mid = getStickyTimeContainer(); | const mid = getStickyTimeContainer(); | ||
if (mid) | if (!mid) return; | ||
ensureClock(mid); | |||
ensureGame(mid); | |||
ensureCountdown(mid); | |||
// Start update loop once | |||
if (!window.__stickyTimeWidgetsStarted) { | |||
window.__stickyTimeWidgetsStarted = true; | |||
updateAll(); | |||
setInterval(updateAll, 1000); | |||
} else { | |||
// If navigating, ensure values refresh immediately | |||
updateAll(); | |||
} | |||
}); | |||
})(); | |||
Revision as of 10:53, 19 February 2026
/* Any JavaScript here will be loaded for all users on every page load. */
/* =========================================================
Tabs (unchanged)
========================================================= */
$(document).ready(function () {
$('.wiki-tabs-container').each(function () {
var $container = $(this);
var $firstButton = $container.find('.wiki-tab-button').first();
var firstTabId = $firstButton.data('tab');
$firstButton.addClass('active');
$container.find('#' + firstTabId).addClass('active').show();
});
$('.wiki-tab-button').on('click', function () {
var $button = $(this);
var tabId = $button.data('tab');
var $container = $button.closest('.wiki-tabs-container');
$container.find('.wiki-tab-button').removeClass('active');
$container.find('.wiki-tab-pane').removeClass('active').hide();
$button.addClass('active');
$container.find('#' + tabId).addClass('active').show();
});
});
/* =========================================================
Sticky Header Widgets (Discord + Clock | Game Date | Countdown)
CLEAN + FIXED:
- No CSS inside JS (your old paste had CSS at the bottom of JS)
- No spacing hacks (spacing should be CSS)
- One shared container for the 3 widgets
========================================================= */
(function () {
const DISCORD_URL = 'https://discord.gg/t2Rp2dRvze';
// Countdown target: Sat, 18 Apr 2026 00:00 UTC
const COUNTDOWN_TARGET_UTC = Date.parse('2026-04-18T00:00:00Z');
// Clock timezone (same for everyone)
const CLOCK_TIMEZONE = 'Etc/UTC'; // change if desired
// Game time anchor (real UTC -> game time)
// At 2026-02-19 14:00:00 UTC, game time was Jan 21 YR2
const GAME_ANCHOR_REAL_UTC = Date.parse('2026-02-19T14:00:00Z');
const GAME_ANCHOR_MONTH_INDEX = 0; // Jan
const GAME_ANCHOR_DAY = 21; // 1..24
const GAME_ANCHOR_YEAR = 2; // YR2
const GAME_MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul'];
const pad2 = (n) => String(n).padStart(2, '0');
function ensureDiscordLink(icons) {
if (document.getElementById('custom-sticky-link')) return;
const link = document.createElement('a');
link.id = 'custom-sticky-link';
link.href = DISCORD_URL;
link.target = '_blank';
link.className =
'cdx-button cdx-button--fake-button cdx-button--fake-button--enabled cdx-button--weight-quiet';
link.textContent = 'Join Us on Discord!';
link.style.marginLeft = '10px';
link.style.fontWeight = 'bold';
icons.prepend(link);
}
// Container that will hold ONLY the three widgets (not Discord, not default icons)
function getStickyTimeContainer() {
const icons = document.querySelector('.vector-sticky-header-icons');
if (!icons) return null;
let mid = document.getElementById('sticky-time-widgets');
if (mid) return mid;
// Make sure Discord is created first so we can insert after it
ensureDiscordLink(icons);
mid = document.createElement('span');
mid.id = 'sticky-time-widgets';
mid.className = 'sticky-time-widgets';
const discord = document.getElementById('custom-sticky-link');
if (discord && discord.parentNode === icons) {
discord.insertAdjacentElement('afterend', mid);
} else {
icons.prepend(mid);
}
return mid;
}
function ensureClock(mid) {
if (document.getElementById('sticky-clock')) return;
const wrap = document.createElement('span');
wrap.id = 'sticky-clock';
wrap.className = 'sticky-clock header-widget';
const label = document.createElement('span');
label.className = 'header-widget__label';
label.textContent = (CLOCK_TIMEZONE === 'Etc/UTC') ? 'UTC:' : 'Time:';
const value = document.createElement('span');
value.className = 'header-widget__value';
value.dataset.role = 'clock';
wrap.appendChild(label);
wrap.appendChild(value);
mid.appendChild(wrap);
}
function ensureGame(mid) {
if (document.getElementById('sticky-game-time')) return;
const wrap = document.createElement('span');
wrap.id = 'sticky-game-time';
wrap.className = 'sticky-game-time header-widget';
const value = document.createElement('span');
value.className = 'header-widget__value';
value.dataset.role = 'game';
wrap.appendChild(value);
mid.appendChild(wrap);
}
function ensureCountdown(mid) {
if (document.getElementById('sticky-countdown')) return;
const wrap = document.createElement('span');
wrap.id = 'sticky-countdown';
wrap.className = 'sticky-countdown header-widget';
const label = document.createElement('span');
label.className = 'header-widget__label';
label.textContent = 'Age ends in:';
const value = document.createElement('span');
value.className = 'header-widget__value';
value.dataset.role = 'countdown';
wrap.appendChild(label);
wrap.appendChild(value);
mid.appendChild(wrap);
}
function formatRemaining(ms) {
if (ms <= 0) return '00:00:00';
const totalSeconds = Math.floor(ms / 1000);
const days = Math.floor(totalSeconds / 86400);
const hours = Math.floor((totalSeconds % 86400) / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = totalSeconds % 60;
if (days > 0) return `${days}d ${pad2(hours)}:${pad2(minutes)}:${pad2(seconds)}`;
return `${pad2(hours)}:${pad2(minutes)}:${pad2(seconds)}`;
}
function computeGameString(nowMs) {
const hoursPassed = Math.floor((nowMs - GAME_ANCHOR_REAL_UTC) / 3600000);
// Day increments every hour
const dayIndex = (GAME_ANCHOR_DAY - 1) + hoursPassed;
const dayNumber = ((dayIndex % 24) + 24) % 24 + 1;
// Month increments every 24 hours
const daysPassed = Math.floor(dayIndex / 24);
// Month cycles (Jan..Jul)
const totalMonthIndex = GAME_ANCHOR_MONTH_INDEX + daysPassed;
const monthIndex = ((totalMonthIndex % 7) + 7) % 7;
// Year increments every 7 real days
const yearsPassed = Math.floor(totalMonthIndex / 7);
const year = GAME_ANCHOR_YEAR + yearsPassed;
return `Current Game Date: ${GAME_MONTHS[monthIndex]} ${dayNumber} YR${year}`;
}
const clockFmt = new Intl.DateTimeFormat('en-GB', {
timeZone: CLOCK_TIMEZONE,
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
});
function updateAll() {
const now = Date.now();
const clockVal = document.querySelector('#sticky-time-widgets [data-role="clock"]');
if (clockVal) clockVal.textContent = clockFmt.format(new Date(now));
const gameVal = document.querySelector('#sticky-time-widgets [data-role="game"]');
if (gameVal) gameVal.textContent = computeGameString(now);
const countdownVal = document.querySelector('#sticky-time-widgets [data-role="countdown"]');
if (countdownVal) countdownVal.textContent = formatRemaining(COUNTDOWN_TARGET_UTC - now);
}
mw.hook('wikipage.content').add(function () {
const icons = document.querySelector('.vector-sticky-header-icons');
if (!icons) return;
const mid = getStickyTimeContainer();
if (!mid) return;
ensureClock(mid);
ensureGame(mid);
ensureCountdown(mid);
// Start update loop once
if (!window.__stickyTimeWidgetsStarted) {
window.__stickyTimeWidgetsStarted = true;
updateAll();
setInterval(updateAll, 1000);
} else {
// If navigating, ensure values refresh immediately
updateAll();
}
});
})();