MediaWiki:All.js: Difference between revisions

From Inkipedia, the Splatoon wiki
m (Infobox gobbler: Initially select the first game button for the same game as the last button added)
(fix countdown when there is only one challenge in the schedule (thank you Harimaron))
 
(7 intermediate revisions by 2 users not shown)
Line 1: Line 1:
/* Any JavaScript here will be loaded for all users both on desktop and mobile */
/* Any JavaScript here will be loaded for all users both on desktop and mobile */
/* strawpoll.me */
mw.loader.load('//splatoonwiki.org/wiki/MediaWiki:StrawpollIntegrator.js?action=raw&ctype=text/javascript')
/* strawpoll.com */
mw.loader.load('//splatoonwiki.org/wiki/MediaWiki:StrawpollIntegrator2.js?action=raw&ctype=text/javascript')


// ================================================================================
// ================================================================================
Line 12: Line 7:
//Check page specific files
//Check page specific files
$(function () {
$(function () {
    var url = new URL(window.location.href);
var url = new URL(window.location.href);
    var action = url.searchParams.get("action")
var action = url.searchParams.get("action")
    if (action === null || action === "view" || action === "submit") {
if (action === null || action === "view" || action === "submit") {
        mw.loader.using("mediawiki.api", function () {
mw.loader.using("mediawiki.api", function () {
            var skin = mw.config.get("skin"),
var skin = mw.config.get("skin"),
                page = mw.config.get("wgPageName"),
page = mw.config.get("wgPageName"),
                user = mw.config.get("wgUserName");
user = mw.config.get("wgUserName");


            var pages = [
var pages = [
                ['MediaWiki:Common.js/' + page + ".js", "js"],
['MediaWiki:Common.js/' + page + ".js", "js"],
                ['MediaWiki:Common.css/' + page + ".css", "css"],
['MediaWiki:Common.css/' + page + ".css", "css"],
                ['MediaWiki:' + skin + '.js/' + page + ".js", "js"],
['MediaWiki:' + skin + '.js/' + page + ".js", "js"],
                ['MediaWiki:' + skin + '.css/' + page + ".css", "css"]
['MediaWiki:' + skin + '.css/' + page + ".css", "css"]
            ];
];
            if (user != null) pages.push(
if (user != null) pages.push(
                ['User:' + user + '/common.js/' + page + ".js", "js"],
['User:' + user + '/common.js/' + page + ".js", "js"],
                ['User:' + user + '/common.css/' + page + ".css", "css"],
['User:' + user + '/common.css/' + page + ".css", "css"],
                ['User:' + user + '/' + skin + '.js/' + page + ".js", "js"],
['User:' + user + '/' + skin + '.js/' + page + ".js", "js"],
                ['User:' + user + '/' + skin + '.css/' + page + ".css", "css"]
['User:' + user + '/' + skin + '.css/' + page + ".css", "css"]
            );
);
            pages.forEach(function (el) {
pages.forEach(function (el) {
                if (el[1] == "js") {
if (el[1] == "js") {
                    if (new URL(window.location).searchParams.get("disable-page-js") != null) return;
if (new URL(window.location).searchParams.get("disable-page-js") != null) return;
                    mw.loader.load('/w/index.php?title=' + encodeURIComponent(el[0]) + '&action=raw&ctype=text/javascript');
mw.loader.load('/w/index.php?title=' + encodeURIComponent(el[0]) + '&action=raw&ctype=text/javascript');
                }
}
                else {
else {
                    if (new URL(window.location).searchParams.get("disable-page-css") != null) return;
if (new URL(window.location).searchParams.get("disable-page-css") != null) return;
                    mw.loader.load('/w/index.php?title=' + encodeURIComponent(el[0]) + '&action=raw&ctype=text/css', 'text/css');
mw.loader.load('/w/index.php?title=' + encodeURIComponent(el[0]) + '&action=raw&ctype=text/css', 'text/css');
                }
}
            });
});
        });
});
    }
}
});
});


