MediaWiki:Common.js: Difference between revisions

From PSP2i Wiki
No edit summary
Tag: Reverted
No edit summary
Tag: Manual revert
 
(5 intermediate revisions by the same user not shown)
Line 1: Line 1:
/* Any JavaScript here will be loaded for all users on every page load. */
/* Any JavaScript here will be loaded for all users on every page load. */
(function($, mw) {
    'use strict';
    var hasLocalStorage = function(){
        try {
            localStorage.setItem('test', 'test')
            localStorage.removeItem('test')
            return true
        } catch (e) {
            return false
        }
    }
    // constants
    var STORAGE_KEY = 'mi:lightTable',
        TABLE_CLASS = 'lighttable',
        LIGHT_ON_CLASS = 'highlight-on',
        MOUSE_OVER_CLASS = 'highlight-over',
        BASE_64_URL = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_',
        PAGE_SEPARATOR = '!',
        TABLE_SEPARATOR = '.',
        CASTAGNOLI_POLYNOMIAL = 0x04c11db7,
        UINT32_MAX = 0xffffffff,
        self = {
            /*
            * Stores the current uncompressed data for the current page.
            */
            data: null,
            /*
            * Perform initial checks on the page and browser.
            */
            init: function() {
                var $tables = $('table.' + TABLE_CLASS),
                    hashedPageName = self.hashString(mw.config.get('wgPageName'));
                // check we have some tables to interact with
                if (!$tables.length) {
                    return;
                }
                // check the browser supports local storage
                if (!hasLocalStorage()) {
                    return;
                }
                self.data = self.load(hashedPageName, $tables.length);
                self.initTables(hashedPageName, $tables);
            },
            /*
            * Initialise table highlighting.
            *
            * @param hashedPageName The current page name as a hash.
            * @param $tables A list of highlightable tables on the current page.
            */
            initTables: function(hashedPageName, $tables) {
                $tables.each(function(tIndex) {
                    var $this = $(this),
                        // data cells
                        $cells = $this.find('td'),
                        $rows = $this.find('tr:has(td)'),
                        // don't rely on headers to find number of columns     
                        // count them dynamically
                        columns = 1,
                        tableData = self.data[tIndex],
                        mode = 'cells';
                    // Switching between either highlighting rows or cells
                    if (!$this.hasClass('individual')) {
                        mode = 'rows';
                        $cells = $rows;
                    }
                    // initialise rows if necessary
                    while ($cells.length > tableData.length) {
                        tableData.push(0);
                    }
                    // counting the column count
                    // necessary to determine colspan of reset button
                    $rows.each(function() {
                        var $this = $(this);
                        columns = Math.max(columns, $this.children('th,td').length);
                    });
                    $cells.each(function(cIndex) {
                        var $this = $(this),
                            cellData = tableData[cIndex];
                        // forbid highlighting any cells/rows that have class nohighlight
                        if (!$this.hasClass('nohighlight')) {
                            // initialize highlighting based on the cookie
                            self.setHighlight($this, cellData);
                            // set mouse events
                            $this
                                .mouseover(function() {
                                    self.setHighlight($this, 2);
                                })
                                .mouseout(function() {
                                    self.setHighlight($this, tableData[cIndex]);
                                })
                                .click(function(e) {
                                    // don't toggle highlight when clicking links
                                    if ((e.target.tagName !== 'A') && (e.target.tagName !== 'IMG')) {
                                        // 1 -> 0
                                        // 0 -> 1
                                        tableData[cIndex] = 1 - tableData[cIndex];
                                        self.setHighlight($this, tableData[cIndex]);
                                        self.save(hashedPageName);
                                    }
                                });
                        }
                    });
                    // add a button for reset
                    var button = new OO.ui.ButtonWidget({
                        label: 'Clear selection',
                        icon: 'clear',
                        title: 'Removes all highlights from the table',
                        classes: ['ht-reset'] // this class is targeted by other gadgets, be careful removing it
                    });
                    button.$element.click(function() {
                        $cells.each(function(cIndex) {
                            tableData[cIndex] = 0;
                            self.setHighlight($(this), 0);
                        });
                        self.save(hashedPageName, $tables.length);
                    });
                    $this.append(
                        $('<tfoot>')
                            .append(
                                $('<tr>')
                                    .append(
                                        $('<th>')
                                            .attr('colspan', columns)
                                            .append(button.$element)
                                    )
                            )
                    );
                });
            },
            /*
            * Change the cell background color based on mouse events.
            *
            * @param $cell The cell element.
            * @param val The value to control what class to add (if any).
            *            0 -> light off (no class)
            *            1 -> light on
            *            2 -> mouse over
            */
            setHighlight: function($cell, val) {
                $cell.removeClass(MOUSE_OVER_CLASS);
                $cell.removeClass(LIGHT_ON_CLASS);
                switch (val) {
                    // light on
                    case 1:
                        $cell.addClass(LIGHT_ON_CLASS);
                        break;
                    // mouse-over
                    case 2:
                        $cell.addClass(MOUSE_OVER_CLASS);
                        break;
                }
            },
            /*
            * Merge the updated data for the current page into the data for other pages into local storage.
            *
            * @param hashedPageName A hash of the current page name.
            */
            save: function(hashedPageName) {
                // load the existing data so we know where to save it
                var curData = localStorage.getItem(STORAGE_KEY),
                    compressedData;
                if (curData === null) {
                    curData = {};
                } else {
                    curData = JSON.parse(curData);
                    curData = self.parse(curData);
                }
                // merge in our updated data and compress it
                curData[hashedPageName] = self.data;
                compressedData = self.compress(curData);
                // convert to a string and save to localStorage
                compressedData = JSON.stringify(compressedData);
                localStorage.setItem(STORAGE_KEY, compressedData);
            },
            /*
            * Compress the entire data set using tha algoritm documented at the top of the page.
            *
            * @param data The data to compress.
            *
            * @return the compressed data.
            */
            compress: function(data) {
                var ret = {};
                Object.keys(data).forEach(function(hashedPageName) {
                    var pageData = data[hashedPageName],
                        pageKey = hashedPageName.charAt(0);
                    if (!ret.hasOwnProperty(pageKey)) {
                        ret[pageKey] = {};
                    }
                    ret[pageKey][hashedPageName] = [];
                    pageData.forEach(function(tableData) {
                        var compressedTableData = '',
                            i, j, k;
                        for (i = 0; i < Math.ceil(tableData.length / 6); i += 1) {
                            k = tableData[6 * i];
                            for (j = 1; j < 6; j += 1) {
                                k = 2 * k + ((6 * i + j < tableData.length) ? tableData[6 * i + j] : 0);
                            }
                            compressedTableData += BASE_64_URL.charAt(k);
                        }
                        ret[pageKey][hashedPageName].push(compressedTableData);
                    });
                    ret[pageKey][hashedPageName] = ret[pageKey][hashedPageName].join(TABLE_SEPARATOR);
                });
                Object.keys(ret).forEach(function(pageKey) {
                    var hashKeys = Object.keys(ret[pageKey]),
                        hashedData = [];
                    hashKeys.forEach(function(key) {
                        var pageData = ret[pageKey][key];
                        hashedData.push(key + pageData);
                    });
                    hashedData = hashedData.join(PAGE_SEPARATOR);
                    ret[pageKey] = hashedData;
                });
                return ret;
            },
            /*
            * Get the existing data for the current page.
            *
            * @param hashedPageName A hash of the current page name.
            * @param numTables The number of tables on the current page. Used to ensure the loaded
            *                  data matches the number of tables on the page thus handling cases
            *                  where tables have been added or removed. This does not check the
            *                  amount of rows in the given tables.
            *
            * @return The data for the current page.
            */
            load: function(hashedPageName, numTables) {
                var data = localStorage.getItem(STORAGE_KEY),
                    pageData;
                if (data === null) {
                    pageData = [];
                } else {
                    data = JSON.parse(data);
                    data = self.parse(data);
                    if (data.hasOwnProperty(hashedPageName)) {
                        pageData = data[hashedPageName];
                    } else {
                        pageData = [];
                    }
                }
                // if more tables were added
                // add extra arrays to store the data in
                // also populates if no existing data was found
                while (numTables > pageData.length) {
                    pageData.push([]);
                }
                // if tables were removed, remove data from the end of the list
                // as there's no way to tell which was removed
                while (numTables < pageData.length) {
                    pageData.pop();
                }
                return pageData;
            },
            /*
            * Parse the compressed data as loaded from local storage using the algorithm desribed
            * at the top of the page.
            *
            * @param data The data to parse.
            *
            * @return the parsed data.
            */
            parse: function(data) {
                var ret = {};
                Object.keys(data).forEach(function(pageKey) {
                    var pageData = data[pageKey].split(PAGE_SEPARATOR);
                    pageData.forEach(function(tableData) {
                        var hashedPageName = tableData.substr(0, 8);
                        tableData = tableData.substr(8).split(TABLE_SEPARATOR);
                        ret[hashedPageName] = [];
                        tableData.forEach(function(rowData, index) {
                            var i, j, k;
                            ret[hashedPageName].push([]);
                            for (i = 0; i < rowData.length; i += 1) {
                                k = BASE_64_URL.indexOf(rowData.charAt(i));
                                // input validation
                                if (k < 0) {
                                    k = 0;
                                }
                                for (j = 5; j >= 0; j -= 1) {
                                    ret[hashedPageName][index][6 * i + j] = (k & 0x1);
                                    k >>= 1;
                                }
                            }
                        });
                    });
                });
                return ret;
            },
            /*
            * Hash a string into a big endian 32 bit hex string. Used to hash page names.
            *
            * @param input The string to hash.
            *
            * @return the result of the hash.
            */
            hashString: function(input) {
                var ret = 0,
                    table = [],
                    i, j, k;
                // guarantee 8-bit chars
                input = window.unescape(window.encodeURI(input));
                // calculate the crc (cyclic redundancy check) for all 8-bit data
                // bit-wise operations discard anything left of bit 31
                for (i = 0; i < 256; i += 1) {
                    k = (i << 24);
                    for (j = 0; j < 8; j += 1) {
                        k = (k << 1) ^ ((k >>> 31) * CASTAGNOLI_POLYNOMIAL);
                    }
                    table[i] = k;
                }
                // the actual calculation
                for (i = 0; i < input.length; i += 1) {
                    ret = (ret << 8) ^ table[(ret >>> 24) ^ input.charCodeAt(i)];
                }
                // make negative numbers unsigned
                if (ret < 0) {
                    ret += UINT32_MAX;
                }
                // 32-bit hex string, padded on the left
                ret = '0000000' + ret.toString(16).toUpperCase();
                ret = ret.substr(ret.length - 8);
                return ret;
            }
        };
    $(self.init);
    /*
    // sample data for testing the algorithm used
    var data = {
        // page1
        '0FF47C63': [
            [0, 1, 1, 0, 1, 0],
            [0, 1, 1, 0, 1, 0, 1, 1, 1],
            [0, 0, 0, 0, 1, 1, 0, 0]
        ],
        // page2
        '02B75ABA': [
            [0, 1, 0, 1, 1, 0],
            [1, 1, 1, 0, 1, 0, 1, 1, 0],
            [0, 0, 1, 1, 0, 0, 0, 0]
        ],
        // page3
        '0676470D': [
            [1, 0, 0, 1, 0, 1],
            [1, 0, 0, 1, 0, 1, 0, 0, 0],
            [1, 1, 1, 1, 0, 0, 1, 1]
        ]
    };
    console.log('input', data);
    var compressedData = self.compress(data);
    console.log('compressed', compressedData);
    var parsedData = self.parse(compressedData);
    console.log(parsedData);
    */
}(this.jQuery, this.mediaWiki));
// </pre>
//[This is the end of the section stolen from https://oldschool.runescape.wiki/w/MediaWiki:Gadget-highlightTable-core.js]
/* Sets the top property for stickyHeader tables */
function setStickyHeaderTop() {
  const stickyTables = document.getElementsByClassName('stickyHeader');
  const headHeight = document.getElementById('mw-header-container').offsetHeight;
  for (var i = 0; i < stickyTables.length; i++) {
    const firstRow = stickyTables[i].getElementsByClassName('headerRow-0');
    const secondRow = stickyTables[i].getElementsByClassName('headerRow-1');
    var firstHeight = 0;
    if (firstRow.length > 0) {
      firstHeight = firstRow[0].offsetHeight;
      const firstHeaders = firstRow[0].getElementsByTagName('th');
      for (var j = 0; j < firstHeaders.length; j++) {
        firstHeaders[j].style.top = headHeight + 'px';
      }
      if (secondRow.length > 0) {
        const secondHeaders = secondRow[0].getElementsByTagName('th');
        var secondHeight = headHeight + firstHeight;
        for (var j = 0; j < secondHeaders.length; j++) {
          secondHeaders[j].style.top = secondHeight + 'px';
        }
      }
    }
  }
}
function toggleDarkMode() {
document.body.classList.toggle('darkMode');
localStorage.setItem('darkMode', localStorage.getItem('darkMode') !== 'true');
}
$(document).ready(function () {
if (document.getElementsByClassName('stickyHeader').length > 0) {
setStickyHeaderTop();
$(window).resize(setStickyHeaderTop);
}
const darkMode = localStorage.getItem('darkMode');
if (darkMode === 'true') {
document.body.classList.add('darkMode');
} else {
document.body.classList.remove('darkMode');
}
});


