Tip of the Day
Make sure you equip your items to get their effects, most don't work from inventory alone.
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(); | |||
} | |||
}); | |||
}); | |||
function pageExists() { | |||
api.get({ | |||
action: 'parse', | |||
page: infotitle, | |||
contentmodel: 'wikitext' | |||
}).done(function(data) { | |||
InsertInfoPanel(data.parse.text['*']); | |||
}); | |||
} | |||
}); | |||
function pageExists() { | |||
} ).done( function ( data ) { | |||
}); | |||
} | |||
}); | |||
} | } | ||
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 | ||
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 { | ||
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++) | } | ||
} | |||
} | } | ||
function clickfunction(thing) { | function clickfunction(thing) { | ||
if (thing.id == null) { | |||
thing = this; | |||
} | |||
//buttons | |||
for (var i = 0; i < buttons.length; i++) { | |||
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"); | |||
} 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" */ | /* 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']; | |||
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) { | 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) { | function ensureFooter($table) { | ||
if (!$table.is('. | if (!$table.is('.lighttablefooter')) return null; // only for opt-in tables | ||
var $tfoot = $table.children('tfoot'); | var $tfoot = $table.children('tfoot'); | ||
if (!$tfoot.length) $tfoot = $('<tfoot>').appendTo($table); | if (!$tfoot.length) $tfoot = $('<tfoot>').appendTo($table); | ||
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); | ||
// | |||
$('<th>').attr('colspan', tableColumnCount($table)) | // Full-width cell for counter + button | ||
$('<th>') | |||
.attr('colspan', tableColumnCount($table)) | |||
.addClass('lt-footer-cell') | |||
.appendTo($row); | |||
} else { | } else { | ||
$row.find('th.lt-footer-cell').attr('colspan', tableColumnCount($table)); | $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)); | |||
} | |||
}; | |||
}); | }); | ||
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));
}
};
});
