MediaWiki:Common.js: Difference between revisions

From Inkipedia, the Splatoon wiki
(Removed no longer used DarkMode poll code)
(Removed no longer used InteractiveMusic code)
Line 561: Line 561:
     })
     })
})
})
// ================================================================================
// Interactive Music Code
// ================================================================================
// Set an onclick handler for opening the filters tab
$(".i-menu-filter").click(function(){
if($(this).next().css("display") == "none"){
$(this).next().css("display", "block")
$(this).css("background", "#aaaaaa")
}
else{
$(this).next().css("display", "none")
$(this).css("background", "")
}
})
// Set an onclick handler for checking boxes
$(".i-checkbox").click(function(){
if($(this).children().css("opacity") == "0"){
$(this).children().css("opacity", "1")
if($(this).hasClass("i-category")){
icheckboxes();
}
}
else{
$(this).children().css("opacity", "0")
if($(this).hasClass("i-category")){
icheckboxes();
}
}
})
// Handle refreshing the music list based on selected filters
function icheckboxes(){
    var allclass = [];
    var classStatus = {};
    $(".i-checkbox.i-category").each(function(){
        var classes = $(this)[0].classList;
        for(var i = 0, len = classes.length;i<len;i++){
            var curr = classes[i];
            if(curr.startsWith("i-category-")){
                if(allclass.indexOf(curr) == -1){
                    allclass[allclass.length] = curr;
                    classStatus[curr] = $(this).children().css("opacity");
                }
            }
        }
    })
    var select = "." + allclass.join(", .");
    $(select).each(function(){
        if($(this).hasClass("i-checkbox")){
            return;
        }
        var classes = $(this)[0].classList;
        var status = 1;
        for(var i = 0, len = classes.length;i<len;i++){
            var curr = classes[i];
            if(curr.startsWith("i-category-")){
                if(classStatus[curr] == 0){
                    status = 0;
                }
            }
        }
        if(status == 1){
            status = ""
        }
        else{
            status = "none"
        }
        $(this).css("display", status)
    })
}


// ================================================================================
// ================================================================================

Revision as of 17:53, 26 December 2020

/* Any JavaScript here will be loaded for all users on every page load. */

/* strawpoll */
mw.loader.load('//splatoonwiki.org/wiki/MediaWiki:StrawpollIntegrator.js?action=raw&ctype=text/javascript')

// ================================================================================
// Pikan -- the analyzer on the edit page
// ================================================================================
$.getScript('//www.pikminwiki.com/index.php?title=User:Espyo/pikan-core.js'
+ '&action=raw&ctype=text/javascript', function () {
  mw.loader.load('//splatoonwiki.org/w/index.php?title=User:Espyo/pikan-inkipedia.js'
  + '&action=raw&ctype=text/javascript');
});

// ================================================================================
// 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());
});

// ================================================================================
// Injection into Upload Wizard for custom licensing and categories
// ================================================================================
//https://commons.wikimedia.org/wiki/Commons:Upload_Wizard/Editing#License_options
//https://www.mediawiki.org/wiki/Extension:UploadWizard/CustomWikiTextLicenseFeature

if ("UploadWizard" === mw.config.get('wgCanonicalSpecialPageName')) {
  mw.loader.using('ext.uploadWizard', function () {
    try {
      var autoAdd = mw.config.get('UploadWizardConfig').autoAdd;
 
      // Add a custom category to all my upload wizard uploads
      // autoAdd.categories.push("Images");

      // Add a custom license tag to all my files uploaded using the sorcerer
      autoAdd.wikitext += "{{fairuse}}";
 
    } catch (ex) {
      mw.log("Injecting custom-category into UploadWizard failed.", ex);
    }
  });
}



///////////////////////////////////////////////////////////////////////////////
//                               Gobbler Class                               //
///////////////////////////////////////////////////////////////////////////////

// Folds all infoboxes into a single, tabbed element

// Object constructor
var Gobbler = function() {

    // Check for a gobbler object in the document
    this.element = document.getElementById("gobbler");
    if (!this.element) return;

    // Gobble all of the infoboxes on the page
    var elements = document.getElementsByClassName("tagInfobox");
    var tabs     = [];
    for (var x = 0; x < elements.length; x++)
        tabs.push(this.gobble(elements[x]));
    this.onTab(tabs[tabs.length - 1]);

    // Add the infoboxes and tab buttons to the gobbler element
    var tabStrip = document.createElement("div");
    tabStrip.style.textAlign = "left";
    if (tabs.length > 1)
        this.element.appendChild(tabStrip);
    for (var x = 0; x < tabs.length; x++) {
        tabStrip.appendChild(tabs[x].button);
        this.element.appendChild(tabs[x].element);
    }

};

// Process an infobox
Gobbler.prototype.gobble = function(element) {

    // Retrieve the infobox's hidden tab button element
    var button = element.getElementsByClassName("tagTab")[0];

    // Create a tab object using the contents of the element
    var tab = {
        button:        button,
        colorInactive: button.style.backgroundColor,
        colorActive:   button.style.outlineColor,
        element:       element,
        cssText:       element.style.cssText
    };

    // Configure element properties
    var that = this;
    element.style.cssText = "width: 100%; height: 100%; display: none;";
    button.style.display  = "inline";
    button.onmousedown    = function(e) {
        if (e.button == 0) that.onTab(tab);
    };
    return tab;
};

