Tip of the Day
If there's maintenance, don't worry, usually your steps will still count during the downtime. Check the Discord announcements to be sure!

MediaWiki:Common.js

From Walkscape Walkthrough
Revision as of 02:17, 6 November 2025 by Bonez565 (talk | contribs)

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/* Any JavaScript here will be loaded for all users on every page load. */
/* VARIABLES ***********************************************************************************************************************/
var currentPageUrl = window.location.href;
var currentPageEditUrl = mw.util.getUrl(null, {
	action: 'edit'
});
var currentPageName = mw.util.getUrl(null, null).replace('/wiki/', '');
var currentPageNamespaceID = mw.config.get('wgNamespaceNumber');
//variables needed after page load
var buttons, contents, allLinks;
//variables needed for the infobutton
var infobutton, infopanel;
/* *********************************************************************************************************************************/
// Matomo
var _paq = window._paq = window._paq || [];
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
/*
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
  var u="https://matomo.tools.walkscape.app/";
  _paq.push(['setTrackerUrl', u+'matomo.php']);
  _paq.push(['setSiteId', '5']);
  var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
  g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
})();
*/
/* sticky edit button */
// display the button only if #mw-content is present, if we are not in the edit mode, and if we're not in the Special: or Community: namespace
/*
if (document.body.contains(document.getElementById("mw-content")) && !currentPageUrl.includes("action=") && currentPageNamespaceID != -1 && currentPageNamespaceID != 3004)  {
document.getElementById("mw-content").insertAdjacentHTML('afterbegin', '<a href="https://wiki-temp.walkscape.app/' + currentPageEditUrl + '"><div id="custom-editbutton"><svg width="40" height="40" viewBox="-12 -10 50 50"><path d="M 20 2 L 26 8 L 8 26 L 2 26 L 2 20 Z M 16 6 L 22 12"></path></svg></div></a>');
}
*/
///////////////////////////////////////// ADDING THE INFO BUTTON
var infotitle = currentPageName + '/doc';
// add the info button ONLY when we use the edit mode now
if (document.body.contains(document.getElementById("mw-content")) && (currentPageUrl.includes("action=edit") || currentPageUrl.includes("action=submit"))) {
	mw.loader.using('mediawiki.api', function() {
		var api = new mw.Api();
		api.get({
			action: "query",
			titles: [infotitle],
		}).then(function(ret) {
			$.each(ret.query.pages, function() {
				if (this.missing !== "") {
					pageExists();
				}
			});
		});

		function pageExists() {
			api.get({
				action: 'parse',
				page: infotitle,
				contentmodel: 'wikitext'
			}).done(function(data) {
				InsertInfoPanel(data.parse.text['*']);
			});
		}
	});
}

function InsertInfoPanel(text) {
	//check which namespace we're in and adapt the text to it
	var infopanelTitle = 'Page';
	if (currentPageNamespaceID == 10) { //Template
		infopanelTitle = 'Template';
	} else if (currentPageNamespaceID == 828) { //Module
		infopanelTitle = 'Module';
	}
	// insert the panel and button
	document.getElementById("mw-content").insertAdjacentHTML('afterbegin', '<div id="custom-infobutton"></div><div id="custom-infopanel" style="display: none"><div id="custom-infopanel-title">' + infopanelTitle + ' Info</div><div id="custom-infopanel-content">' + text + '</div></div>');
	//set up containers
	infopanel = document.getElementById("custom-infopanel");
	infobutton = document.getElementById("custom-infobutton");
	//add an event listener to the button
	infobutton.addEventListener("click", infobuttonClicked);
}