/* Collapsible tables; reimplemented with mw-collapsible
/* Collapsible tables; reimplemented with mw-collapsible
Line 553: Line 79:
     }
     }
});
});
// FloatingTableHeader.js
// From https://archive.is/zJzTe,
// with very slight modifications
// ported and adapted for Wikia Oasis by mfaizsyahmi
// ---
// Heavily modified by Paradox-
//---------------------------------------- Floatheader
$("table.floatheader").each(function() {
  $(this).wrap("<div class=\"divTableWithFloatingHeader\" style=\"position:relative\"></div>");
 
  //--- If there is no thead, put all header rows into a thead
  //--- Code taken from jQuery tablesorter (& modified)
  //--- https://phabricator.wikimedia.org/source/mediawiki/browse/master/resources/src/jquery.tablesorter/jquery.tablesorter.js$283
  if (!$("thead", this).length) {
    var thead = $("<thead>");
    $("> tbody > tr", this).each(function() {
      if ($("> td", this).length) {
        return false;
      }
      thead.append(this);
    });
    $("> tbody", this).before(thead);
  }
 
  var originalHeader = $("thead", this);
  var clonedHeader = originalHeader.clone().hide();
  originalHeader.after(clonedHeader);
 
  originalHeader.addClass("tableFloatingHeaderOriginal");
  clonedHeader.addClass("tableFloatingHeader");
  clonedHeader.css("position", "absolute");
  clonedHeader.css("top", "0");
  clonedHeader.css("left", $(this).css("margin-left"));
 
  //--- Floating Header cell fixes
  $("th", clonedHeader).each(function() {
    //--- Remove Sort Buttons
    if ($(this).hasClass("headerSort")) {
      $(this).removeClass("headerSort").removeAttr("tabindex").removeAttr("title");
    }
   
    //--- Remove mw-collapsible Toggle Button
    $(".mw-collapsible-toggle", this).remove();
   
    //--- Remove Transparency
    var bgcolors = $(this).css("background-color");
    if (bgcolors.charAt(3) == "a" && bgcolors != "rgba(0, 0, 0, 0)") {
      $(this).css("background-color", "rgb(" + bgcolors.match(/\d+/g).slice(0, 3).join(", ") + ")");
    }
  });
});
UpdateTableHeaders();
$(window).scroll(UpdateTableHeaders).resize(UpdateTableHeaders);
function UpdateTableHeaders() {
  $("div.divTableWithFloatingHeader").each(function() {
    var offset = $(this).offset();
    var scrollTop = $(window).scrollTop();
    var floatingHeader = $(".tableFloatingHeader", this);
    var topNavOffset = $(".fandom-sticky-header").height() || $(".global-navigation").height() || 0;
    console.log(topNavOffset)
    if (scrollTop > offset.top - topNavOffset && scrollTop + topNavOffset < offset.top + $(this).height()) {
      var originalHeader = $(".tableFloatingHeaderOriginal", this);
     
      floatingHeader.css("top", Math.min(scrollTop - offset.top + topNavOffset, $(this).height() - floatingHeader.height() - $("tr:last", this).height()) + "px").show();
     
      /* hack for cellpadding and cellspacing attributes: tr's width is increased by 2*cellspacing, and each header cell is reduced by 2*cellpadding */
      var insidetable = $(this).find($(".floatheader")).first();
      var cellspacing = +$(insidetable).attr("cellspacing") || 0;
      var cellpadding = +$(insidetable).attr("cellpadding") || 0;
     
      //--- Copy cell width & horizontal padding from original header -- only way to make the cells obey their width
      $("th", floatingHeader).each(function(i) {
        var oh = $("th", originalHeader).eq(i);
        $(this).width((Number(oh.css("width").replace("px","")) - 2*cellpadding) + "px");
        $(this).css("padding-left", oh.css("padding-left"));
        $(this).css("padding-right", oh.css("padding-right"));
      });
     
      //--- Copy width from original thead -- Add 1px to wikitables
      floatingHeader.css("width", originalHeader.width() + 2*cellspacing + +$("> table", this).hasClass("wikitable") + "px");
    }
    else {
      floatingHeader.hide();
    }
  });
}