// Event handler for when a tab is selected/clicked
Gobbler.prototype.onTab = function(tab) {

    // Deselect the previous infobox
    if (this.current) {
        this.current.button.style.fontWeight      = "normal";
        this.current.button.style.backgroundColor = this.current.colorInactive;
        this.current.element.style.display        = "none";
    }

    // Select the new infobox
    this.element.style.cssText       = tab.cssText;
    tab.button.style.fontWeight      = "bold";
    tab.element.style.display        = "inline";
    tab.button.style.backgroundColor = tab.colorActive;
    this.current                     = tab;
};

new Gobbler();



///////////////////////////////////////////////////////////////////////////////
//                        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() {

    // Initialize instance fields
    var now = new Date();
    this.slots = [
        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
    var that = this;
    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)
// ================================================================================
// Made by FunPL with slight help from google

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]);
    })
}

onApiReady(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");
    })
})

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

//Define vars
var mwSkin = mw.config.get("skin")
var pageTypes = {};
pageTypes.globalJS = 'MediaWiki:Common.js/' + mw.config.get("wgPageName") + ".js";
pageTypes.globalCSS = 'MediaWiki:Common.css/' + mw.config.get("wgPageName") + ".css";
pageTypes.skinJS = 'MediaWiki:' + mwSkin + '.js/' + mw.config.get("wgPageName") + ".js";
pageTypes.skinCSS = 'MediaWiki:' + mwSkin + '.css/' + mw.config.get("wgPageName") + ".css";
pageTypes.userJS = 'User:' + mw.config.get("wgUserName") + '/common.js/' + mw.config.get("wgPageName") + ".js";
pageTypes.userCSS = 'User:' + mw.config.get("wgUserName") + '/common.css/' + mw.config.get("wgPageName") + ".css";
pageTypes.userSkinJS = 'User:' + mw.config.get("wgUserName") + '/' + mwSkin + '.js/' + mw.config.get("wgPageName") + ".js";
pageTypes.userSkinCSS = 'User:' + mw.config.get("wgUserName") + '/' + mwSkin + '.css/' + mw.config.get("wgPageName") + ".css";

var url_string = window.location.href;
var url = new URL(url_string);
var action = url.searchParams.get("action");
var disable_page_js = url.searchParams.get("disable-page-js");

function checkPage(type, exist) {
    if (exist == null) {
        new mw.Api().get({
            action: 'parse',
            page: type,
            prop: ""
        }).done(function (data) {
            checkPage(type, true)
        }).fail(function (data) {
            checkPage(type, false)
        });
    }
    else if (exist) {
        if (getTypeByValue(type).endsWith("JS") && disable_page_js == null) {
            mw.loader.load('/w/index.php?title=' + pageEnc(type) + '&action=raw&ctype=text/javascript');
        }
        else {
            mw.loader.load('/w/index.php?title=' + pageEnc(type) + '&action=raw&ctype=text/css', 'text/css');
        }
        checkNextPage(type)
    }
    else if (!exist) {
       checkNextPage(type)
    }
}

function checkNextPage(type) {
    switch (type) {
        case pageTypes.globalJS:
            checkPage(pageTypes.skinJS);
            break;
        case pageTypes.globalCSS:
            checkPage(pageTypes.globalJS);
            break;
        case pageTypes.skinJS:
            checkPage(pageTypes.skinCSS);
            break;
        case pageTypes.skinCSS:
            if (mw.config.get("wgUserName") != null) {
                checkPage(pageTypes.userJS);
            }
            break;
        case pageTypes.userJS:
            if (mw.config.get("wgUserName") != null) {
                checkPage(pageTypes.userCSS);
            }
            break;
        case pageTypes.userCSS:
            if (mw.config.get("wgUserName") != null) {
                checkPage(pageTypes.userSkinJS);
            }
            break;
        case pageTypes.userSkinJS:
            if (mw.config.get("wgUserName") != null) {
                checkPage(pageTypes.userSkinCSS);
            }
            break;
    }
}

function pageEnc(string) {
    return encodeURIComponent(string);
}

function getTypeByValue(value) {
    return Object.keys(pageTypes).find(function (key) { return pageTypes[key] === value });
}

//Check page specific code
if(action === null || action === "view" || action === "submit"){
    onApiReady(function () {
        checkPage(pageTypes.globalCSS);
    });
}

function onApiReady(callback) {
    if (mw.Api != null) {
        setTimeout(callback, 0);
    } else {
        setTimeout(onApiReady.bind(null, callback), 0);
    }
}

// ================================================================================
// Warning message in console to help prevent scams
// ================================================================================
setTimeout(function () {
    console.log("%cHold up squiddo!",
        "color:rgb(170, 220, 0);font-family:Splatoon2, sans-serif;font-size:40px")
    console.log("%cIf someone told you to paste something here, there is a 99% chance you're getting scammed!",
        "font-family:Splatoon2, sans-serif;font-size:15px")
    console.log("%cPasting anything here could give scammers access to your account",
        "color:red;font-family:Splatoon2, sans-serif;font-size:15px")
    console.log("%cUnless you know what you're doing, don't paste anything here!",
        "font-family:Splatoon2, sans-serif;font-size:25px")
}, 1500)