function infobuttonClicked() {
	if (infopanel.style.display == 'none') {
		infopanel.style.display = 'block';
	} else {
		infopanel.style.display = 'none';
	}
}
/* rename the "DISCUSSION" talk page button to "DOCUMENTATION" */
//if there is such a button and we're in the Template: namespace
if (document.body.contains(document.getElementById("ca-talk")) && currentPageNamespaceID == 10) {
	document.getElementById("ca-talk").innerHTML = '<svg class="cosmos-button-icon" width="28" height="28" viewBox="-2 -2 20 20"><path d="M 4 2 Q 4 0 6 0 L 14 0 Q 16 0 16 2 L 8 2 Q 8 0 6 0 Q 4 0 4 2 L 4 12 Q 4 14 2 14 Q 0 14 0 12 L 2 12 L 0 12 Q 0 14 2 14 L 12 14 Q 14 14 14 12 L 14 4"></path></svg><span class="cosmos-button-text">Documentation</span>';
}
/* call specific functions when the page loads */
// old browsers or already loaded
if (document.readyState != "loading") AfterPageLoad();
// modern browsers
else if (document.addEventListener) document.addEventListener("DOMContentLoaded", AfterPageLoad);
/* FUNCTIONS **********************************************************************************************************************/
function AfterPageLoad() {
	StartTabs();
}
/* Tabs */
function StartTabs() {
	buttons = document.getElementsByClassName("tab-button");
	contents = document.getElementsByClassName("tab-content");
	if (buttons.length > 0) {
		clickfunction(buttons[0]);
		for (var i = 0; i < buttons.length; i++) {
			//console.log("Added Event Listener!");
			buttons[i].addEventListener("click", clickfunction);
		}
	}
}

function clickfunction(thing) {
	if (thing.id == null) {
		thing = this;
	}
	//buttons
	for (var i = 0; i < buttons.length; i++) {
		if (buttons[i].id == thing.id) {
			buttons[i].classList.remove("tab-disabled");
			buttons[i].classList.add("tab-active");
		} else {
			buttons[i].classList.remove("tab-active");
			buttons[i].classList.add("tab-disabled");
		}
	}
	//contents
	for (var i = 0; i < contents.length; i++) {
		if (contents[i].id == thing.id) {
			contents[i].style.display = 'block'
		} else {
			contents[i].style.display = 'none'
		}
	}
}
/* Multi-select highlight for tables with class "mw-lighttable" */
/* Persist multi-select highlights for tables with class "mw-lighttable"
   + footer: "Entries Completed (X / Y)" + Clear All (by ID) */
