Tip of the Day
Collectibles you find give 1 extra achievement point per item!

MediaWiki:Common.js: Difference between revisions

From Walkscape Walkthrough
mNo edit summary
mNo edit summary
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. */
/* VARIABLES ***********************************************************************************************************************/
/* VARIABLES ***********************************************************************************************************************/
var currentPageUrl = window.location.href;
var currentPageUrl = window.location.href;
var currentPageEditUrl = mw.util.getUrl( null, { action: 'edit' } );
var currentPageEditUrl = mw.util.getUrl(null, {
var currentPageName = mw.util.getUrl( null, null ).replace('/wiki/', '');
action: 'edit'
});
var currentPageName = mw.util.getUrl(null, null).replace('/wiki/', '');
var currentPageNamespaceID = mw.config.get('wgNamespaceNumber');
var currentPageNamespaceID = mw.config.get('wgNamespaceNumber');
//variables needed after page load
//variables needed after page load
var buttons, contents, allLinks;
var buttons, contents, allLinks;
//variables needed for the infobutton
//variables needed for the infobutton
var infobutton, infopanel;
var infobutton, infopanel;
/* *********************************************************************************************************************************/
/* *********************************************************************************************************************************/
// Matomo
// Matomo
var _paq = window._paq = window._paq || [];
var _paq = window._paq = window._paq || [];
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
/*
/*
_paq.push(['trackPageView']);
_paq.push(['trackPageView']);
Line 32: Line 26:
})();
})();
*/
*/
/* sticky edit button */
/* 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
// 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
/*
/*
Line 41: Line 33:
}
}
*/
*/
///////////////////////////////////////// ADDING THE INFO BUTTON
///////////////////////////////////////// ADDING THE INFO BUTTON
var infotitle = currentPageName + '/doc';
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();
}
});
});