Latest revision as of 17:48, 28 May 2022

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

/* Collapsible tables; reimplemented with mw-collapsible
	 * Styling is also in place to avoid FOUC
	 *
	 * Allows tables to be collapsed, showing only the header. See [[Help:Collapsing]].
	 * @version 3.0.0 (2018-05-20)
	 * @source https://www.mediawiki.org/wiki/MediaWiki:Gadget-collapsibleTables.js
	 * @author [[User:R. Koot]]
	 * @author [[User:Krinkle]]
	 * @author [[User:TheDJ]]
	 * @deprecated Since MediaWiki 1.20: Use class="mw-collapsible" instead which
	 * is supported in MediaWiki core. Shimmable since MediaWiki 1.32
	 *
	 * @param {jQuery} $content
	 * */
	function makeCollapsibleMwCollapsible( $content ) {
		var $tables = $content
			.find( 'table.collapsible:not(.mw-collapsible)' )
			.addClass( 'mw-collapsible' );

		$.each( $tables, function ( index, table ) {
			// mw.log.warn( 'This page is using the deprecated class collapsible. Please replace it with mw-collapsible.');
			if ( $( table ).hasClass( 'collapsed' ) ) {
				$( table ).addClass( 'mw-collapsed' );
				// mw.log.warn( 'This page is using the deprecated class collapsed. Please replace it with mw-collapsed.');
			}
		} );
		if ( $tables.length > 0 ) {
			mw.loader.using( 'jquery.makeCollapsible' ).then( function () {
				$tables.makeCollapsible();
			} );
		}
	}
	mw.hook( 'wikipage.content' ).add( makeCollapsibleMwCollapsible );

	/**
	 * Add support to mw-collapsible for autocollapse, innercollapse and outercollapse
	 *
	 * Maintainers: TheDJ
	 */
	function mwCollapsibleSetup( $collapsibleContent ) {
		var $element,
			$toggle,
			autoCollapseThreshold = 2;
		$.each( $collapsibleContent, function ( index, element ) {
			$element = $( element );
			if ( $element.hasClass( 'collapsible' ) ) {
				$element.find( 'tr:first > th:first' ).prepend( $element.find( 'tr:first > * > .mw-collapsible-toggle' ) );
			}
			if ( $collapsibleContent.length >= autoCollapseThreshold && $element.hasClass( 'autocollapse' ) ) {
				$element.data( 'mw-collapsible' ).collapse();
			} else if ( $element.hasClass( 'innercollapse' ) ) {
				if ( $element.parents( '.outercollapse' ).length > 0 ) {
					$element.data( 'mw-collapsible' ).collapse();
				}
			}
			// because of colored backgrounds, style the link in the text color
			// to ensure accessible contrast
			$toggle = $element.find( '.mw-collapsible-toggle' );
			if ( $toggle.length ) {
				// Make the toggle inherit text color
				if ( $toggle.parent()[ 0 ].style.color ) {
					$toggle.find( 'a' ).css( 'color', 'inherit' );
				}
			}
		} );
	}

	mw.hook( 'wikipage.collapsibleContent' ).add( mwCollapsibleSetup );