mw.loader.using(['jquery', 'mediawiki.storage']).then(function () {
  var DEBUG = false; // flip to true for console logs

  // ---------- Config ----------
  var TABLE_CLASS_OPTIN = 'mw-lighttablefooter'; // add this class to tables that should get a footer
  var TABLE_KEY_ATTR    = 'data-storage-key'; // per-table storage key attribute
  var ROW_ID_ATTRS      = ['data-achievement-id', 'data-row-id']; // attributes that carry a stable ID

  // ---------- Storage (localStorage with MW fallback) ----------
  function canUseLocalStorage() {
    try {
      var k = '__lt_test__' + Math.random();
      localStorage.setItem(k, '1');
      localStorage.removeItem(k);
      return true;
    } catch (e) { if (DEBUG) console.warn('localStorage unavailable:', e && e.name); return false; }
  }
  var storageImpl = canUseLocalStorage()
    ? { get: k => localStorage.getItem(k), set: (k,v) => localStorage.setItem(k,v), remove: k => localStorage.removeItem(k) }
    : { get: k => mw.storage.get(k),      set: (k,v) => mw.storage.set(k,v),      remove: k => mw.storage.remove(k) };

  function readMap(key){ try{ var raw=storageImpl.get(key); return raw ? JSON.parse(raw) : {}; }catch(e){ if(DEBUG)console.warn('readMap',key,e); return {}; } }
  function writeMap(key,map){ try{ storageImpl.set(key, JSON.stringify(map)); if(DEBUG)console.log('writeMap',key,map); }catch(e){ console.error('writeMap failed',key,e); } }

  // ---------- Helpers ----------
  function defaultKeyForTable($table){
    var page = mw.config.get('wgPageName') || 'UnknownPage';
    var idx  = $('table.mw-lighttable').index($table);
    return 'rs:lightTable:' + page + ':' + idx;
  }
  function getStorageKeyForTable($table){
    var key = $table.attr(TABLE_KEY_ATTR) || defaultKeyForTable($table);
    if (DEBUG) console.log('[lt] key:', key);
    return key;
  }
  function getRowId($tr){
    for (var i=0;i<ROW_ID_ATTRS.length;i++){ var v=$tr.attr(ROW_ID_ATTRS[i]); if(v) return v; }
    // fallback: if someone put the id on the first cell
    var $cell = $tr.children('td['+ROW_ID_ATTRS[0]+'],th['+ROW_ID_ATTRS[0]+'],td['+ROW_ID_ATTRS[1]+'],th['+ROW_ID_ATTRS[1]+']').first();
    if ($cell.length) return $cell.attr(ROW_ID_ATTRS[0]) || $cell.attr(ROW_ID_ATTRS[1]) || '';
    return '';
  }
function tableColumnCount($table) {
  function sumCols($row) {
    var cols = 0;
    $row.children('th,td').each(function () {
      var span = parseInt($(this).attr('colspan') || '1', 10);
      cols += (isNaN(span) ? 1 : span);
    });
    return cols;
  }

  var maxCols = 0;

  // Prefer THEAD: handle multi-row headers and colspans correctly
  var $thead = $table.children('thead');
  if ($thead.length) {
    $thead.children('tr').each(function () {
      maxCols = Math.max(maxCols, sumCols($(this)));
    });
  }

  // Fallback / cross-check with TBODY (in case no THEAD or unusual markup)
  if (maxCols === 0) {
    $table.children('tbody').children('tr').each(function () {
      maxCols = Math.max(maxCols, sumCols($(this)));
    });
  }

  return Math.max(maxCols, 1);
}

  // ---------- Footer (counter + clear-all-by-ID) ----------
  function countSelectable($table){
    var seen = Object.create(null), selectedById = Object.create(null);
    $table.find('tbody tr').each(function(){
      var id = getRowId($(this)); if (!id) return;
      seen[id] = true; if ($(this).hasClass('highlight-on')) selectedById[id] = true;
    });
    var total=0, selected=0; for (var k in seen) total++; for (var k2 in selectedById) selected++;
    return {selected, total};
  }

  function ensureFooter($table){
    if (!$table.hasClass(TABLE_CLASS_OPTIN)) return null; // opt-in only

    var $tfoot = $table.children('tfoot'); if (!$tfoot.length) $tfoot = $('<tfoot>').appendTo($table);
    var $row   = $tfoot.children('tr.lt-footer-row');
    if (!$row.length) {
      $row = $('<tr>').addClass('lt-footer-row').appendTo($tfoot);
      $('<th>').attr('colspan', tableColumnCount($table)).addClass('lt-footer-cell').appendTo($row);
    } else {
      $row.find('th.lt-footer-cell').attr('colspan', tableColumnCount($table));
    }

    var $cell = $row.find('th.lt-footer-cell');
    if (!$cell.find('.lt-clear-btn').length) {
      var $counter = $('<span class="lt-footer-counter"></span>');
      var $btn = $('<button type="button" class="lt-clear-btn">Clear All</button>')
        .css({ marginLeft: '1em' })
        .on('click', function(){
          // collect unique IDs present
          var ids = Object.create(null);
          $table.find('tbody tr').each(function(){ var id=getRowId($(this)); if (id) ids[id]=true; });

          // remove highlight from all rows with those IDs
          $table.find('tbody tr').each(function(){ var $tr=$(this), id=getRowId($tr); if (ids[id]) $tr.removeClass('highlight-on').attr('aria-selected','false'); });

          // clear in storage by ID
          var key = getStorageKeyForTable($table);
          var map = readMap(key);
          Object.keys(ids).forEach(function(id){ map[id]=0; });
          writeMap(key, map);

          updateFooter($table);
        });

      $cell.empty().append($counter).append($btn);
    }
    return $cell.find('.lt-footer-counter');
  }

  function updateFooter($table){
    var $counter = ensureFooter($table); if (!$counter) return;
    var c = countSelectable($table);
    $counter.text('Entries Completed: (' + c.selected + ' / ' + c.total + ')');
  }

  // ---------- Apply & Bind ----------
  function applyFromStorage($root){
    $root.find('table.mw-lighttable').each(function(){
      var $table = $(this), key = getStorageKeyForTable($table), map = readMap(key);
      $table.find('tbody tr').each(function(){
        var $tr=$(this), id=getRowId($tr); if(!id){ if(DEBUG)console.warn('[lt] row missing ID',this); return; }
        var on = map[id] === 1 || map[id] === '1';
        $tr.toggleClass('highlight-on', on).attr('aria-selected', on ? 'true' : 'false');
        if (!$tr.attr('tabindex')) $tr.attr('tabindex','0');
      });
      updateFooter($table);
    });
  }

  function bindHandlers($root){
    $root.find('table.mw-lighttable').each(function(){
      var $table = $(this), key = getStorageKeyForTable($table);
      $table.off('.mwLighttable');

      // Don't toggle on link clicks (and stop bubbling)
      $table.on('click.mwLighttable','a',function(e){ e.stopPropagation(); });

      // Toggle on non-link clicks; group by ID
      $table.on('click.mwLighttable','tbody tr',function(e){
        if ($(e.target).closest('a, button, input, label, select, textarea').length) return;
        var $tr=$(this), id=getRowId($tr); if(!id) return;

        var on = !$tr.hasClass('highlight-on');
        var $group = $table.find('tbody tr').filter(function(){ return getRowId($(this)) === id; });
        $group.toggleClass('highlight-on', on).attr('aria-selected', on ? 'true' : 'false');

        var map = readMap(key); map[id] = on ? 1 : 0; writeMap(key, map);
        updateFooter($table);
      });

      // Keyboard support
      $table.on('keydown.mwLighttable','tbody tr',function(e){
        if (e.key === ' ' || e.key === 'Enter'){ e.preventDefault(); $(this).trigger('click'); }
      });
    });
  }

  // ---------- Init ----------
  $(function(){ applyFromStorage($(document)); bindHandlers($(document)); });
  mw.hook('wikipage.content').add(function($c){ applyFromStorage($c); bindHandlers($c); });

  // ---------- Optional debug helpers ----------
  window.ltDebug = {
    dump: function(){ $('table.mw-lighttable').each(function(){ var key=getStorageKeyForTable($(this)); console.log('[lt] DUMP', key, readMap(key)); }); },
    clearAll: function(){ $('table.mw-lighttable').each(function(){ var key=getStorageKeyForTable($(this)); storageImpl.remove(key); console.log('[lt] CLEARED', key); }); }
  };
});

