Tip of the Day
Pressing the mini-map at the top-right opens the full map for travel options.

MediaWiki:Common.js: Difference between revisions

From Walkscape Walkthrough
mNo edit summary
mNo edit summary
Line 185: Line 185:


/* 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 ---
function countSelectable($table) {
  // Selectable = rows that have a row ID (same logic as your storage)
  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 $rows = $table.find('tbody tr');
  var total = 0, selected = 0;
  $rows.each(function () {
    var $tr = $(this);
    var id = getRowId($tr);
    if (!id) return;        // not selectable
    total++;
    if ($tr.hasClass('highlight-on')) 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('.mw-lighttablefooter')) return null; // only for tables that opt in
  // Create <tfoot> if not there; reuse if it exists
  var $tfoot = $table.children('tfoot');
  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');
  if (!$row.length) {
    $row = $('<tr>').addClass('lt-footer-row').appendTo($tfoot);
    // single full-width header cell for the status text
    $('<th>').attr('colspan', tableColumnCount($table))
            .addClass('lt-footer-cell')
            .appendTo($row);
  } else {
    // If column count changed (rare), keep colspan in sync
    $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 + ')');
}
/* Persist multi-select highlights for tables with class "mw-lighttable" (with robust storage + debug) */
/* Persist multi-select highlights for tables with class "mw-lighttable" (with robust storage + debug) */
mw.loader.using(['jquery', 'mediawiki.storage']).then(function () {
mw.loader.using(['jquery', 'mediawiki.storage']).then(function () {
Line 282: Line 349:
         if (!$tr.attr('tabindex')) $tr.attr('tabindex','0');
         if (!$tr.attr('tabindex')) $tr.attr('tabindex','0');
       });
       });
    // Refresh footer after restoring selection
    updateFooter($table);
     });
     });
   }
   }
Line 314: Line 384:
         map[id] = on ? 1 : 0;
         map[id] = on ? 1 : 0;
         writeMap(key, map);
         writeMap(key, map);
       
// Update footer count after toggle
updateFooter($table);


         if (DEBUG) console.log('[lt] toggled', id, '->', on ? 1 : 0);
         if (DEBUG) console.log('[lt] toggled', id, '->', on ? 1 : 0);
Line 357: Line 430:
   };
   };
});
});
// --- Footer helpers: "Items Completed (X / Y)" for tables with class lighttablefooter ---
function countSelectable($table) {
  // Selectable = rows that have a row ID (same logic as your storage)
  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 $rows = $table.find('tbody tr');
  var total = 0, selected = 0;
  $rows.each(function () {
    var $tr = $(this);
    var id = getRowId($tr);
    if (!id) return;        // not selectable
    total++;
    if ($tr.hasClass('highlight-on')) 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('.mw-lighttablefooter')) return null; // only for tables that opt in
  // Create <tfoot> if not there; reuse if it exists
  var $tfoot = $table.children('tfoot');
  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');
  if (!$row.length) {
    $row = $('<tr>').addClass('lt-footer-row').appendTo($tfoot);
    // single full-width header cell for the status text
    $('<th>').attr('colspan', tableColumnCount($table))
            .addClass('lt-footer-cell')
            .appendTo($row);
  } else {
    // If column count changed (rare), keep colspan in sync
    $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 + ')');
}

Revision as of 00:58, 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 lighttablefooter ---

function countSelectable($table) {
  // Selectable = rows that have a row ID (same logic as your storage)
  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 $rows = $table.find('tbody tr');
  var total = 0, selected = 0;
  $rows.each(function () {
    var $tr = $(this);
    var id = getRowId($tr);
    if (!id) return;         // not selectable
    total++;
    if ($tr.hasClass('highlight-on')) 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('.mw-lighttablefooter')) return null; // only for tables that opt in
  // Create <tfoot> if not there; reuse if it exists
  var $tfoot = $table.children('tfoot');
  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');
  if (!$row.length) {
    $row = $('<tr>').addClass('lt-footer-row').appendTo($tfoot);
    // single full-width header cell for the status text
    $('<th>').attr('colspan', tableColumnCount($table))
             .addClass('lt-footer-cell')
             .appendTo($row);
  } else {
    // If column count changed (rare), keep colspan in sync
    $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 + ')');
}


/* 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) {
          if (DEBUG) console.warn('[lt] click on row without ID', this);
          return;
        }

        var map = readMap(key);
        var on  = !$tr.hasClass('highlight-on');

        $tr.toggleClass('highlight-on', on).attr('aria-selected', on ? 'true' : 'false');
        map[id] = on ? 1 : 0;
        writeMap(key, map);
        
		// Update footer count after toggle
		updateFooter($table);

        if (DEBUG) console.log('[lt] toggled', id, '->', on ? 1 : 0);
      });

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