User:Tgr/conference-helper.js

From Wikimania

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.
/**
 * Various functionality for on-wiki conference pages:
 * - show <time> elements in the user's local time
 * - align time zone names with the used time zone
 * - highlight events currently in progress
 *
 * 1) Show <time> elements in the user's local time: the content of any <time>
 *    elements with one of the 'zonestamp', 'tpl-zonestamp' 'lt-time' classes
 *    will be replaced with the element's datetime attribute, converted to the
 *    user's local timezone (as defined by the browser) and formatted as HH:mm.
 *    If the element has an 'end-datetime' data attribute, it will show both,
 *    formatted as HH:mm - HH:mm.
 *    The {{zonestamp}} template can be used to generate such elements, with a
 *    no-JS fallback.
 *
 * 2) Align time zone names with the used time zone: the content of elements
 *    with an 'lt-timezone' class will be replaced with the short name of the
 *    user's timezone. The intention with this is to write
 *    <span class="lt-timezone">UTC</span> and then the times and time zones
 *    on the page will stay consistent whether the user has enabled this
 *    helper or not.
 *
 * 3) Highlight events currently in progress: any elements with a
 *    'lt-highlight-if-now' class and 'start' and 'end' data attributes will get
 *    an additional 'lt-highlighted' class if the current time is between the
 *    given start and end timestamps. Alternatively, 'lt-highlight-td-if-now'
 *    will result in the closest <td> parent getting 'lt-highlighted'.
 *    It also adds a 'lt-current' id attribute to the first such element.
 *
 * Test page: https://wikimania.wikimedia.org/wiki/User:Tgr/conference-helper/test
 */
( function () {
	var timezone,
		highlightTimer,
		fmt = Intl.DateTimeFormat( Navigator.language, { timeStyle: 'short' } );

	timezone = new Intl.DateTimeFormat( Navigator.language, { timeZoneName: 'short' } )
		.formatToParts( new Date() )
		.filter( function( el ) {
			return el.type === 'timeZoneName';
		} ).map( function( el ) {
			return el.value;
		} )[0] || null;

	if ( !timezone ) {
		return;
	}

	function localizeTimeElements() {
		$( 'time.zonestamp, time.tpl-zonestamp, time.lt-time' ).each( function ( i ) {
			var text = '',
				$time = $( this ),
				startText = $time.attr( 'datetime' ),
				endText = $time.data( 'end-datetime' ),
				start = new Date( startText ),
				end = endText ? new Date( endText ) : null;
			
			if ( isNaN( start ) ) {
				// could not parse the date, bail out
				mw.log.error( 'Invalid start date #%d: %s', i, startText );
				$time.addClass( 'error' );
				return;
			} else if ( isNaN( end ) ) {
				mw.log.error( 'Invalid end date #%d: %s', i, startText );
				$time.addClass( 'error' );
				return;
			} else if ( end && ( end < start ) ) {
				mw.log.error( 'Invalid range #%d: %s - %s', i, startText, endText );
				$time.addClass( 'error' );
				return;
			}

			if ( end ) {
				text = fmt.formatRange( start, end );
			} else {
				text = fmt.format( start );
			}
			$time.text( text );
		} );
	}

	function localizeTimezone() {
		$( '.lt-timezone' ).each( function () {
			var $timezone = $( this );
			
			$timezone.text( timezone );
		} );
	}

	function highlightCurrent() {
		var firstCurrentFound = false;
		
		$( '.lt-highlight-if-now, .lt-highlight-td-if-now' ).each( function () {
			var $this = $( this ),
				$target = $( [] ),
				start = new Date( $this.data( 'start' ) ),
				end = new Date( $this.data( 'end' ) ),
				now = new Date(),
				current = end && start <= now && now <= end;

			if ( isNaN( start ) || isNaN( end ) ) {
				return;
			}

			if ( $this.hasClass( 'lt-highlight-if-now' ) ) {
				$target = $target.add( $this );
			}
			if ( $this.hasClass( 'lt-highlight-td-if-now' ) ) {
				$target = $target.add( $this.closest( 'td' ) );
			}

			$target.toggleClass( 'lt-highlighted', current );
			if ( current && !firstCurrentFound && $target.length ) {
				$( '#lt-current' ).attr( 'id', null );
				$target.last().attr( 'id', 'lt-current' );
			}
		} );
	}

	mw.hook( 'wikipage.content' ).add( function () {
		localizeTimeElements();
		localizeTimezone();
		highlightCurrent();
		if ( !highlightTimer ) {
			highlightTimer = setInterval( function () {
				highlightCurrent();
			}, 60 * 1000 );
		}
	} );
} )();