mw.hook('wikipage.content').add(function ($content) {
  var $wrap = $content.find('.tabberresize').first();
  if (!$wrap.length) return;

  function recalc() {
    if (window.matchMedia('(max-width: 850px)').matches) {
      // Mobile: leave sizing to CSS
      $wrap.css({ width: '100%', float: 'none', display: 'block' });
      return;
    }

    // 1) Find widest tab label (measured as rendered <a.tabber__tab>)
    var maxTab = 0;
    $wrap.find('.tabber__tab').each(function () {
      // offsetWidth includes padding/border; safe for equal sizing
      maxTab = Math.max(maxTab, Math.ceil(this.offsetWidth));
    });
    if (!maxTab) maxTab = 90; // fallback

    // Expose to CSS grid
    $wrap.get(0).style.setProperty('--tab-w', maxTab + 'px');

    // 2) Measure the infobox table width (first one in the active panel)
    var $table = $wrap.find('table.ItemInfobox, table.wikitable.ItemInfobox').first();
    var tableW = $table.length ? Math.ceil($table.outerWidth()) : 0;

    // 3) Set wrapper width to max(table width, 3 × tab width)
    var target = Math.max(tableW, maxTab * 3 + 12);
    $wrap.css({
      width: target + 'px !important' ,
      float: 'right',
      display: 'inline-block',
      maxWidth: '100%'
    });
  }

  // Initial + on resize (debounced enough for this use)
  recalc();
  $(window).on('resize', recalc);
});