Line 53: Line 48:
jQuery(function($) {
jQuery(function($) {
   if (typeof(disableUsernameReplace) != 'undefined' && disableUsernameReplace)
   if (typeof(disableUsernameReplace) != 'undefined' && disableUsernameReplace)
    return;
return;
    
    
   var username = mw.config.get('wgUserName');
   var username = mw.config.get('wgUserName');
   if (username == null)
   if (username == null)
    return;
return;


   $('.insertusername').text(username);
   $('.insertusername').text(username);
Line 69: Line 64:
   var userEditCount = mw.config.get('wgUserEditCount');
   var userEditCount = mw.config.get('wgUserEditCount');
   if (userEditCount == null)
   if (userEditCount == null)
    return;
return;


   $('.inserteditcount').text(userEditCount);
   $('.inserteditcount').text(userEditCount);
Line 81: Line 76:
   var userRegistrationDate = mw.config.get('wgUserRegistration');
   var userRegistrationDate = mw.config.get('wgUserRegistration');
   if (userRegistrationDate == null)
   if (userRegistrationDate == null)
    return;
return;
    
    
   var d = new Date(0); // Sets the date to the epoch
   var d = new Date(0); // Sets the date to the epoch
Line 95: Line 90:
// Advances a timestamp to the next multiple of 2 hours
// Advances a timestamp to the next multiple of 2 hours
function advanceDateTime(time) {
function advanceDateTime(time) {
    var ret = new Date(time.getTime());
var ret = new Date(time.getTime());
    ret.setMinutes(0);
ret.setMinutes(0);
    ret.setTime(ret.getTime() + 3600000 * (
ret.setTime(ret.getTime() + 3600000 * (
        ret.getUTCHours() & 1 ? 1 : 2
ret.getUTCHours() & 1 ? 1 : 2
    ));
));
    return ret;
return ret;
}
}


// Formats a timestamp as a string in local time
// Formats a timestamp as a string in local time
function formatDateTime(time) {
function formatDateTime(time) {
    var ret =
var ret =
        [ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
[ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
          "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
        ][time.getMonth()] + " " +
][time.getMonth()] + " " +
        time.getDate() + " " +
time.getDate() + " " +
        zeroPad(time.getHours(), 2) + ":" +
zeroPad(time.getHours(), 2) + ":" +
        zeroPad(time.getMinutes(), 2)
zeroPad(time.getMinutes(), 2)
    ;
;
    return ret;
return ret;
}
}


// Parses a UTC date string in the format "MMM dd hh:mm YYYY", the year at the end of the string is optional and replaces the year argument if provided
// Parses a UTC date string in the format "MMM dd hh:mm YYYY", the year at the end of the string is optional and replaces the year argument if provided
function parseDateTime(text, year) {
function parseDateTime(text, year) {
    text = text.split(/[\s:]+/);
text = text.split(/[\s:]+/);
    if(parseInt(text[4]) != NaN && parseInt(text[4]) < 9999 && parseInt(text[4]) >= 1970) year = text[4];
if(parseInt(text[4]) != NaN && parseInt(text[4]) < 9999 && parseInt(text[4]) >= 1970) year = text[4];
    return new Date(Date.UTC(
return new Date(Date.UTC(
        year,
year,
        { jan: 0, feb: 1, mar: 2, apr: 3, may:  4, jun:  5,
{ jan: 0, feb: 1, mar: 2, apr: 3, may:  4, jun:  5,
          jul: 6, aug: 7, sep: 8, oct: 9, nov: 10, dec: 11
  jul: 6, aug: 7, sep: 8, oct: 9, nov: 10, dec: 11
        }[text[0].toLowerCase()],
}[text[0].toLowerCase()],
        parseInt(text[1]), // Day
parseInt(text[1]), // Day
        parseInt(text[2]), // Hours
parseInt(text[2]), // Hours
        parseInt(text[3]), // Minutes
parseInt(text[3]), // Minutes
        0, 0 // Seconds, milliseconds
0, 0 // Seconds, milliseconds
    ));
));
}
}


// Parses a last-fetched string into a Date object
// Parses a last-fetched string into a Date object
function parseFetched(now, text) {
function parseFetched(now, text) {
    var ret = parseDateTime(text, now.getUTCFullYear());
var ret = parseDateTime(text, now.getUTCFullYear());
    if (now < ret) // Accounts for year boundary
if (now < ret) // Accounts for year boundary
        ret.setUTCFullYear(ret.getUTCFullYear() - 1);
ret.setUTCFullYear(ret.getUTCFullYear() - 1);
    return ret;
return ret;
}
}


// Parses a schedule string into a Date object
// Parses a schedule string into a Date object
function parseSchedule(fetched, text) {
function parseSchedule(fetched, text) {
    var ret = parseDateTime(text, fetched.getUTCFullYear());
var ret = parseDateTime(text, fetched.getUTCFullYear());
    if (ret.getTime() < fetched.getTime() - 8640000000)
if (ret.getTime() < fetched.getTime() - 8640000000)
        ret.setUTCFullYear(ret.getUTCFullYear() + 1);
ret.setUTCFullYear(ret.getUTCFullYear() + 1);
    return ret;
return ret;
}
}


// Calculates the time remaining until a given timestamp, as a string
// Calculates the time remaining until a given timestamp, as a string
function timeUntil(now, target) {
function timeUntil(now, target) {
    target      = target.getTime() - now.getTime();
target      = target.getTime() - now.getTime();
    target      = Math.floor(target % 7200000 / 1000);
target      = Math.floor(target % 7200000 / 1000);
    var seconds = zeroPad(target % 60, 2);
var seconds = zeroPad(target % 60, 2);
    var minutes = zeroPad(Math.floor(target / 60) % 60, 2);
var minutes = zeroPad(Math.floor(target / 60) % 60, 2);
    var hours  = Math.floor(target / 3600);
var hours  = Math.floor(target / 3600);
    return hours + ":" + minutes + ":" + seconds;
return hours + ":" + minutes + ":" + seconds;
}
}


// Pad a number with leading zeroes
// Pad a number with leading zeroes
function zeroPad(number, digits) {
function zeroPad(number, digits) {
    number = "" + number;
number = "" + number;
    while (number.length < digits)
while (number.length < digits)
        number = "0" + number;
number = "0" + number;
    return number;
return number;
}
}


Line 177: Line 172:
var BattleSchedule = function() {
var BattleSchedule = function() {


    // Initialize instance fields
// Initialize instance fields
    this.lblNow    = document.getElementById("battle1");
this.lblNow    = document.getElementById("battle1");
    this.lblNext    = document.getElementById("battle2");
this.lblNext    = document.getElementById("battle2");
    this.lblFetched = document.getElementById("battleFetched");
this.lblFetched = document.getElementById("battleFetched");
    this.prev      = false;
this.prev      = false;


    // Error checking
// Error checking
    if (!this.lblFetched) return; // No schedule data
if (!this.lblFetched) return; // No schedule data


    // Get the current and last-fetched timestamps
// Get the current and last-fetched timestamps
    var now    = new Date();
var now    = new Date();
    var fetched = parseFetched(now, this.lblFetched.innerHTML);
var fetched = parseFetched(now, this.lblFetched.innerHTML);


    // Determine the timestamp of the following two rotations
// Determine the timestamp of the following two rotations
    this.next  = advanceDateTime(fetched);
this.next  = advanceDateTime(fetched);
    this.later = advanceDateTime(this.next);
this.later = advanceDateTime(this.next);


    // Update initial display
// Update initial display
    this.onTick(now);
this.onTick(now);
    this.lblFetched.innerHTML = formatDateTime(fetched);
this.lblFetched.innerHTML = formatDateTime(fetched);


    // Schedule periodic updates
// Schedule periodic updates
    var that = this;
var that = this;
    this.timer = setInterval(function() { that.onTick(new Date()); }, 1000);
this.timer = setInterval(function() { that.onTick(new Date()); }, 1000);
};
};


Line 206: Line 201:
BattleSchedule.prototype.onTick = function(now) {
BattleSchedule.prototype.onTick = function(now) {


    // Determine when the "Now" row enters the past
// Determine when the "Now" row enters the past
    if (now >= this.next && !this.prevNow) {
if (now >= this.next && !this.prevNow) {
        this.prev            = true;
this.prev            = true;
        this.lblNow.innerHTML = "Previous";
this.lblNow.innerHTML = "Previous";
    }
}


    // Determine when the "Next" row enters the past
// Determine when the "Next" row enters the past
    if (now >= this.later && !this.prevNext) {
if (now >= this.later && !this.prevNext) {
        this.lblNext.innerHTML = "Previous";
this.lblNext.innerHTML = "Previous";
        clearInterval(this.timer);
clearInterval(this.timer);
        return;
return;
    }
}


    // Display the time until the next rotation
// Display the time until the next rotation
    this.lblNext.innerHTML =
this.lblNext.innerHTML =
        (this.prev ? "Now, for another " : "Next, in ") +
(this.prev ? "Now, for another " : "Next, in ") +
        timeUntil(now, this.prev ? this.later : this.next)
timeUntil(now, this.prev ? this.later : this.next)
    ;
;
};
};


Line 239: Line 234:
var SalmonSchedule = function() {
var SalmonSchedule = function() {


    // Get the current and last-fetched timestamps
// Get the current and last-fetched timestamps
    var lblFetched = document.getElementById("salmonFetched");
var lblFetched = document.getElementById("salmonFetched");
    if (!lblFetched) return; // No schedule
if (!lblFetched) return; // No schedule
    var now        = new Date();
var now        = new Date();
    var fetched    = parseFetched(now, lblFetched.innerHTML);
var fetched    = parseFetched(now, lblFetched.innerHTML);


    // Initialize instance fields
// Initialize instance fields
    this.slots = [
this.slots = [
        this.parse(document.getElementById("salmon1"), fetched),
this.parse(document.getElementById("salmon1"), fetched),
        this.parse(document.getElementById("salmon2"), fetched),
this.parse(document.getElementById("salmon2"), fetched),
    ];
];


    // Update initial display
// Update initial display
    this.onTick(now);
this.onTick(now);
    lblFetched.innerHTML = formatDateTime(fetched);
lblFetched.innerHTML = formatDateTime(fetched);


    // Schedule periodic updates
// Schedule periodic updates
    var that = this;
var that = this;
    this.timer = setInterval(function() { that.onTick(new Date()); }, 1000);
this.timer = setInterval(function() { that.onTick(new Date()); }, 1000);
};
};


Line 263: Line 258:
SalmonSchedule.prototype.onTick = function(now) {
SalmonSchedule.prototype.onTick = function(now) {


    // Cycle through slots
// Cycle through slots
    for (var x = 0; x < this.slots.length; x++) {
for (var x = 0; x < this.slots.length; x++) {
        var slot = this.slots[x];
var slot = this.slots[x];
        if (slot.prev) continue; // Skip this slot
if (slot.prev) continue; // Skip this slot


        // Determine when this slot should stop updating
// Determine when this slot should stop updating
        slot.prev = now >= slot.end;
slot.prev = now >= slot.end;


        // Update the element
// Update the element
        slot.element.innerHTML =
slot.element.innerHTML =
            now >= slot.end ? "Previous" :
now >= slot.end ? "Previous" :
            now >= slot.start ? "Now - " + formatDateTime(slot.end) :
now >= slot.start ? "Now - " + formatDateTime(slot.end) :
            formatDateTime(slot.start) + " - " + formatDateTime(slot.end)
formatDateTime(slot.start) + " - " + formatDateTime(slot.end)
        ;
;
    }
}


    // De-schedule the timer
// De-schedule the timer
    if (this.slots[this.slots.length - 1].prev)
if (this.slots[this.slots.length - 1].prev)
        clearInterval(this.timer);
clearInterval(this.timer);
};
};


// Parse a single Salmon Run schedule slot
// Parse a single Salmon Run schedule slot
SalmonSchedule.prototype.parse = function(element, fetched) {
SalmonSchedule.prototype.parse = function(element, fetched) {
    var text = element.innerHTML;
var text = element.innerHTML;
    return {
return {
        element: element,
element: element,
        start:  parseSchedule(fetched, text.substring( 0, 12)),
start:  parseSchedule(fetched, text.substring( 0, 12)),
        end:    parseSchedule(fetched, text.substring(15, 27)),
end:    parseSchedule(fetched, text.substring(15, 27)),
        prev:    false
prev:    false
    };
};
}
}


Line 307: Line 302:
// Object constructor
// Object constructor
var ShopSchedule = function() {
var ShopSchedule = function() {
    var lblFetched = document.getElementById("shopFetched");
var lblFetched = document.getElementById("shopFetched");
    if (!lblFetched) return; // No schedule
if (!lblFetched) return; // No schedule


    // Get the current and last-fetched timestamps
// Get the current and last-fetched timestamps
    var now    = new Date();
var now    = new Date();
    var fetched = parseFetched(now, lblFetched.innerHTML);
var fetched = parseFetched(now, lblFetched.innerHTML);


    // Update initial display
// Update initial display
    lblFetched.innerHTML = formatDateTime(fetched);
lblFetched.innerHTML = formatDateTime(fetched);
};
};


new ShopSchedule();
new ShopSchedule();




Line 330: Line 324:
// Object constructor
// Object constructor
var SplatfestSchedule = function() {
var SplatfestSchedule = function() {
    var that = this;
var that = this;


    // Initialize instance fields
// Initialize instance fields
    var now = new Date();
var now = new Date();
    this.slots = Array.from( document.querySelectorAll(".splatfestTimer") ).map( function (el) { return that.parse(el, now) } );
this.slots = Array.from( document.querySelectorAll(".splatfestTimer") ).map( function (el) { return that.parse(el, now) } );
    this.slots.push( // backwards compatibility
this.slots.push( // backwards compatibility
        this.parse(document.getElementById("splatfest1"), now),
this.parse(document.getElementById("splatfest1"), now),
        this.parse(document.getElementById("splatfest2"), now),
this.parse(document.getElementById("splatfest2"), now),
        this.parse(document.getElementById("splatfest3"), now)
this.parse(document.getElementById("splatfest3"), now)
    );
);


    // Update initial display
// Update initial display
    this.onTick(now);
this.onTick(now);


    // Schedule periodic updates
// Schedule periodic updates
    this.timer = setInterval(function() { that.onTick(new Date()); }, 1000);
this.timer = setInterval(function() { that.onTick(new Date()); }, 1000);
};
};


Line 351: Line 345:
SplatfestSchedule.prototype.onTick = function(now) {
SplatfestSchedule.prototype.onTick = function(now) {


    // Cycle through slots
// Cycle through slots
    for (var x = 0; x < this.slots.length; x++) {
for (var x = 0; x < this.slots.length; x++) {
        var slot = this.slots[x];
var slot = this.slots[x];
        if (slot.prev) continue; // Skip this slot
if (slot.prev) continue; // Skip this slot


        // Determine when this slot should stop updating
// Determine when this slot should stop updating
        slot.prev = now >= slot.end;
slot.prev = now >= slot.end;


        // Update the element
// Update the element
        slot.element.innerHTML =
slot.element.innerHTML =
            now >= slot.end ? "Concluded" :
now >= slot.end ? "Concluded" :
            now >= slot.start ? "Now - " + formatDateTime(slot.end) :
now >= slot.start ? "Now - " + formatDateTime(slot.end) :
            formatDateTime(slot.start)
formatDateTime(slot.start)
        ;
;
    }
}


    // De-schedule the timer
// De-schedule the timer
    if (this.slots[this.slots.length - 1].prev)
if (this.slots[this.slots.length - 1].prev)
        clearInterval(this.timer);
clearInterval(this.timer);
};
};


Line 375: Line 369:
SplatfestSchedule.prototype.parse = function(element, now) {
SplatfestSchedule.prototype.parse = function(element, now) {


    // Error checking
// Error checking
    if (!element) return { prev: true };
if (!element) return { prev: true };


    // Determine the current time and start and end timestamps
// Determine the current time and start and end timestamps
    var start = parseDateTime(element.innerHTML, now.getUTCFullYear());
var start = parseDateTime(element.innerHTML, now.getUTCFullYear());
    return {
return {
        element: element,
element: element,
        start:  start,
start:  start,
        end:    new Date(start.getTime() + 172800000),
end:    new Date(start.getTime() + 172800000),
        prev:    false
prev:    false
    };
};
};
};


new SplatfestSchedule();
new SplatfestSchedule();
///////////////////////////////////////////////////////////////////////////////
//                          ChallengeSchedule Class                        //
///////////////////////////////////////////////////////////////////////////////
// By user Harimaron
// Maintains auto-updating Challenge schedule elements
// Object constructor
var ChallengeSchedule = function() {
"use strict";
this.timesEl = [
document.getElementById("challengeTime1"),
document.getElementById("challengeTime2"),
];
if (!this.timesEl[0])
return; // No schedule data
var lblFetched = document.getElementById("challengeFetched");
if (!lblFetched)
return;
var now = new Date();
var fetched = parseFetched(now, lblFetched.innerHTML);
/**
* @param {string} str
*/
function parse(str) {
if (!str.endsWith(" UTC"))
return null;
var dates = str.split("-", 2);
if (dates.length != 2)
return null;
return ({
start: parseSchedule(fetched, dates[0].trim()),
end: parseSchedule(fetched, dates[1].slice(0, -4).trim()),
});
}
this.data = [];
for (var i = 0; i < this.timesEl.length; i++) {
var timeEl = this.timesEl[i];
if (!timeEl)
continue;
var orderEl = timeEl.getElementsByClassName("challengeOrder");
if (!orderEl.length)
continue;
var current = {
orderEl: orderEl[0],
slots: [/*{
el: new HTMLElement(),
start: new Date(),
end: new Date(),
},*/],
};
var slotsEl = timeEl.getElementsByClassName("challengeTimeSlot");
for (var j = 0; j < slotsEl.length; j++) {
var slotEl = slotsEl[j];
var slot = parse(slotEl.innerText);
if (!slot)
continue;
current.slots.push({
el: slotEl,
start: slot.start,
end: slot.end,
});
}
if (current.slots.length > 0)
this.data.push(current);
}
if (!this.data.length)
return;
var _this = this;
this.timer = setInterval(function() { _this.onTick(new Date()); }, 1000);
};
// Periodic update handler
/**
* @param now {Date}
*/
ChallengeSchedule.prototype.onTick = function(now) {
"use strict";
var upcomingOrNext = 1;
var hasFuture = false;
for (var i = 0; i < this.data.length; i++) {
var current = this.data[i];
for (var j = 0; j < current.slots.length; j++) {
var slot = current.slots[j];
var startStr = formatDateTime(slot.start);
var endStr = zeroPad(slot.end.getHours(), 2) + ":" + zeroPad(slot.end.getMinutes(), 2);
if (now >= slot.end) {
slot.el.style.textDecoration = "line-through";
slot.el.style.fontWeight = "normal";
} else if (now >= slot.start) {
slot.el.style.textDecoration = "underline";
startStr = "Now";
endStr += " (" + timeUntil(now, slot.end) + " left)";
}
slot.el.innerText = startStr + " - " + endStr;
}
var start = current.slots[0].start;
var end = current.slots[current.slots.length - 1].end;
if (now >= end) {
upcomingOrNext = 0;
current.orderEl.innerText = "Past";
} else if (now >= start) {
upcomingOrNext = 0;
hasFuture = true;
current.orderEl.innerText = "Current";
} else if (upcomingOrNext <= 1) {
hasFuture = true;
current.orderEl.innerText = "Upcoming";
} else {
hasFuture = true;
current.orderEl.innerText = "Next";
}
upcomingOrNext++;
}
if (!hasFuture) {
clearInterval(this.timer);
}
};
new ChallengeSchedule();


// ================================================================================
// ================================================================================
Line 399: Line 516:


function MLGetFileFromName(name){
function MLGetFileFromName(name){
    return new Promise(function(k,no){
return new Promise(function(k,no){
        if(window.MediaLoader.FileCache[name] == null){
if(window.MediaLoader.FileCache[name] == null){
            new mw.Api().get({
new mw.Api().get({
                "action": "parse",
"action": "parse",
                "format": "json",
"format": "json",
                "text": "[["+name+"]]",
"text": "[["+name+"]]",
                "prop": "text",
"prop": "text",
                "contentmodel": "wikitext"
"contentmodel": "wikitext"
            }).then(function(file){
}).then(function(file){
                var filetext = $($.parseHTML(file.parse.text["*"])).find('p').html();
var filetext = $($.parseHTML(file.parse.text["*"])).find('p').html();
                window.MediaLoader.FileCache[name] = filetext;
window.MediaLoader.FileCache[name] = filetext;
                k(filetext);
k(filetext);
            },no);
},no);
        }
}
        else
else
            k(window.MediaLoader.FileCache[name]);
k(window.MediaLoader.FileCache[name]);
    })
})
}
}


mw.loader.using("mediawiki.api", function(){
mw.loader.using("mediawiki.api", function(){
    $(".MediaLoader").each(function(){
$(".MediaLoader").each(function(){
        $(this).data("state", "unloaded");
$(this).data("state", "unloaded");
        var children = $(this).children();
var children = $(this).children();
        if(children.length < 1){
if(children.length < 1){
            console.error("[MediaLoader] Error P1");
console.error("[MediaLoader] Error P1");
            return;
return;
        }
}
        var child = $(children[0]);
var child = $(children[0]);
        child.find(".MediaLoader-text").click(function(){
child.find(".MediaLoader-text").click(function(){
            var parent = $(this).parent().parent();
var parent = $(this).parent().parent();
            try{
try{
                if(parent.data("state") == "unloaded"){
if(parent.data("state") == "unloaded"){
                    parent.data("state", "busy");
parent.data("state", "busy");
                    $(this).text("Loading...");
$(this).text("Loading...");
                   
                    MLGetFileFromName(parent.data("file")).then(function(filetext){
MLGetFileFromName(parent.data("file")).then(function(filetext){
                        parent.find(".MediaLoader-file").html(filetext);
parent.find(".MediaLoader-file").html(filetext);
                        parent.find(".MediaLoader-text").text("Unload "+parent.data("name"));
parent.find(".MediaLoader-text").text("Unload "+parent.data("name"));
                        parent.data("state", "loaded");
parent.data("state", "loaded");
                    }, console.error)
}, console.error)
                }
}
                else if(parent.data("state") == "loaded"){
else if(parent.data("state") == "loaded"){
                    parent.find(".MediaLoader-file").html("");
parent.find(".MediaLoader-file").html("");
                    $(this).text("Load "+parent.data("name"));
$(this).text("Load "+parent.data("name"));
                    parent.data("state", "unloaded");
parent.data("state", "unloaded");
                }
}
            }
}
            catch(ex){
catch(ex){
                console.error(ex);
console.error(ex);
                parent.data("state", "error");
parent.data("state", "error");
                $(this).text("An unexpected error has occured");
$(this).text("An unexpected error has occured");
                parent.find(".MediaLoader-file").html("<a></a>");
parent.find(".MediaLoader-file").html("<a></a>");
                parent.find(".MediaLoader-file").children("a").attr("href", "//splatoonwiki.org/wiki/"+parent.data("file"))
parent.find(".MediaLoader-file").children("a").attr("href", "//splatoonwiki.org/wiki/"+parent.data("file"))
                parent.find(".MediaLoader-file").children("a").text(parent.data("name"))
parent.find(".MediaLoader-file").children("a").text(parent.data("name"))
                $(this).css("cursor", "");
$(this).css("cursor", "");
            }
}
        })
})
        child.find(".MediaLoader-text").text("Load "+$(this).data("name"));
child.find(".MediaLoader-text").text("Load "+$(this).data("name"));
        child.find(".MediaLoader-text").addClass("noselect");
child.find(".MediaLoader-text").addClass("noselect");
        child.find(".MediaLoader-text").css("cursor", "pointer");
child.find(".MediaLoader-text").css("cursor", "pointer");
        child.find(".MediaLoader-file").addClass("noselect");
child.find(".MediaLoader-file").addClass("noselect");
    })
})
   
    $(".MediaLoadAll").each(function(){
$(".MediaLoadAll").each(function(){
        var children = $(this).children();
var children = $(this).children();
        if(children.length < 2){
if(children.length < 2){
            console.error("[MediaLoadAll] Error P1");
console.error("[MediaLoadAll] Error P1");
            return;
return;
        }
}
        children.click(function(){
children.click(function(){
            var parent = $(this).parent();
var parent = $(this).parent();
            try{
try{
                var load = $(this).hasClass("MediaLoadAll-load");
var load = $(this).hasClass("MediaLoadAll-load");
                $(parent.data("group") != "{{{group}}}" ? '.MediaLoader[data-group="'+parent.data("group")+'"]' : ".MediaLoader").each(function(){
$(parent.data("group") != "{{{group}}}" ? '.MediaLoader[data-group="'+parent.data("group")+'"]' : ".MediaLoader").each(function(){
                    if(($(this).data("state") == "unloaded" && load) || ($(this).data("state") == "loaded" && !load))
if(($(this).data("state") == "unloaded" && load) || ($(this).data("state") == "loaded" && !load))
                        $(this).find(".MediaLoader-text").click();
$(this).find(".MediaLoader-text").click();
                })
})
            }
}
            catch(ex){
catch(ex){
                console.error(ex);
console.error(ex);
                $(this).text("An unexpected error has occured");
$(this).text("An unexpected error has occured");
                $(this).css("cursor", "");
$(this).css("cursor", "");
            }
}
        })
})
        $(this).css("display", "")
$(this).css("display", "")
        children.filter(".MediaLoadAll-load").text("Load all "+$(this).data("name"));
children.filter(".MediaLoadAll-load").text("Load all "+$(this).data("name"));
        children.filter(".MediaLoadAll-unload").text("Unload all "+$(this).data("name"));
children.filter(".MediaLoadAll-unload").text("Unload all "+$(this).data("name"));
        children.addClass("noselect");
children.addClass("noselect");
        children.css("cursor", "pointer");
children.css("cursor", "pointer");
    })
})
})
})


Line 505: Line 622:
    
    
   // Parse time fields from the number
   // Parse time fields from the number
                                              time = Math.floor(time / 1000);
  time = Math.floor(time / 1000);
   var secs  = ("00" + (time % 60)).slice(-2); time = Math.floor(time /  60);
   var secs  = ("00" + (time % 60)).slice(-2); time = Math.floor(time /  60);
   var mins  = ("00" + (time % 60)).slice(-2); time = Math.floor(time /  60);
   var mins  = ("00" + (time % 60)).slice(-2); time = Math.floor(time /  60);
Line 512: Line 629:
   // Construct the string representation
   // Construct the string representation
   return {
   return {
    d: time,
d: time,
    h: hours,
h: hours,
    m: mins,
m: mins,
    s: secs,
s: secs,
    p: passed
p: passed
   };
   };
    
    
Line 533: Line 650:
    
    
   for(var c = 0; c < countdowns.length; c++){
   for(var c = 0; c < countdowns.length; c++){
   
    var diff = 0;
var diff = 0;
    if(countdowns[c].stage) {
if(countdowns[c].stage) {
      diff = timeToStruct(getStageCountdown(now));
  diff = timeToStruct(getStageCountdown(now));
    } else {
} else {
      diff = timeToStruct(countdowns[c].time - now);
  diff = timeToStruct(countdowns[c].time - now);
    }
}
   
    if(diff.p && diff.d < -1) {
if(diff.p && diff.d < -1) {
      // Over 24 hours passed
  // Over 24 hours passed
      countdowns[c].span.innerHTML = countdowns[c].doneMessage;
  countdowns[c].span.innerHTML = countdowns[c].doneMessage;
    } else if(diff.p){
} else if(diff.p){
      // 24 hours haven't passed yet
  // 24 hours haven't passed yet
      countdowns[c].span.innerHTML = countdowns[c].ongoingMessage;
  countdowns[c].span.innerHTML = countdowns[c].ongoingMessage;
    } else {
} else {
      // The time hasn't come yet
  // The time hasn't come yet
      countdowns[c].span.innerHTML =
  countdowns[c].span.innerHTML =
        diff.d + "d " +
diff.d + "d " +
        diff.h + "h " +
diff.h + "h " +
        diff.m + "m " +
diff.m + "m " +
        diff.s + "s";
diff.s + "s";
    }
}
   }
   }
}
}
Line 565: Line 682:


   if(!stage) {
   if(!stage) {
    // Format is "<day> <hour>|<24 hour msg>|<afterwards msg>"
// Format is "<day> <hour>|<24 hour msg>|<afterwards msg>"
    var parts = countdown.innerHTML.split("|");
var parts = countdown.innerHTML.split("|");
    doneMessage = (parts.length >= 3) ? parts[2] : parts[1];
doneMessage = (parts.length >= 3) ? parts[2] : parts[1];
    ongoingMessage = parts[1];
ongoingMessage = parts[1];
   
    var timeParts = parts[0].split(/[ \n]/);
var timeParts = parts[0].split(/[ \n]/);
    var date = timeParts[0].split("/");
var date = timeParts[0].split("/");
    var hour = timeParts[1].split(":");
var hour = timeParts[1].split(":");
    time = Date.UTC(date[0], date[1] - 1, date[2], hour[0], hour[1]);
time = Date.UTC(date[0], date[1] - 1, date[2], hour[0], hour[1]);
   }
   }
    
    
   countdowns.push( {
   countdowns.push( {
    span:          countdown,
span:          countdown,
    stage:          stage,
stage:          stage,
    time:          time,
time:          time,
    ongoingMessage: ongoingMessage,
ongoingMessage: ongoingMessage,
    doneMessage:    doneMessage
doneMessage:    doneMessage
   } );
   } );
    
    
Line 594: Line 711:
   var stageCountdowns = document.getElementsByClassName("stageCountdown");
   var stageCountdowns = document.getElementsByClassName("stageCountdown");
   for(var sc = 0; sc < stageCountdowns.length; sc++) {
   for(var sc = 0; sc < stageCountdowns.length; sc++) {
    getCountdownInfo(stageCountdowns[sc], true);
getCountdownInfo(stageCountdowns[sc], true);
   }
   }
    
    
   var countdowns = document.getElementsByClassName("countdown");
   var countdowns = document.getElementsByClassName("countdown");
   for(var c = 0; c < countdowns.length; c++) {
   for(var c = 0; c < countdowns.length; c++) {
    getCountdownInfo(countdowns[c], false);
getCountdownInfo(countdowns[c], false);
   }
   }
    
    
Line 633: Line 750:
// Produce a game button for the infobox
// Produce a game button for the infobox
var button = game.cloneNode(true);
var button = game.cloneNode(true);
button.gameId  = game.gameId;
button.gobbler = gobbler;
button.gobbler = gobbler;
button.addEventListener("keydown", setGame);
button.addEventListener("keydown", setGame);
Line 695: Line 813:
// Configure instance fields
// Configure instance fields
infobox.gameId = getGameId(infobox);
infobox.gameId = getGameId(infobox);
infobox.remove();


// Prevent lazy loading of any contained images
// Prevent lazy loading of any contained images

Latest revision as of 10:37, 13 February 2024

/* Any JavaScript here will be loaded for all users both on desktop and mobile */

// ================================================================================
// Page specific JS/CSS
// ================================================================================

//Check page specific files
$(function () {
	var url = new URL(window.location.href);
	var action = url.searchParams.get("action")
	if (action === null || action === "view" || action === "submit") {
		mw.loader.using("mediawiki.api", function () {
			var skin = mw.config.get("skin"),
				page = mw.config.get("wgPageName"),
				user = mw.config.get("wgUserName");

			var pages = [
				['MediaWiki:Common.js/' + page + ".js", "js"],
				['MediaWiki:Common.css/' + page + ".css", "css"],
				['MediaWiki:' + skin + '.js/' + page + ".js", "js"],
				['MediaWiki:' + skin + '.css/' + page + ".css", "css"]
			];
			if (user != null) pages.push(
				['User:' + user + '/common.js/' + page + ".js", "js"],
				['User:' + user + '/common.css/' + page + ".css", "css"],
				['User:' + user + '/' + skin + '.js/' + page + ".js", "js"],
				['User:' + user + '/' + skin + '.css/' + page + ".css", "css"]
			);
			pages.forEach(function (el) {
				if (el[1] == "js") {
					if (new URL(window.location).searchParams.get("disable-page-js") != null) return;
					mw.loader.load('/w/index.php?title=' + encodeURIComponent(el[0]) + '&action=raw&ctype=text/javascript');
				}
				else {
					if (new URL(window.location).searchParams.get("disable-page-css") != null) return;
					mw.loader.load('/w/index.php?title=' + encodeURIComponent(el[0]) + '&action=raw&ctype=text/css', 'text/css');
				}
			});
		});
	}
});

// ================================================================================
// Username replace function for [[Template:USERNAME]]
// ================================================================================
// Inserts user name into <span class="insertusername"></span>.
// Disable by setting disableUsernameReplace = true.
jQuery(function($) {
  if (typeof(disableUsernameReplace) != 'undefined' && disableUsernameReplace)
	return;
  
  var username = mw.config.get('wgUserName');
  if (username == null)
	return;

  $('.insertusername').text(username);
});

// ================================================================================
// Editcount replace function for [[Template:EDITCOUNT]]
// ================================================================================
// Inserts edit count into <span class="inserteditcount"></span>
jQuery(function($) {
  var userEditCount = mw.config.get('wgUserEditCount');
  if (userEditCount == null)
	return;

  $('.inserteditcount').text(userEditCount);
});

// ================================================================================
// Registration date replace function for [[Template:REGISTRATIONDATE]]
// ================================================================================
// Inserts registration date into <span class="insertregistrationdate"></span>
jQuery(function($) {
  var userRegistrationDate = mw.config.get('wgUserRegistration');
  if (userRegistrationDate == null)
	return;
  
  var d = new Date(0); // Sets the date to the epoch
  d.setUTCMilliseconds(userRegistrationDate);

  $('.insertregistrationdate').text(d.toLocaleString());
});

///////////////////////////////////////////////////////////////////////////////
//                        Schedule Utility Functions                         //
///////////////////////////////////////////////////////////////////////////////

// Advances a timestamp to the next multiple of 2 hours
function advanceDateTime(time) {
	var ret = new Date(time.getTime());
	ret.setMinutes(0);
	ret.setTime(ret.getTime() + 3600000 * (
		ret.getUTCHours() & 1 ? 1 : 2
	));
	return ret;
}

// Formats a timestamp as a string in local time
function formatDateTime(time) {
	var ret =
		[ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
		  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
		][time.getMonth()] + " " +
		time.getDate() + " " +
		zeroPad(time.getHours(), 2) + ":" +
		zeroPad(time.getMinutes(), 2)
	;
	return ret;
}

// Parses a UTC date string in the format "MMM dd hh:mm YYYY", the year at the end of the string is optional and replaces the year argument if provided
function parseDateTime(text, year) {
	text = text.split(/[\s:]+/);
	if(parseInt(text[4]) != NaN && parseInt(text[4]) < 9999 && parseInt(text[4]) >= 1970) year = text[4];
	return new Date(Date.UTC(
		year,
		{ jan: 0, feb: 1, mar: 2, apr: 3, may:  4, jun:  5,
		  jul: 6, aug: 7, sep: 8, oct: 9, nov: 10, dec: 11
		}[text[0].toLowerCase()],
		parseInt(text[1]), // Day
		parseInt(text[2]), // Hours
		parseInt(text[3]), // Minutes
		0, 0 // Seconds, milliseconds
	));
}

// Parses a last-fetched string into a Date object
function parseFetched(now, text) {
	var ret = parseDateTime(text, now.getUTCFullYear());
	if (now < ret) // Accounts for year boundary
		ret.setUTCFullYear(ret.getUTCFullYear() - 1);
	return ret;
}

// Parses a schedule string into a Date object
function parseSchedule(fetched, text) {
	var ret = parseDateTime(text, fetched.getUTCFullYear());
	if (ret.getTime() < fetched.getTime() - 8640000000)
		ret.setUTCFullYear(ret.getUTCFullYear() + 1);
	return ret;
}

// Calculates the time remaining until a given timestamp, as a string
function timeUntil(now, target) {
	target      = target.getTime() - now.getTime();
	target      = Math.floor(target % 7200000 / 1000);
	var seconds = zeroPad(target % 60, 2);
	var minutes = zeroPad(Math.floor(target / 60) % 60, 2);
	var hours   = Math.floor(target / 3600);
	return hours + ":" + minutes + ":" + seconds;
}

// Pad a number with leading zeroes
function zeroPad(number, digits) {
	number = "" + number;
	while (number.length < digits)
		number = "0" + number;
	return number;
}



///////////////////////////////////////////////////////////////////////////////
//                           BattleSchedule Class                            //
///////////////////////////////////////////////////////////////////////////////

// Maintains auto-updating Ink Battle schedule elements

// Object constructor
var BattleSchedule = function() {

	// Initialize instance fields
	this.lblNow     = document.getElementById("battle1");
	this.lblNext    = document.getElementById("battle2");
	this.lblFetched = document.getElementById("battleFetched");
	this.prev       = false;

	// Error checking
	if (!this.lblFetched) return; // No schedule data

	// Get the current and last-fetched timestamps
	var now     = new Date();
	var fetched = parseFetched(now, this.lblFetched.innerHTML);

	// Determine the timestamp of the following two rotations
	this.next  = advanceDateTime(fetched);
	this.later = advanceDateTime(this.next);

	// Update initial display
	this.onTick(now);
	this.lblFetched.innerHTML = formatDateTime(fetched);

	// Schedule periodic updates
	var that = this;
	this.timer = setInterval(function() { that.onTick(new Date()); }, 1000);
};

// Periodic update handler
BattleSchedule.prototype.onTick = function(now) {

	// Determine when the "Now" row enters the past
	if (now >= this.next && !this.prevNow) {
		this.prev             = true;
		this.lblNow.innerHTML = "Previous";
	}

	// Determine when the "Next" row enters the past
	if (now >= this.later && !this.prevNext) {
		this.lblNext.innerHTML = "Previous";
		clearInterval(this.timer);
		return;
	}

	// Display the time until the next rotation
	this.lblNext.innerHTML =
		(this.prev ? "Now, for another " : "Next, in ") +
		timeUntil(now, this.prev ? this.later : this.next)
	;
};

new BattleSchedule();



///////////////////////////////////////////////////////////////////////////////
//                           SalmonSchedule Class                            //
///////////////////////////////////////////////////////////////////////////////

// Maintains auto-updating Salmon Run schedule elements

// Object constructor
var SalmonSchedule = function() {

	// Get the current and last-fetched timestamps
	var lblFetched = document.getElementById("salmonFetched");
	if (!lblFetched) return; // No schedule
	var now        = new Date();
	var fetched    = parseFetched(now, lblFetched.innerHTML);

	// Initialize instance fields
	this.slots = [
		this.parse(document.getElementById("salmon1"), fetched),
		this.parse(document.getElementById("salmon2"), fetched),
	];

	// Update initial display
	this.onTick(now);
	lblFetched.innerHTML = formatDateTime(fetched);

	// Schedule periodic updates
	var that = this;
	this.timer = setInterval(function() { that.onTick(new Date()); }, 1000);
};

// Periodic update handler
SalmonSchedule.prototype.onTick = function(now) {

	// Cycle through slots
	for (var x = 0; x < this.slots.length; x++) {
		var slot = this.slots[x];
		if (slot.prev) continue; // Skip this slot

		// Determine when this slot should stop updating
		slot.prev = now >= slot.end;

		// Update the element
		slot.element.innerHTML =
			now >= slot.end ? "Previous" :
			now >= slot.start ? "Now - " + formatDateTime(slot.end) :
			formatDateTime(slot.start) + " - " + formatDateTime(slot.end)
		;
	}

	// De-schedule the timer
	if (this.slots[this.slots.length - 1].prev)
		clearInterval(this.timer);
};

// Parse a single Salmon Run schedule slot
SalmonSchedule.prototype.parse = function(element, fetched) {
	var text = element.innerHTML;
	return {
		element: element,
		start:   parseSchedule(fetched, text.substring( 0, 12)),
		end:     parseSchedule(fetched, text.substring(15, 27)),
		prev:    false
	};
}

new SalmonSchedule();



///////////////////////////////////////////////////////////////////////////////
//                            ShopSchedule Class                             //
///////////////////////////////////////////////////////////////////////////////

// Maintains auto-updating SplatNet 2 Shop schedule elements

// Object constructor
var ShopSchedule = function() {
	var lblFetched = document.getElementById("shopFetched");
	if (!lblFetched) return; // No schedule

	// Get the current and last-fetched timestamps
	var now     = new Date();
	var fetched = parseFetched(now, lblFetched.innerHTML);

	// Update initial display
	lblFetched.innerHTML = formatDateTime(fetched);
};

new ShopSchedule();


///////////////////////////////////////////////////////////////////////////////
//                          SplatfestSchedule Class                          //
///////////////////////////////////////////////////////////////////////////////

// Maintains auto-updating Splatfest schedule elements

// Object constructor
var SplatfestSchedule = function() {
	var that = this;

	// Initialize instance fields
	var now = new Date();
	this.slots = Array.from( document.querySelectorAll(".splatfestTimer") ).map( function (el) { return that.parse(el, now) } );
	this.slots.push( // backwards compatibility
		this.parse(document.getElementById("splatfest1"), now),
		this.parse(document.getElementById("splatfest2"), now),
		this.parse(document.getElementById("splatfest3"), now)
	);

	// Update initial display
	this.onTick(now);

	// Schedule periodic updates
	this.timer = setInterval(function() { that.onTick(new Date()); }, 1000);
};

// Periodic update handler
SplatfestSchedule.prototype.onTick = function(now) {

	// Cycle through slots
	for (var x = 0; x < this.slots.length; x++) {
		var slot = this.slots[x];
		if (slot.prev) continue; // Skip this slot

		// Determine when this slot should stop updating
		slot.prev = now >= slot.end;

		// Update the element
		slot.element.innerHTML =
			now >= slot.end ? "Concluded" :
			now >= slot.start ? "Now - " + formatDateTime(slot.end) :
			formatDateTime(slot.start)
		;
	}

	// De-schedule the timer
	if (this.slots[this.slots.length - 1].prev)
		clearInterval(this.timer);
};

// Parse a single Splatfest schdule slot
SplatfestSchedule.prototype.parse = function(element, now) {

	// Error checking
	if (!element) return { prev: true };

	// Determine the current time and start and end timestamps
	var start = parseDateTime(element.innerHTML, now.getUTCFullYear());
	return {
		element: element,
		start:   start,
		end:     new Date(start.getTime() + 172800000),
		prev:    false
	};
};

new SplatfestSchedule();

///////////////////////////////////////////////////////////////////////////////
//                           ChallengeSchedule Class                         //
///////////////////////////////////////////////////////////////////////////////
// By user Harimaron

// Maintains auto-updating Challenge schedule elements

// Object constructor
var ChallengeSchedule = function() {
	"use strict";
	this.timesEl = [
		document.getElementById("challengeTime1"),
		document.getElementById("challengeTime2"),
	];
	if (!this.timesEl[0])
		return; // No schedule data
	
	var lblFetched = document.getElementById("challengeFetched");
	if (!lblFetched)
		return;
	var now = new Date();
	var fetched = parseFetched(now, lblFetched.innerHTML);
	/**
	 * @param {string} str
	 */
	function parse(str) {
		if (!str.endsWith(" UTC"))
			return null;
		var dates = str.split("-", 2);
		if (dates.length != 2)
			return null;
		return ({
			start: parseSchedule(fetched, dates[0].trim()),
			end: parseSchedule(fetched, dates[1].slice(0, -4).trim()),
		});
	}
	this.data = [];
	for (var i = 0; i < this.timesEl.length; i++) {
		var timeEl = this.timesEl[i];
		if (!timeEl)
			continue;
		var orderEl = timeEl.getElementsByClassName("challengeOrder");
		if (!orderEl.length)
			continue;
		var current = {
			orderEl: orderEl[0],
			slots: [/*{
				el: new HTMLElement(),
				start: new Date(),
				end: new Date(),
			},*/],
		};
		var slotsEl = timeEl.getElementsByClassName("challengeTimeSlot");
		for (var j = 0; j < slotsEl.length; j++) {
			var slotEl = slotsEl[j];
			var slot = parse(slotEl.innerText);
			if (!slot)
				continue;
			current.slots.push({
				el: slotEl,
				start: slot.start,
				end: slot.end,
			});
		}
		if (current.slots.length > 0)
			this.data.push(current);
	}

	if (!this.data.length)
		return;
	var _this = this;
	this.timer = setInterval(function() { _this.onTick(new Date()); }, 1000);
};

// Periodic update handler
/**
 * @param now {Date}
 */
ChallengeSchedule.prototype.onTick = function(now) {
	"use strict";
	var upcomingOrNext = 1;
	var hasFuture = false;
	for (var i = 0; i < this.data.length; i++) {
		var current = this.data[i];
		for (var j = 0; j < current.slots.length; j++) {
			var slot = current.slots[j];
			var startStr = formatDateTime(slot.start);
			var endStr = zeroPad(slot.end.getHours(), 2) + ":" + zeroPad(slot.end.getMinutes(), 2);
			if (now >= slot.end) {
				slot.el.style.textDecoration = "line-through";
				slot.el.style.fontWeight = "normal";
			} else if (now >= slot.start) {
				slot.el.style.textDecoration = "underline";
				startStr = "Now";
				endStr += " (" + timeUntil(now, slot.end) + " left)";
			}
			slot.el.innerText = startStr + " - " + endStr;
		}
		var start = current.slots[0].start;
		var end = current.slots[current.slots.length - 1].end;
		if (now >= end) {
			upcomingOrNext = 0;
			current.orderEl.innerText = "Past";
		} else if (now >= start) {
			upcomingOrNext = 0;
			hasFuture = true;
			current.orderEl.innerText = "Current";
		} else if (upcomingOrNext <= 1) {
			hasFuture = true;
			current.orderEl.innerText = "Upcoming";
		} else {
			hasFuture = true;
			current.orderEl.innerText = "Next";
		}
		upcomingOrNext++;
	}
	if (!hasFuture) {
		clearInterval(this.timer);
	}
};

new ChallengeSchedule();

// ================================================================================
// MediaLoader - Prevent audio from loading until clicked
// Version 2 (19.04.2020)
// ================================================================================

window.MediaLoader = {};
window.MediaLoader.FileCache = {};

function MLGetFileFromName(name){
	return new Promise(function(k,no){
		if(window.MediaLoader.FileCache[name] == null){
			new mw.Api().get({
				"action": "parse",
				"format": "json",
				"text": "[["+name+"]]",
				"prop": "text",
				"contentmodel": "wikitext"
			}).then(function(file){
				var filetext = $($.parseHTML(file.parse.text["*"])).find('p').html();
				window.MediaLoader.FileCache[name] = filetext;
				k(filetext);
			},no);
		}
		else
			k(window.MediaLoader.FileCache[name]);
	})
}

mw.loader.using("mediawiki.api", function(){
	$(".MediaLoader").each(function(){
		$(this).data("state", "unloaded");
		var children = $(this).children();
		if(children.length < 1){
			console.error("[MediaLoader] Error P1");
			return;
		}
		var child = $(children[0]);
		child.find(".MediaLoader-text").click(function(){
			var parent = $(this).parent().parent();
			try{
				if(parent.data("state") == "unloaded"){
					parent.data("state", "busy");
					$(this).text("Loading...");
					
					MLGetFileFromName(parent.data("file")).then(function(filetext){
						parent.find(".MediaLoader-file").html(filetext);
						parent.find(".MediaLoader-text").text("Unload "+parent.data("name"));
						parent.data("state", "loaded");
					}, console.error)
				}
				else if(parent.data("state") == "loaded"){
					parent.find(".MediaLoader-file").html("");
					$(this).text("Load "+parent.data("name"));
					parent.data("state", "unloaded");
				}
			}
			catch(ex){
				console.error(ex);
				parent.data("state", "error");
				$(this).text("An unexpected error has occured");
				parent.find(".MediaLoader-file").html("<a></a>");
				parent.find(".MediaLoader-file").children("a").attr("href", "//splatoonwiki.org/wiki/"+parent.data("file"))
				parent.find(".MediaLoader-file").children("a").text(parent.data("name"))
				$(this).css("cursor", "");
			}
		})
		child.find(".MediaLoader-text").text("Load "+$(this).data("name"));
		child.find(".MediaLoader-text").addClass("noselect");
		child.find(".MediaLoader-text").css("cursor", "pointer");
		child.find(".MediaLoader-file").addClass("noselect");
	})
	
	$(".MediaLoadAll").each(function(){
		var children = $(this).children();
		if(children.length < 2){
			console.error("[MediaLoadAll] Error P1");
			return;
		}
		children.click(function(){
			var parent = $(this).parent();
			try{
				var load = $(this).hasClass("MediaLoadAll-load");
				$(parent.data("group") != "{{{group}}}" ? '.MediaLoader[data-group="'+parent.data("group")+'"]' : ".MediaLoader").each(function(){
					if(($(this).data("state") == "unloaded" && load) || ($(this).data("state") == "loaded" && !load))
						$(this).find(".MediaLoader-text").click();
				})
			}
			catch(ex){
				console.error(ex);
				$(this).text("An unexpected error has occured");
				$(this).css("cursor", "");
			}
		})
		$(this).css("display", "")
		children.filter(".MediaLoadAll-load").text("Load all "+$(this).data("name"));
		children.filter(".MediaLoadAll-unload").text("Unload all "+$(this).data("name"));
		children.addClass("noselect");
		children.css("cursor", "pointer");
	})
})

// ================================================================================
// Countdowns
// ================================================================================
// Credits go to AbelToy, Guy Perfect, Espyo for the countdown code.

// List of countdowns on the current page
var countdowns = [];

// Converts from time to a clean time info structure
function timeToStruct(time) {

  var passed = time < 0; //Has the moment passed?
  
  // Parse time fields from the number
											  time = Math.floor(time / 1000);
  var secs  = ("00" + (time % 60)).slice(-2); time = Math.floor(time /   60);
  var mins  = ("00" + (time % 60)).slice(-2); time = Math.floor(time /   60);
  var hours = ("00" + (time % 24)).slice(-2); time = Math.floor(time /   24);

  // Construct the string representation
  return {
	d: time,
	h: hours,
	m: mins,
	s: secs,
	p: passed
  };
  
}

// Gets the time remaining until the next stage rotation
function getStageCountdown(now) {
  var hour   = Math.floor(now / 3600000) % 24 + 2; // Add 2 for UTC bias
  var now    = hour * 3600000 + now % 3600000;     // Current adjusted hour
  var target = (hour + 4 & -4) * 3600000;          // Target hour
  return target - now;
}

function tickCountdowns() {
  var now = Date.now();
  
  for(var c = 0; c < countdowns.length; c++){
	
	var diff = 0;
	if(countdowns[c].stage) {
	  diff = timeToStruct(getStageCountdown(now));
	} else {
	  diff = timeToStruct(countdowns[c].time - now);
	}
	
	if(diff.p && diff.d < -1) {
	  // Over 24 hours passed
	  countdowns[c].span.innerHTML = countdowns[c].doneMessage;
	} else if(diff.p){
	  // 24 hours haven't passed yet
	  countdowns[c].span.innerHTML = countdowns[c].ongoingMessage;
	} else {
	  // The time hasn't come yet
	  countdowns[c].span.innerHTML =
		diff.d + "d " +
		diff.h + "h " +
		diff.m + "m " +
		diff.s + "s";
	}
  }
}

// Returns the info from a countdown span on the page.
function getCountdownInfo(countdown, stage) {
  var time = null;
  var ongoingMessage = "";
  var doneMessage = "";

  if(!stage) {
	// Format is "<day> <hour>|<24 hour msg>|<afterwards msg>"
	var parts = countdown.innerHTML.split("|");
	doneMessage = (parts.length >= 3) ? parts[2] : parts[1];
	ongoingMessage = parts[1];
	
	var timeParts = parts[0].split(/[ \n]/);
	var date = timeParts[0].split("/");
	var hour = timeParts[1].split(":");
	time = Date.UTC(date[0], date[1] - 1, date[2], hour[0], hour[1]);
  }
  
  countdowns.push( {
	span:           countdown,
	stage:          stage,
	time:           time,
	ongoingMessage: ongoingMessage,
	doneMessage:    doneMessage
  } );
  
  // The spans start hidden and with the info
  // Delete the info and show the span
  countdown.style.display = "inline";
  countdown.innerHTML = "";
}

// Finds countdown spans on the document and sets up the countdowns
function setupCountdowns() {
  var stageCountdowns = document.getElementsByClassName("stageCountdown");
  for(var sc = 0; sc < stageCountdowns.length; sc++) {
	getCountdownInfo(stageCountdowns[sc], true);
  }
  
  var countdowns = document.getElementsByClassName("countdown");
  for(var c = 0; c < countdowns.length; c++) {
	getCountdownInfo(countdowns[c], false);
  }
  
  setInterval(tickCountdowns, 1000);
}

setupCountdowns();



///////////////////////////// New infobox gobbler //////////////////////////////

// Keep looking for infoboxes until the page finishes loading
(function() {
	var gobblers  = []; // Found gobblers
	var infoboxes = []; // Found infoboxes

	// Add an infobox to a gobbler
	var addInfobox = function(gobbler, infobox) {

		// The infobox is already gobbled
		if (gobbler.infoboxKeys.has(infobox))
			return;
		gobbler.infoboxKeys.add(infobox);

		// Locate the game button template that applies to this infobox
		var game = gobbler.games.find(function(game) {
			return game.gameId == infobox.gameId;
		});
		if (!game)
			return false; // No matching game button template

		// Produce a game button for the infobox
		var button = game.cloneNode(true);
		button.gameId  = game.gameId;
		button.gobbler = gobbler;
		button.addEventListener("keydown", setGame);
		button.addEventListener("pointerdown", setGame);
		gobbler.gameList.append(button);
		gobbler.buttons.push(button);

		// Add the infobox to the gobbler
		infobox.remove();
		infobox = infobox.cloneNode(true);
		infobox.classList.add("inactive");
		gobbler.infoboxes.push(infobox);
		gobbler.append(infobox);
		button.infobox = infobox;
		gobbler.infoboxes.forEach(function(box) {
			box.classList[box != infobox ? "add" : "remove"]("inactive");
		});
		
		// Select the first game button for the same game as the new button
		setGame({
			type  : "pointerdown",
			button: 0,
			target:	gobbler.buttons.find(function(btn) {
				return btn.gameId == button.gameId;
			})
		});

		gobbler.classList.add("active");
	};

	// Derive a game ID from a game button or infobox element class list
	var getGameId = function(element) {
		return Array.from(element.classList).find(function(clazz) {
			return !clazz.startsWith("infobox");
		});
	};
	
	// Add a newly discovered gobbler
	var newGobbler = function(gobbler) {

		// Configure instance fields
		gobbler.buttons     = [];
		gobbler.gameList    = gobbler.querySelector(":scope > .infobox-game-list");
		gobbler.games       = Array.from(gobbler.gameList.querySelectorAll(".infobox-game"));
		gobbler.infoboxes   = [];
		gobbler.infoboxKeys = new Set();

		// Process game button templates
		gobbler.games.forEach(function(game) {
			game.gameId   = getGameId(game);
			game.tabIndex = 0;
			game.classList.add("inactive");
			game.remove();
		});

		return gobbler;
	};
	
	// Process a newly discovered infobox
	var newInfobox = function(infobox) {

		// Configure instance fields
		infobox.gameId = getGameId(infobox);

		// Prevent lazy loading of any contained images
		Array.from(infobox.querySelectorAll("[data-src]")).forEach(function(lazy) {
			var unlazy = document.createElement("img");
			unlazy.src = lazy.getAttribute("data-src");
			unlazy.setAttribute("style", lazy.getAttribute("style"));
			lazy.before(unlazy);
			lazy.remove();
		});

		return infobox;
	};
	
	// Configure styles on game buttons
	var setButtonStyles = function(button) {
		button.gobbler.buttons.forEach(function(btn) {

			// Do not display any buttons unless there are two or more
			if (button.gobbler.buttons.length < 2) {
				btn.classList.remove("active");
				btn.classList.remove("inactive");
				return;
			}
			
			// Configure styles on visible buttons
			var current = button.gobbler.buttons.length > 1 && btn == button;
			btn.classList[ current ? "add" : "remove"]("active"  );
			btn.classList[!current ? "add" : "remove"]("inactive");
		})	
	};

	// Specify the selected game
	var setGame = function(e) {
		
		// Ignore this event
		if (
			e.type != "keydown" && e.type != "pointerdown" ||
			e.type == "keydown" && e.key != " " && e.key != "Enter" ||
			e.type == "pointerdown" && e.button != 0 // Left click only
		) return;

		// Prevent the default user agent behavior
		if (e.preventDefault) {
			e.preventDefault();
			e.stopPropagation();
		}

		// Identify the button element
		var button = e.target;
		for (; button && !button.gobbler; button = button.parentNode);
		if (!button || !button.gobbler)
			return; // Failsafe

		// Configure button styles
		setButtonStyles(button);

		// Configure infobox visibility
		button.gobbler.infoboxes.forEach(function(infobox) {
			infobox.classList[infobox == button.infobox ? "remove" : "add"]("inactive");
		});
	};

	// Monitor for new gobblers and infoboxes
	var interval = setInterval(function() {

		// Find new gobblers and infoboxes
		Array.from(document.querySelectorAll(".infobox-gobbler"))
			.filter(function(g) { return gobblers.indexOf(g) == -1; })
			.forEach(function(g) { gobblers.push(newGobbler(g)); })
		;
		Array.from(document.querySelectorAll("*:not(.infobox-gobbler) > .infobox"))
			.forEach(function(i) { infoboxes.push(newInfobox(i)); })
		;

		// Process all gobblers
		gobblers.forEach(function(gobbler) {
			
			// Process all infoboxes
			infoboxes.forEach(function(infobox) {
				addInfobox(gobbler, infobox);
			});
			
		});

		// Stop watching for new infoboxes
		if (document.readyState == "complete")
			clearInterval(interval);
	}, 100);
})();

////////////////////////////////////////////////////////////////////////////////