// add the info button ONLY when we use the edit mode now
function pageExists() {
if (document.body.contains(document.getElementById("mw-content")) && (currentPageUrl.includes("action=edit") || currentPageUrl.includes("action=submit")))
api.get({
{
action: 'parse',
mw.loader.using('mediawiki.api', function() {
page: infotitle,
var api = new mw.Api();
contentmodel: 'wikitext'
}).done(function(data) {
api.get( {
InsertInfoPanel(data.parse.text['*']);
    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 )
function InsertInfoPanel(text) {
{
//check which namespace we're in and adapt the text to it
//check which namespace we're in and adapt the text to it
var infopanelTitle = 'Page';
var infopanelTitle = 'Page';
if (currentPageNamespaceID == 10) { //Template
if (currentPageNamespaceID == 10) { //Template
infopanelTitle = 'Template';
infopanelTitle = 'Template';
}
} else if (currentPageNamespaceID == 828) { //Module
else if (currentPageNamespaceID == 828) { //Module
infopanelTitle = 'Module';
infopanelTitle = 'Module';
}
}
// insert the panel and button
// 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>');
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
//set up containers
infopanel = document.getElementById("custom-infopanel");
infopanel = document.getElementById("custom-infopanel");
infobutton = document.getElementById("custom-infobutton");
infobutton = document.getElementById("custom-infobutton");
//add an event listener to the button
//add an event listener to the button
infobutton.addEventListener("click", infobuttonClicked);
infobutton.addEventListener("click", infobuttonClicked);
}
}


function infobuttonClicked()
function infobuttonClicked() {
{
if (infopanel.style.display == 'none') {
if (infopanel.style.display == 'none')
{
infopanel.style.display = 'block';
infopanel.style.display = 'block';
}
} else {
else
{
infopanel.style.display = 'none';
infopanel.style.display = 'none';
}
}
}
}
/* rename the "DISCUSSION" talk page button to "DOCUMENTATION" */
/* rename the "DISCUSSION" talk page button to "DOCUMENTATION" */
//if there is such a button and we're in the Template: namespace
//if there is such a button and we're in the Template: namespace
if (document.body.contains(document.getElementById("ca-talk")) && currentPageNamespaceID == 10) {
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>';
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 */
/* call specific functions when the page loads */
// old browsers or already loaded
// old browsers or already loaded
if (document.readyState!="loading") AfterPageLoad();
if (document.readyState != "loading") AfterPageLoad();
// modern browsers
// modern browsers
else if (document.addEventListener) document.addEventListener("DOMContentLoaded", AfterPageLoad);
else if (document.addEventListener) document.addEventListener("DOMContentLoaded", AfterPageLoad);
/* FUNCTIONS **********************************************************************************************************************/
/* FUNCTIONS **********************************************************************************************************************/
 
function AfterPageLoad() {
function AfterPageLoad()
StartTabs();
{
StartTabs();
}
}
/* Tabs */
/* Tabs */
 
function StartTabs() {
function StartTabs()
buttons = document.getElementsByClassName("tab-button");
{
contents = document.getElementsByClassName("tab-content");
buttons = document.getElementsByClassName("tab-button");
if (buttons.length > 0) {
contents = document.getElementsByClassName("tab-content");
clickfunction(buttons[0]);
 
for (var i = 0; i < buttons.length; i++) {
if (buttons.length > 0){
//console.log("Added Event Listener!");
clickfunction(buttons[0]);
buttons[i].addEventListener("click", clickfunction);
 
}
for (var i = 0; i < buttons.length; i++)
}
  {
    //console.log("Added Event Listener!");
    buttons[i].addEventListener("click", clickfunction);
  }
}
}
}


function clickfunction(thing) {
function clickfunction(thing) {
  if (thing.id == null)
if (thing.id == null) {
    {
thing = this;
      thing = this;
}
    }
//buttons
 
for (var i = 0; i < buttons.length; i++) {
  //buttons
if (buttons[i].id == thing.id) {
for (var i = 0; i < buttons.length; i++)
buttons[i].classList.remove("tab-disabled");
  {
buttons[i].classList.add("tab-active");
    if (buttons[i].id == thing.id)
} else {
      {
buttons[i].classList.remove("tab-active");
        buttons[i].classList.remove("tab-disabled");
buttons[i].classList.add("tab-disabled");
        buttons[i].classList.add("tab-active");
}
      }
}
    else
//contents
      {
for (var i = 0; i < contents.length; i++) {
        buttons[i].classList.remove("tab-active");
if (contents[i].id == thing.id) {
        buttons[i].classList.add("tab-disabled");
contents[i].style.display = 'block'
      }
} else {
  }
contents[i].style.display = 'none'
  //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" */
/* Multi-select highlight for tables with class "mw-lighttable" */
// --- Footer helpers: "Items Completed (X / Y)" for tables with class lighttablefooter ---
// --- Footer helpers: "Items Completed (X / Y)" for tables with class mw-lighttablefooter ---
 
function countSelectable($table) {
function countSelectable($table) {
  var ROW_ID_ATTRS = ['data-achievement-id','data-row-id'];
var ROW_ID_ATTRS = ['data-achievement-id', 'data-row-id'];
  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;
    }
    return '';
  }


  var seen = Object.create(null);
function getRowId($tr) {
  var selectedById = Object.create(null);
for (var i = 0; i < ROW_ID_ATTRS.length; i++) {
 
var v = $tr.attr(ROW_ID_ATTRS[i]);
  $table.find('tbody tr').each(function () {
if (v) return v;
    var $tr = $(this);
}
    var id = getRowId($tr);
return '';
    if (!id) return;
}
 
var seen = Object.create(null);
    seen[id] = true;
var selectedById = Object.create(null);
    if ($tr.hasClass('highlight-on')) selectedById[id] = true;
$table.find('tbody tr').each(function() {
  });
var $tr = $(this);
 
var id = getRowId($tr);
  var total = 0, selected = 0;
if (!id) return;
  for (var k in seen) { total++; }
seen[id] = true;
  for (var k in selectedById) { selected++; }
if ($tr.hasClass('highlight-on')) selectedById[id] = true;
 
});
  return { selected: selected, total: total };
var total = 0,
selected = 0;
for (var k in seen) {
total++;
}
for (var k in selectedById) {
selected++;
}
return {
selected: selected,
total: total
};
}
}


function tableColumnCount($table) {
function tableColumnCount($table) {
  // Prefer header count if present; fall back to first body row
// Prefer header count if present; fall back to first body row
  var $ths = $table.find('> thead > tr:first > th');
var $ths = $table.find('> thead > tr:first > th');
  if ($ths.length) return $ths.length;
if ($ths.length) return $ths.length;
  var $firstRow = $table.find('> tbody > tr:first');
var $firstRow = $table.find('> tbody > tr:first');
  if (!$firstRow.length) return 1;
if (!$firstRow.length) return 1;
  var cols = 0;
var cols = 0;
  $firstRow.children('th,td').each(function(){
$firstRow.children('th,td').each(function() {
    var span = parseInt($(this).attr('colspan') || '1', 10);
var span = parseInt($(this).attr('colspan') || '1', 10);
    cols += isNaN(span) ? 1 : span;
cols += isNaN(span) ? 1 : span;
  });
});
  return Math.max(cols, 1);
return Math.max(cols, 1);
}
}


function ensureFooter($table) {
function ensureFooter($table) {
   if (!$table.is('.mw-lighttablefooter')) return null; // only for tables that opt in
   if (!$table.is('.lighttablefooter')) return null; // only for opt-in tables
  // Create <tfoot> if not there; reuse if it exists
 
   var $tfoot = $table.children('tfoot');
   var $tfoot = $table.children('tfoot');
   if (!$tfoot.length) $tfoot = $('<tfoot>').appendTo($table);
   if (!$tfoot.length) $tfoot = $('<tfoot>').appendTo($table);


  // Use a marker class so we only manage our own footer row
   var $row = $tfoot.children('tr.lt-footer-row');
   var $row = $tfoot.children('tr.lt-footer-row');
   if (!$row.length) {
   if (!$row.length) {
     $row = $('<tr>').addClass('lt-footer-row').appendTo($tfoot);
     $row = $('<tr>').addClass('lt-footer-row').appendTo($tfoot);
     // single full-width header cell for the status text
 
     $('<th>').attr('colspan', tableColumnCount($table))
     // Full-width cell for counter + button
            .addClass('lt-footer-cell')
     $('<th>')
            .appendTo($row);
      .attr('colspan', tableColumnCount($table))
      .addClass('lt-footer-cell')
      .appendTo($row);
   } else {
   } else {
    // If column count changed (rare), keep colspan in sync
     $row.find('th.lt-footer-cell').attr('colspan', tableColumnCount($table));
     $row.find('th.lt-footer-cell').attr('colspan', tableColumnCount($table));
   }
   }
  return $row.find('th.lt-footer-cell');
}
function updateFooter($table) {
  var $cell = ensureFooter($table);
  if (!$cell) return;
  var c = countSelectable($table);
  $cell.text('Items Completed: (' + c.selected + ' / ' + c.total + ')');
}


  var $cell = $row.find('th.lt-footer-cell');


/* Persist multi-select highlights for tables with class "mw-lighttable" (with robust storage + debug) */
  // If button not already there, create it
mw.loader.using(['jquery', 'mediawiki.storage']).then(function () {
  if (!$cell.find('.lt-clear-btn').length) {
    var $counter = $('<span class="lt-footer-counter"></span>');


  var DEBUG = false; // set true to see logs in the console
    var $btn = $('<button type="button" class="lt-clear-btn">Clear All</button>')
      .css({ marginLeft: '1em' })
      .on('click', function () {
        // Clear all highlight-on rows in this table
        $table.find('tbody tr.highlight-on')
          .removeClass('highlight-on')
          .attr('aria-selected', 'false');


  var TABLE_KEY_ATTR = 'data-storage-key';                 // per-table storage key
        // Clear from storage
  var ROW_ID_ATTRS  = ['data-achievement-id','data-row-id']; // row id attributes
        var key = getStorageKeyForTable($table);
        var map = readMap(key);
        $table.find('tbody tr').each(function () {
          var id = getRowId($(this));
          if (id) map[id] = 0;
        });
        writeMap(key, map);


  // --- Storage layer (localStorage with fallback to mw.storage) ----------------
        // Update footer after clearing
        updateFooter($table);
      });


  function canUseLocalStorage() {
     $cell.empty().append($counter).append($btn);
     try {
      var k = '__lt_test__' + Math.random();
      localStorage.setItem(k, '1');
      localStorage.removeItem(k);
      return true;
    } catch (e) {
      if (DEBUG) console.warn('localStorage not available:', e && e.name);
      return false;
    }
   }
   }


   var storageImpl;
   return $cell.find('.lt-footer-counter');
  if (canUseLocalStorage()) {
}
    storageImpl = {
      get: function (key) { return localStorage.getItem(key); },
      set: function (key, val) { localStorage.setItem(key, val); },
      remove: function (key) { localStorage.removeItem(key); }
    };
    if (DEBUG) console.log('[lt] using window.localStorage');
  } else {
    // Fallback to MW storage (string values)
    storageImpl = {
      get: function (key) { return mw.storage.get(key); },
      set: function (key, val) { mw.storage.set(key, val); },
      remove: function (key) { mw.storage.remove(key); }
    };
    if (DEBUG) console.log('[lt] using mw.storage fallback');
  }


  function readMap(key) {
function updateFooter($table) {
    try {
  var $counter = ensureFooter($table);
      var raw = storageImpl.get(key);
  if (!$counter) return;
      if (!raw) return {};
      return JSON.parse(raw);
    } catch (e) {
      if (DEBUG) console.warn('[lt] readMap parse error for', key, e);
      return {};
    }
  }
  function writeMap(key, map) {
    try {
      storageImpl.set(key, JSON.stringify(map));
      if (DEBUG) console.log('[lt] writeMap', key, map);
    } catch (e) {
      console.error('[lt] writeMap failed for', key, e);
    }
  }


   // --- Helpers ----------------------------------------------------------------
   var c = countSelectable($table);
  $counter.text('Entries Completed: (' + c.selected + ' / ' + c.total + ')');
}


  function defaultKeyForTable($table) {
/* Persist multi-select highlights for tables with class "mw-lighttable" (with robust storage + debug) */
    var page = mw.config.get('wgPageName') || 'UnknownPage';
mw.loader.using(['jquery', 'mediawiki.storage']).then(function() {
    var idx  = $('table.mw-lighttable').index($table);
var DEBUG = false; // set true to see logs in the console
    return 'rs:lightTable:' + page + ':' + idx;
var TABLE_KEY_ATTR = 'data-storage-key'; // per-table storage key
  }
var ROW_ID_ATTRS = ['data-achievement-id', 'data-row-id']; // row id attributes
  function getStorageKeyForTable($table) {
// --- Storage layer (localStorage with fallback to mw.storage) ----------------
    var key = $table.attr(TABLE_KEY_ATTR) || defaultKeyForTable($table);
function canUseLocalStorage() {
    if (DEBUG) console.log('[lt] table key:', key, $table.get(0));
try {
    return key;
var k = '__lt_test__' + Math.random();
  }
localStorage.setItem(k, '1');
localStorage.removeItem(k);
return true;
} catch (e) {
if (DEBUG) console.warn('localStorage not available:', e && e.name);
return false;
}
}
var storageImpl;
if (canUseLocalStorage()) {
storageImpl = {
get: function(key) {
return localStorage.getItem(key);
},
set: function(key, val) {
localStorage.setItem(key, val);
},
remove: function(key) {
localStorage.removeItem(key);
}
};
if (DEBUG) console.log('[lt] using window.localStorage');
} else {
// Fallback to MW storage (string values)
storageImpl = {
get: function(key) {
return mw.storage.get(key);
},
set: function(key, val) {
mw.storage.set(key, val);
},
remove: function(key) {
mw.storage.remove(key);
}
};
if (DEBUG) console.log('[lt] using mw.storage fallback');
}


  function getRowId($tr) {
function readMap(key) {
    for (var i = 0; i < ROW_ID_ATTRS.length; i++) {
try {
      var v = $tr.attr(ROW_ID_ATTRS[i]);
var raw = storageImpl.get(key);
      if (v) return v;
if (!raw) return {};
    }
return JSON.parse(raw);
    return '';
} catch (e) {
  }
if (DEBUG) console.warn('[lt] readMap parse error for', key, e);
return {};
}
}


  function applyFromStorage($root) {
function writeMap(key, map) {
    $root.find('table.mw-lighttable').each(function () {
try {
      var $table = $(this);
storageImpl.set(key, JSON.stringify(map));
      var key = getStorageKeyForTable($table);
if (DEBUG) console.log('[lt] writeMap', key, map);
      var map = readMap(key);
} catch (e) {
console.error('[lt] writeMap failed for', 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;
}


      $table.find('tbody tr').each(function () {
function getStorageKeyForTable($table) {
        var $tr = $(this);
var key = $table.attr(TABLE_KEY_ATTR) || defaultKeyForTable($table);
        var id  = getRowId($tr);
if (DEBUG) console.log('[lt] table key:', key, $table.get(0));
        if (!id) {
return key;
          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');
      });


    // Refresh footer after restoring selection
function getRowId($tr) {
    updateFooter($table);
for (var i = 0; i < ROW_ID_ATTRS.length; i++) {
    });
var v = $tr.attr(ROW_ID_ATTRS[i]);
  }
if (v) return v;
}
return '';
}


  function bindHandlers($root) {
function applyFromStorage($root) {
    $root.find('table.mw-lighttable').each(function () {
$root.find('table.mw-lighttable').each(function() {
      var $table = $(this);
var $table = $(this);
      var key = getStorageKeyForTable($table);
var key = getStorageKeyForTable($table);
var map = readMap(key);
$table.find('tbody tr').each(function() {
var $tr = $(this);
var 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');
});
// Refresh footer after restoring selection
updateFooter($table);
});
}


      $table.off('.mwLighttable');
function bindHandlers($root) {
 
$root.find('table.mw-lighttable').each(function() {
      // Prevent link clicks from toggling (and from bubbling)
var $table = $(this);
      $table.on('click.mwLighttable', 'a', function (e) {
var key = getStorageKeyForTable($table);
        e.stopPropagation();
$table.off('.mwLighttable');
      });
// Prevent link clicks from toggling (and from bubbling)
 
$table.on('click.mwLighttable', 'a', function(e) {
      // Toggle on non-link clicks
e.stopPropagation();
$table.on('click.mwLighttable', 'tbody tr', function (e) {
});
  if ($(e.target).closest('a, button, input, label, select, textarea').length) return;
// Toggle on non-link clicks
$table.on('click.mwLighttable', 'tbody tr', function(e) {
  var $tr = $(this);
if ($(e.target).closest('a, button, input, label, select, textarea').length) return;
  var id = getRowId($tr);
var $tr = $(this);
  if (!id) return;
var id = getRowId($tr);
if (!id) return;
  var map = readMap(key);
var map = readMap(key);
  var on = !$tr.hasClass('highlight-on');
var on = !$tr.hasClass('highlight-on');
// NEW: toggle every row in this table with the same ID
  // NEW: toggle every row in this table with the same ID
var $group = $table.find('tbody tr').filter(function() {
  var $group = $table.find('tbody tr').filter(function () {
return getRowId($(this)) === id;
    return getRowId($(this)) === id;
});
  });
$group.toggleClass('highlight-on', on).attr('aria-selected', on ? 'true' : 'false');
  $group.toggleClass('highlight-on', on).attr('aria-selected', on ? 'true' : 'false');
map[id] = on ? 1 : 0;
writeMap(key, map);
  map[id] = on ? 1 : 0;
updateFooter($table);
  writeMap(key, map);
});
// Keyboard toggle
  updateFooter($table);
$table.on('keydown.mwLighttable', 'tbody tr', function(e) {
if (e.key === ' ' || e.key === 'Enter') {
e.preventDefault();
$(this).trigger('click');
}
});
});
});
 
}
      // Keyboard toggle
// Initial
      $table.on('keydown.mwLighttable', 'tbody tr', function (e) {
$(function() {
        if (e.key === ' ' || e.key === 'Enter') {
applyFromStorage($(document));
          e.preventDefault();
bindHandlers($(document));
          $(this).trigger('click');
});
        }
// Dynamic content
      });
mw.hook('wikipage.content').add(function($c) {
    });
applyFromStorage($c);
  }
bindHandlers($c);
 
});
  // Initial
// Optional: expose quick debug helpers
  $(function () {
window.ltDebug = {
    applyFromStorage($(document));
dump: function() {
    bindHandlers($(document));
$('table.mw-lighttable').each(function() {
  });
var key = getStorageKeyForTable($(this));
  // Dynamic content
console.log('[lt] DUMP', key, readMap(key));
  mw.hook('wikipage.content').add(function ($c) {
});
    applyFromStorage($c);
},
    bindHandlers($c);
clear: function() {
  });
$('table.mw-lighttable').each(function() {
 
var key = getStorageKeyForTable($(this));
  // Optional: expose quick debug helpers
storageImpl.remove(key);
  window.ltDebug = {
console.log('[lt] CLEARED', key);
    dump: function () {
});
      $('table.mw-lighttable').each(function () {
applyFromStorage($(document));
        var key = getStorageKeyForTable($(this));
}
        console.log('[lt] DUMP', key, readMap(key));
};
      });
    },
    clear: function () {
      $('table.mw-lighttable').each(function () {
        var key = getStorageKeyForTable($(this));
        storageImpl.remove(key);
        console.log('[lt] CLEARED', key);
      });
      applyFromStorage($(document));
    }
  };
});
});

Revision as of 01:19, 14 August 2025

/* 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" */
// --- Footer helpers: "Items Completed (X / Y)" for tables with class mw-lighttablefooter ---
function countSelectable($table) {
	var ROW_ID_ATTRS = ['data-achievement-id', 'data-row-id'];

	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;
		}
		return '';
	}
	var seen = Object.create(null);
	var selectedById = Object.create(null);
	$table.find('tbody tr').each(function() {
		var $tr = $(this);
		var id = getRowId($tr);
		if (!id) return;
		seen[id] = true;
		if ($tr.hasClass('highlight-on')) selectedById[id] = true;
	});
	var total = 0,
		selected = 0;
	for (var k in seen) {
		total++;
	}
	for (var k in selectedById) {
		selected++;
	}
	return {
		selected: selected,
		total: total
	};
}

function tableColumnCount($table) {
	// Prefer header count if present; fall back to first body row
	var $ths = $table.find('> thead > tr:first > th');
	if ($ths.length) return $ths.length;
	var $firstRow = $table.find('> tbody > tr:first');
	if (!$firstRow.length) return 1;
	var cols = 0;
	$firstRow.children('th,td').each(function() {
		var span = parseInt($(this).attr('colspan') || '1', 10);
		cols += isNaN(span) ? 1 : span;
	});
	return Math.max(cols, 1);
}

function ensureFooter($table) {
  if (!$table.is('.lighttablefooter')) return null; // only for opt-in tables

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

    // Full-width cell for counter + button
    $('<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 button not already there, create it
  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 () {
        // Clear all highlight-on rows in this table
        $table.find('tbody tr.highlight-on')
          .removeClass('highlight-on')
          .attr('aria-selected', 'false');

        // Clear from storage
        var key = getStorageKeyForTable($table);
        var map = readMap(key);
        $table.find('tbody tr').each(function () {
          var id = getRowId($(this));
          if (id) map[id] = 0;
        });
        writeMap(key, map);

        // Update footer after clearing
        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 + ')');
}

/* Persist multi-select highlights for tables with class "mw-lighttable" (with robust storage + debug) */
mw.loader.using(['jquery', 'mediawiki.storage']).then(function() {
	var DEBUG = false; // set true to see logs in the console
	var TABLE_KEY_ATTR = 'data-storage-key'; // per-table storage key
	var ROW_ID_ATTRS = ['data-achievement-id', 'data-row-id']; // row id attributes
	// --- Storage layer (localStorage with fallback to mw.storage) ----------------
	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 not available:', e && e.name);
			return false;
		}
	}
	var storageImpl;
	if (canUseLocalStorage()) {
		storageImpl = {
			get: function(key) {
				return localStorage.getItem(key);
			},
			set: function(key, val) {
				localStorage.setItem(key, val);
			},
			remove: function(key) {
				localStorage.removeItem(key);
			}
		};
		if (DEBUG) console.log('[lt] using window.localStorage');
	} else {
		// Fallback to MW storage (string values)
		storageImpl = {
			get: function(key) {
				return mw.storage.get(key);
			},
			set: function(key, val) {
				mw.storage.set(key, val);
			},
			remove: function(key) {
				mw.storage.remove(key);
			}
		};
		if (DEBUG) console.log('[lt] using mw.storage fallback');
	}

	function readMap(key) {
		try {
			var raw = storageImpl.get(key);
			if (!raw) return {};
			return JSON.parse(raw);
		} catch (e) {
			if (DEBUG) console.warn('[lt] readMap parse error for', key, e);
			return {};
		}
	}

	function writeMap(key, map) {
		try {
			storageImpl.set(key, JSON.stringify(map));
			if (DEBUG) console.log('[lt] writeMap', key, map);
		} catch (e) {
			console.error('[lt] writeMap failed for', 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] table key:', key, $table.get(0));
		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;
		}
		return '';
	}

	function applyFromStorage($root) {
		$root.find('table.mw-lighttable').each(function() {
			var $table = $(this);
			var key = getStorageKeyForTable($table);
			var map = readMap(key);
			$table.find('tbody tr').each(function() {
				var $tr = $(this);
				var 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');
			});
			// Refresh footer after restoring selection
			updateFooter($table);
		});
	}

	function bindHandlers($root) {
		$root.find('table.mw-lighttable').each(function() {
			var $table = $(this);
			var key = getStorageKeyForTable($table);
			$table.off('.mwLighttable');
			// Prevent link clicks from toggling (and from bubbling)
			$table.on('click.mwLighttable', 'a', function(e) {
				e.stopPropagation();
			});
			// Toggle on non-link clicks
			$table.on('click.mwLighttable', 'tbody tr', function(e) {
				if ($(e.target).closest('a, button, input, label, select, textarea').length) return;
				var $tr = $(this);
				var id = getRowId($tr);
				if (!id) return;
				var map = readMap(key);
				var on = !$tr.hasClass('highlight-on');
				// NEW: toggle every row in this table with the same ID
				var $group = $table.find('tbody tr').filter(function() {
					return getRowId($(this)) === id;
				});
				$group.toggleClass('highlight-on', on).attr('aria-selected', on ? 'true' : 'false');
				map[id] = on ? 1 : 0;
				writeMap(key, map);
				updateFooter($table);
			});
			// Keyboard toggle
			$table.on('keydown.mwLighttable', 'tbody tr', function(e) {
				if (e.key === ' ' || e.key === 'Enter') {
					e.preventDefault();
					$(this).trigger('click');
				}
			});
		});
	}
	// Initial
	$(function() {
		applyFromStorage($(document));
		bindHandlers($(document));
	});
	// Dynamic content
	mw.hook('wikipage.content').add(function($c) {
		applyFromStorage($c);
		bindHandlers($c);
	});
	// Optional: expose quick debug helpers
	window.ltDebug = {
		dump: function() {
			$('table.mw-lighttable').each(function() {
				var key = getStorageKeyForTable($(this));
				console.log('[lt] DUMP', key, readMap(key));
			});
		},
		clear: function() {
			$('table.mw-lighttable').each(function() {
				var key = getStorageKeyForTable($(this));
				storageImpl.remove(key);
				console.log('[lt] CLEARED', key);
			});
			applyFromStorage($(document));
		}
	};
});