MediaWiki:Gadget-site-deploycal.js

From Wikitech

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)
  • Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: 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 * 100 );

			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 ) ),
						'&ndash;',
						$('<time>').addClass('deploycal-endtime').attr('datetime', endTime).text( formatTime( endTime ) ),
						'&nbsp;',
						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);
			}
		});
	}
}());