$(".more_info").click(function () {
    var $title = $(this).find(".title");
    if (!$title.length) {
        $(this).append('<span class="title">' + $(this).attr("title") + '</span>');
    } else {
        $title.remove();
    }
});

// FloatingTableHeader.js
// From https://archive.is/zJzTe, 
// with very slight modifications
// ported and adapted for Wikia Oasis by mfaizsyahmi
// ---
// Heavily modified by Paradox-

//---------------------------------------- Floatheader

$("table.floatheader").each(function() {
  $(this).wrap("<div class=\"divTableWithFloatingHeader\" style=\"position:relative\"></div>");
  
  //--- If there is no thead, put all header rows into a thead
  //--- Code taken from jQuery tablesorter (& modified)
  //--- https://phabricator.wikimedia.org/source/mediawiki/browse/master/resources/src/jquery.tablesorter/jquery.tablesorter.js$283
  if (!$("thead", this).length) {
    var thead = $("<thead>");
    $("> tbody > tr", this).each(function() {
      if ($("> td", this).length) {
        return false;
      }
      thead.append(this);
    });
    $("> tbody", this).before(thead);
  }
  
  var originalHeader = $("thead", this);
  var clonedHeader = originalHeader.clone().hide();
  originalHeader.after(clonedHeader);
  
  originalHeader.addClass("tableFloatingHeaderOriginal");
  clonedHeader.addClass("tableFloatingHeader");
  clonedHeader.css("position", "absolute");
  clonedHeader.css("top", "0");
  clonedHeader.css("left", $(this).css("margin-left"));
  
  //--- Floating Header cell fixes
  $("th", clonedHeader).each(function() {
    //--- Remove Sort Buttons
    if ($(this).hasClass("headerSort")) {
      $(this).removeClass("headerSort").removeAttr("tabindex").removeAttr("title");
    }
    
    //--- Remove mw-collapsible Toggle Button
    $(".mw-collapsible-toggle", this).remove();
    
    //--- Remove Transparency
    var bgcolors = $(this).css("background-color");
    if (bgcolors.charAt(3) == "a" && bgcolors != "rgba(0, 0, 0, 0)") {
      $(this).css("background-color", "rgb(" + bgcolors.match(/\d+/g).slice(0, 3).join(", ") + ")");
    }
  });
});

