MediaWiki:Gadget-site-deploycal.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.
/**
* deploycal: Localize the deployment calendar to your local time
*/
(function() {
var jumpEl, $jumpBtn,
hashTarget = '#!/deploycal/current',
futureItems = [];
/** @param {Date} time */
function formatTime(time) {
var hours = '0' + time.getHours();
var mins = '0' + time.getMinutes();
return hours.slice(-2) + ':' + mins.slice(-2);
}
function scrollToCurrent() {
var current = $('.deploycal-event-now')[0] || $('.deploycal-event-past').last()[0] || $('.deploycal-eventcard-row')[0];
if (current && current.scrollIntoView) {
if (current.id && history.pushState) {
history.pushState(null, '', '#' + current.id);
}
current.scrollIntoView({ block: 'start', behavior: 'smooth' });
}
}
function updateRelativeMarker() {
$.each(futureItems.slice(), function (i, el) {
var $children = $(el).children('time'),
startTime = new Date($children.eq(0).attr('datetime')).getTime(),
endTime = new Date($children.eq(1).attr('datetime')).getTime(),
$row = $(el).parent().parent(),
now = Date.now(),
startDiff = ( startTime - now ) / 1000,
relText,
diffHours,
// 5 minutes ahead
nowNotif = now + ( 5 * 60 * 1000 );
if (!$.contains(document.body, el)) {
// Remove, no longer attached (e.g. previous content)
futureItems.splice(i, 1);
} else if (endTime < now) {
$row.removeClass('deploycal-event-now').addClass('deploycal-event-past');
// Remove
futureItems.splice(i, 1);
} else if (startTime < nowNotif) {
$row.addClass('deploycal-event-now');
}
// Update relative time
// - soon: 30 minutes or less
// - 1 hour: 30-90 minutes (0.5-1.5h)
// - N hours: 1.5 - 23 hours
if (startDiff > 0 && startDiff < (23 * 3600)) {
if (startDiff < 1800) {
relText = 'starting soon!';
} else {
diffHours = Math.max(1, Math.round(startDiff / 3600));
if (diffHours === 1) {
relText = '1 hour from now';
} else {
relText = diffHours + ' hours from now';
}
}
$row.find('.deploycal-time-rel').text(relText)
} else if (startDiff < 0 && endTime > now) {
$row.find('.deploycal-time-rel').text('happening now');
} else {
// Remove
$row.find('.deploycal-time-rel').remove();
}
});
if (futureItems.length) {
setTimeout(function () {
if (window.requestAnimationFrame) {
requestAnimationFrame(updateRelativeMarker);
} else {
updateRelativeMarker();
}
}, 60 * 1000);
}
}
function handleCalendarPage($content) {
// In case of live preview or post-edit from from VisualEditor,
// this hook may fire more than once during a browsing context.
var previousItemCount = futureItems.length;
$content.find('.deploycal-time-utc').each( function( idx, el ) {
var localTz, $children, startTime, endTime, sfTz, tzStr, $cell, $row, now, startDiff;
$children = $(el).children('time');
startTime = new Date($children.eq(0).attr('datetime'));
localTz = startTime.getTimezoneOffset() / -60;
endTime = new Date($children.eq(1).attr('datetime'));
$cell = $(el).parent();
$row = $cell.parent();
now = Date.now();
startDiff = ( startTime - now ) / 1000;
if (endTime < now) {
$row.addClass('deploycal-event-past');
} else {
futureItems.push(el);
}
sfTz = /([\+|-][0-9]+):([0-9]{2})/.exec( $(el).siblings('.deploycal-time-sf').children('time').eq(1).attr('datetime') );
sfTz = ( sfTz[1] * 60 + sfTz[2] * 1 ) / 60;
// Add local time if timezone is not SF or UTC
if ( localTz !== sfTz && localTz !== 0 ) {
tzStr = 'UTC' + ((localTz > 0) ? '+' + localTz : localTz);
$cell.append(
$('<br>'),
$('<span>').addClass('deploycal-time-local').append(
$('<time>').addClass('deploycal-starttime').attr('datetime', startTime).text( formatTime( startTime ) ),
'–',
$('<time>').addClass('deploycal-endtime').attr('datetime', endTime).text( formatTime( endTime ) ),
' ',
tzStr
)
);
}
// Add relative time if event is current or starts within 23 hours
if ((startDiff < 0 && endTime > now) || (startDiff > 0 && startDiff < (23 * 3600))) {
$cell.append(
$('<br>'),
$('<span>').addClass('deploycal-time-rel')
);
}
});
// Only start a update-marker loop if we haven't started one already
if (!previousItemCount && futureItems.length) {
updateRelativeMarker();
}
// Activate shortcut link to last current event, created by [[Template:Navigation MediaWiki deployment]]
jumpEl = document.querySelector('.mw-parser-output .deploycal-jump');
if (jumpEl) {
$jumpBtn = $('<button class="mw-ui-button">').append($('<a>').attr('href', hashTarget).text(jumpEl.textContent))
.on('click', function (e) {
e.preventDefault();
scrollToCurrent();
});
$(jumpEl).empty().append($jumpBtn).addClass('deploycal-jump-bound');
}
if (location.hash === hashTarget) {
// User navigated to permalink (e.g. from sidebar while on another page)
mw.requestIdleCallback(scrollToCurrent);
}
}
function addSidebarLink() {
var link = document.querySelector('#n-Deployments a');
if (!link) {
return;
}
var link2 = link.cloneNode();
link2.href += hashTarget;
link2.textContent = '[curr]';
link.parentNode.appendChild(document.createTextNode(' \u00a0 '));
link.parentNode.appendChild(link2);
}
function handleInterface() {
mw.requestIdleCallback(addSidebarLink);
}
// For the interface on all page (fires once)
$(handleInterface);
// Optimisation: Limit to [[Deployments]]
if (mw.config.get('wgPageName') === 'Deployments') {
// Whenever content is ready (may fire again, after an edit)
mw.hook('wikipage.content').add(handleCalendarPage);
window.addEventListener('hashchange', function () {
if (location.hash === hashTarget) {
// Do what I mean: User clicked on permalink in sidebar while already on the Deployments page
mw.requestIdleCallback(scrollToCurrent);
}
});
}
}());