MediaWiki:Gadget-fix-schedule-times.js

From WikiConference North America
Revision as of 05:36, 22 October 2021 by Enterprisey (talk | contribs) (further usage notes)
Jump to navigation Jump to search

Note: After saving, 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: Go to Menu → Settings (Opera → Preferences on a Mac) and then to Privacy & security → Clear browsing data → Cached images and files.
// vim: ts=4 sw=4 et ai si
// HOW TO USE: you really should know at least a little javascript for this... (find someone who does if not). Copy this page to your wiki. The title must be the same. Or you can use a different title, but it MUST start with "MediaWiki:" and end in ".js", and you'll have to edit the snippet. Then, on your wiki's MediaWiki:Common.js page (VERY DANGEROUS), determine the page ID of your schedule (click "Page information" under "Tools" in the sidebar), then insert this snippet onto that MediaWiki:Common.js page (don't include the slash and star on their own lines at the beginning and end... actually, you should only be doing this if you have at least minimal JavaScript knowledge...). Make sure to replace BOTH occurrences of REPLACE_ME_WITH_THE_PAGE_ID_OF_YOUR_SCHEDULE_PAGE with that page ID you found. The snippet loads in this gadget and also automatically reloads the page when the schedule changes. (It checks every 2 minutes.) You must also update CONFERENCE_DAYS and WRITTEN_SCHEDULE_OFFSET_SECONDS in this page with the appropriate values for your conference. You will also want to, in your schedule, include a link to the schedule page itself but with the text ?dontFixSchedule at the end of the URL right after the title of the schedule page. That disables these two features and is useful when you're working on the schedule itself. The schedule is assumed to USE 24-HOUR TIME with TWO DIGITS ALWAYS FOR THE HOUR. If you DON'T DO THAT, it WILL NOT WORK. This script was originally created for WikiConference North America 2021. Please see https://wikiconference.org/wiki/2021/Schedule for an example of a schedule that is formatted for use with this script. Note that events with no ending times are indicated like this: 12:34+ with a plus sign after just one time. Although note that due to this script running, you won't see the original formatting unless you edit the page or append ?dontFixSchedule to the URL!
/* DELETE THIS LINE WHEN COPYING. This is the start of the stuff that goes in MediaWiki:Common.js
if ( ( mw.config.get( "wgArticleId" ) === REPLACE_ME_WITH_THE_PAGE_ID_OF_YOUR_SCHEDULE_PAGE ) && ( mw.config.get( 'wgAction' ) === 'view' ) ) {
mw.loader.load( '/index.php?title=MediaWiki%3AGadget-fix-schedule-times.js&action=raw&ctype=text/javascript' );
mw.loader.using( ['mediawiki.api'], function () {
if(window.location.search.indexOf("dontFixSchedule")>=0)return;
    var api = new mw.Api();
    window.setInterval(function(){
        api.get({
            action:'query',
            pageids:REPLACE_ME_WITH_THE_PAGE_ID_OF_YOUR_SCHEDULE_PAGE,
            prop:'revisions',
            rvprop:'ids',
            uselang:'content',
            format:'json',
            formatversion:'2'
        }).then(function(res){
            var revid = res.query.pages[0].revisions[0].revid;
            if(revid !== mw.config.get('wgRevisionId')){
                mw.notify('Schedule changed. Reloading.');
                window.location.reload();
            }
        });
    },1000 * 60 * 2); // check every 2 minutes. if you wanted 5 minutes, replace the 2 with a 5.
});
}
DELETE THIS LINE WHEN COPYING. This is the end of the stuff that goes in MediaWiki:Common.js */
$.when(
    $.ready
).then( function () {
// quit immediately if dontFixSchedule is in the URL after the ?
if(window.location.search.indexOf("dontFixSchedule")>=0)return;

    // Configuration
    var CONFERENCE_DAYS = {
        "Friday, October 8": Date.UTC( 2021, /* month index, not month number! */ 9, 8 ),
        "Saturday, October 9": Date.UTC( 2021, 9, 9 ),
        "Sunday, October 10": Date.UTC( 2021, 9, 10 )
    };
    var WRITTEN_SCHEDULE_OFFSET_SECONDS = -4 * 3600; // written schedule for WCNA 2021 is Eastern Time which is UTC-4

    // Other constants
    var TIME_REGEX = /(\d\d):(\d\d)( - (\d\d):(\d\d)|\+)/;

    function pl( n ) { return ( Math.floor( n ) === 1 ) ? "" : "s"; }

    function textDescribingRelativeTime( minutesUntilStart, minutesUntilEnd, countdownDuringEvent ) {
        if ( minutesUntilStart > /* one day */ 1440 ) {
            return "in " + Math.floor( minutesUntilStart / 1440 ).toFixed( 0 ) + " day" + pl( minutesUntilStart / 1440 );
        } else if ( minutesUntilStart > /* 3 hours */ 180 ) {
            return "in " + Math.floor( minutesUntilStart / 60 ).toFixed( 0 ) + " hour" + pl( minutesUntilStart / 60 );
        } else if ( minutesUntilStart > 120 ) {
            return "in 2 hours and " + Math.floor( minutesUntilStart % 60 ).toFixed( 0 ) + " minute" + pl( minutesUntilStart % 60 );
        } else if ( minutesUntilStart > 60 ) {
            return "in an hour and " + Math.floor( minutesUntilStart % 60 ).toFixed( 0 ) + " minute" + pl( minutesUntilStart % 60 );
        } else if ( minutesUntilStart > 1 ) {
            return "in " + Math.floor( minutesUntilStart ).toFixed( 0 ) + " minute" + pl( minutesUntilStart );
        } else if ( minutesUntilStart > 0 ) {
            return "in less than a minute";
        } else if ( minutesUntilStart > -1 ) {
            return "started just now";
        } else if ( minutesUntilStart > -5 ) {
            return "started " + Math.floor( -minutesUntilStart ).toFixed( 0 ) + " minute" + pl( -minutesUntilStart ) + " ago";
        } else if ( minutesUntilEnd > 1 ) {
            if ( countdownDuringEvent ) {
                return "ends in " + Math.floor( minutesUntilEnd ).toFixed( 0 ) + " minute" + pl( minutesUntilEnd );
            } else {
                if ( minutesUntilStart > -60 ) {
                    return "started " + Math.floor( -minutesUntilStart ).toFixed( 0 ) + " minute" + pl( -minutesUntilStart ) + " ago";
                } else {
                    return "started " + Math.floor( -minutesUntilStart / 60 ).toFixed( 0 ) + " hour" + pl( -minutesUntilStart / 60 ) + " ago";
                }
            }
        } else if ( minutesUntilEnd > 0 ) {
            return "ends in one minute";
        } else if ( minutesUntilEnd > -1 ) {
            return "ended just now";
        } else if ( minutesUntilEnd > -60 ) {
            return "ended " + Math.floor( -minutesUntilEnd ).toFixed( 0 ) + " minute" + pl( -minutesUntilEnd ) + " ago";
        } else if ( minutesUntilEnd > -1440 ) {
            return "ended " + Math.floor( -minutesUntilEnd / 60 ).toFixed( 0 ) + " hour" + pl( -minutesUntilEnd / 60 ) + " ago";
        } else {
            return "ended " + Math.floor( -minutesUntilEnd / 1440 ).toFixed( 0 ) + " day" + pl( -minutesUntilEnd / 1440 ) + " ago";
        }
    }

    function formatRelativeTime( minutesUntilStart, minutesUntilEnd, countdownDuringEvent ) {
        var text = textDescribingRelativeTime( minutesUntilStart, minutesUntilEnd, countdownDuringEvent );
        if ( minutesUntilStart > 0 ) {
            return "(" + text + ")";
        } else if ( minutesUntilEnd > 0 ) {
            return "<b>(" + text + ")</b>";
        } else {
            return "<i>(" + text + ")</i>";
        }
    }

    function formatTime( unixTimestamp ) {
        return new Date( unixTimestamp * 1000 ).toLocaleTimeString().replace( /(\d\d?:\d\d):00/, "$1" ).replace( / /g, "&nbsp;" );
    }

    function makeHtml( originalTimeMatch, tableCell ) {
        var dayHeading = $( tableCell ).closest( "table" ).prev().prev(); // skip icon key
        var dayHeadingText = dayHeading.find( ".mw-headline" ).text();
        var baseDateUnix = Math.floor( CONFERENCE_DAYS[dayHeadingText] / 1000 );
        var isSoftEndingTime = !originalTimeMatch[4]; // if there's no group-4 match, no ending time was specified

        // First, get the actual timestamps
        var startTimeUnix = baseDateUnix + parseInt( originalTimeMatch[1], 10 ) * 3600 +
                parseInt( originalTimeMatch[2], 10 ) * 60 - WRITTEN_SCHEDULE_OFFSET_SECONDS;
        var endTimeUnix = isSoftEndingTime ? ( startTimeUnix + 3600 * 5 ) : ( baseDateUnix + parseInt( originalTimeMatch[4], 10 ) * 3600 +
                parseInt( originalTimeMatch[5], 10 ) * 60 - WRITTEN_SCHEDULE_OFFSET_SECONDS );

        // Now, get some text like "Starts in 10 minutes".
        var nowUnix = Math.floor( new Date().getTime() / 1000 );
        var relativeTimeFormatted = formatRelativeTime(
            ( startTimeUnix - nowUnix ) / 60,
            ( endTimeUnix - nowUnix ) / 60,
            /* countdownDuringEvent */ !isSoftEndingTime,
        );
        return formatTime( startTimeUnix ) + ( isSoftEndingTime ? "+" : ( " - " + formatTime( endTimeUnix ) ) ) + "<br />" + relativeTimeFormatted;
    }

    $( "small" ).each( function () {
        if ( this.textContent === "(US Eastern Time)" ) {
try{
            $( this ).text( "In " + Intl.DateTimeFormat().resolvedOptions().timeZone.replace( /_/g, " " ) + " time" );
}catch(e){
console.error(e);
$(this).text("In your local timezone (hover to see Eastern U.S. time)");
        }
        }
    } );

    $( "td" ).each( function () {
        var match = TIME_REGEX.exec( this.textContent );
        if ( match ) {
            this.innerHTML = this.innerHTML.replace( match[0], "<span class='fixed' title='"+match[0]+" Eastern Time' data-original='" + match[0] + "'>" + match[0] + "</span>" );
        }
    } );

    function go() {
        $( "span.fixed" ).each( function () {
            $( this ).html( makeHtml( TIME_REGEX.exec( this.dataset.original ), this.parentNode ) );
        } );
    }

    go();
    window.setInterval( go, 60 * 1000 );
} );