UpdateTableHeaders();
$(window).scroll(UpdateTableHeaders).resize(UpdateTableHeaders);

function UpdateTableHeaders() {
  $("div.divTableWithFloatingHeader").each(function() {
    var offset = $(this).offset();
    var scrollTop = $(window).scrollTop();
    var floatingHeader = $(".tableFloatingHeader", this);
    var topNavOffset = $(".fandom-sticky-header").height() || $(".global-navigation").height() || 0;
    console.log(topNavOffset)
    if (scrollTop > offset.top - topNavOffset && scrollTop + topNavOffset < offset.top + $(this).height()) {
      var originalHeader = $(".tableFloatingHeaderOriginal", this);
      
      floatingHeader.css("top", Math.min(scrollTop - offset.top + topNavOffset, $(this).height() - floatingHeader.height() - $("tr:last", this).height()) + "px").show();
      
      /* hack for cellpadding and cellspacing attributes: tr's width is increased by 2*cellspacing, and each header cell is reduced by 2*cellpadding */
      var insidetable = $(this).find($(".floatheader")).first();
      var cellspacing = +$(insidetable).attr("cellspacing") || 0;
      var cellpadding = +$(insidetable).attr("cellpadding") || 0;
      
      //--- Copy cell width & horizontal padding from original header -- only way to make the cells obey their width
      $("th", floatingHeader).each(function(i) {
        var oh = $("th", originalHeader).eq(i);
        $(this).width((Number(oh.css("width").replace("px","")) - 2*cellpadding) + "px");
        $(this).css("padding-left", oh.css("padding-left"));
        $(this).css("padding-right", oh.css("padding-right"));
      });
      
      //--- Copy width from original thead -- Add 1px to wikitables
      floatingHeader.css("width", originalHeader.width() + 2*cellspacing + +$("> table", this).hasClass("wikitable") + "px");
    }
    else {
      floatingHeader.hide();
    }
  });
}