MediaWiki:All.js: Difference between revisions
From Inkipedia, the Splatoon wiki
GuyPerfect (talk | contribs) m (Troubleshooting) |
GuyPerfect (talk | contribs) m (Troubleshooting) |
||
Line 618: | Line 618: | ||
// Add an infobox to a gobbler | // Add an infobox to a gobbler | ||
var addInfobox = function(gobbler, infobox) { | var addInfobox = function(gobbler, infobox) { | ||
// The infobox is already gobbled | // The infobox is already gobbled | ||
if (gobbler.infoboxKeys.has(infobox)) | if (gobbler.infoboxKeys.has(infobox)) | ||
Line 741: | Line 741: | ||
// Find new gobblers and infoboxes | // Find new gobblers and infoboxes | ||
Array.from(document.querySelectorAll(".infobox-gobbler")) | Array.from(document.querySelectorAll(".infobox-gobbler")) | ||
.filter(function(g) { return gobblers.indexOf(g) | .filter(function(g) { return gobblers.indexOf(g) == -1; }) | ||
.forEach(function(g) { gobblers.push(newGobbler(g)); }) | .forEach(function(g) { gobblers.push(newGobbler(g)); }) | ||
; | ; |
Revision as of 16:20, 7 October 2022
/* 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') // ================================================================================ // 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(); // ================================================================================ // 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.gobbler = gobbler; button.addEventListener("keydown", setGame); button.addEventListener("pointerdown", setGame); gobbler.gameList.append(button); gobbler.buttons.push(button); gobbler.buttons.forEach(function(btn) { btn.classList[ gobbler.buttons.length == 1 || btn != gobbler.buttons[gobbler.buttons.length - 1] ? "remove" : "add"]("inactive"); }); // 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.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); infobox.remove(); // 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; }; // 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 e.preventDefault(); e.stopPropagation(); // Identify the button element var button = e.target; for (; button && !button.gobbler; button = button.parentNode); if (!button) return; // Failsafe // Configure button visibility button.gobbler.buttons.forEach(function(btn) { var current = button.gobbler.buttons.length > 1 && btn != button; button.classList[ current ? "add" : "remove"]("active" ); button.classList[!current ? "add" : "remove"]("inactive"); }); // 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); })(); ////////////////////////////////////////////////////////////////////////////////