MediaWiki:MonkeyTTracker.js
Jump to navigation
Jump to search
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.
(function () {
'use strict';
const STORAGE_KEY = 'cora.questTracker.monkeyT.v1';
const SELECTORS = {
questCheckbox: '.quest-checkbox',
locationSection: '.location-section',
trackerRootId: 'monkeytab-tracker',
totalProgress: '#total-progress',
completionPercentage: '#completion-percentage',
progressFill: '#progress-fill',
resetButton: '#reset-progress',
statsContainer: '.progress-stats'
};
function safeQuerySelector(selector, context) {
try {
return (context || document).querySelector(selector);
} catch (_) {
return null;
}
}
function safeQuerySelectorAll(selector, context) {
try {
return Array.from((context || document).querySelectorAll(selector));
} catch (_) {
return [];
}
}
function safeJsonParse(value) {
try {
return JSON.parse(value);
} catch (_) {
return null;
}
}
function safeGetLocalStorage(key) {
try {
return localStorage.getItem(key);
} catch (_) {
return null;
}
}
function safeSetLocalStorage(key, value) {
try {
localStorage.setItem(key, value);
} catch (_) {}
}
function safeRemoveLocalStorage(key) {
try {
localStorage.removeItem(key);
} catch (_) {}
}
function getQuestId(checkbox, index) {
const dataQuest = checkbox.getAttribute('data-quest');
if (dataQuest) return dataQuest;
const name = checkbox.getAttribute('name');
if (name) return name;
const id = checkbox.getAttribute('id');
if (id) return id;
return `quest-${index}`;
}
function parseRequiredCountForCheckbox(checkbox) {
const label = checkbox.closest('label');
const nameEl = label ? safeQuerySelector('.quest-name', label) : null;
const text = (nameEl ? nameEl.textContent : '') || '';
const match = text.match(/(\d+)/);
if (!match) return 1;
const n = Number(match[1]);
return Number.isFinite(n) && n > 0 ? n : 1;
}
function collectRegions(root) {
const sections = safeQuerySelectorAll(SELECTORS.locationSection, root);
return sections
.map((section) => ({
section,
checkboxes: safeQuerySelectorAll(SELECTORS.questCheckbox, section)
}))
.filter((x) => x.checkboxes.length > 0);
}
function applySingleSelectionPerRegion(regions) {
regions.forEach(({ checkboxes }) => {
const checked = checkboxes.filter((cb) => cb.checked);
const anyChecked = checked.length > 0;
if (!anyChecked) {
checkboxes.forEach((cb) => {
cb.disabled = false;
});
return;
}
const chosen = checked[0];
if (checked.length > 1) {
checked.slice(1).forEach((cb) => {
cb.checked = false;
});
}
checkboxes.forEach((cb) => {
cb.disabled = cb !== chosen;
});
});
}
function loadState(checkboxes) {
const raw = safeGetLocalStorage(STORAGE_KEY);
const parsed = raw ? safeJsonParse(raw) : null;
const state = parsed && typeof parsed === 'object' ? parsed : {};
checkboxes.forEach((cb, index) => {
const key = getQuestId(cb, index);
cb.checked = !!state[key];
});
}
function saveState(checkboxes) {
const out = {};
checkboxes.forEach((cb, index) => {
const key = getQuestId(cb, index);
out[key] = !!cb.checked;
});
safeSetLocalStorage(STORAGE_KEY, JSON.stringify(out));
}
function ensureItemsStatElement(root) {
const container = safeQuerySelector(SELECTORS.statsContainer, root);
if (!container) return null;
let el = container.querySelector('[data-stat="items-progress"]');
if (el) return el;
const item = document.createElement('div');
item.className = 'stat-item';
item.setAttribute('data-stat', 'items-progress');
const label = document.createElement('span');
label.className = 'stat-label';
label.textContent = 'Items:';
const value = document.createElement('span');
value.className = 'stat-value';
value.setAttribute('data-stat-value', 'items-progress');
value.textContent = '0/0';
item.appendChild(label);
item.appendChild(value);
container.appendChild(item);
return item;
}
function updateProgress(regions, allCheckboxes, root) {
const totalRegions = regions.length;
const completedRegions = regions.filter(({ checkboxes }) => checkboxes.some((cb) => cb.checked)).length;
const percentage = totalRegions > 0 ? Math.round((completedRegions / totalRegions) * 100) : 0;
const totalProgressEl = safeQuerySelector(SELECTORS.totalProgress, root);
const completionEl = safeQuerySelector(SELECTORS.completionPercentage, root);
const fillEl = safeQuerySelector(SELECTORS.progressFill, root);
if (totalProgressEl) totalProgressEl.textContent = `${completedRegions}/${totalRegions}`;
if (completionEl) completionEl.textContent = `${percentage}%`;
if (fillEl) fillEl.style.width = `${percentage}%`;
const itemsStat = ensureItemsStatElement(root);
const itemsValue = itemsStat ? itemsStat.querySelector('[data-stat-value="items-progress"]') : null;
if (itemsValue) {
const totalItems = allCheckboxes.reduce((sum, cb) => sum + parseRequiredCountForCheckbox(cb), 0);
const completedItems = allCheckboxes.reduce(
(sum, cb) => sum + (cb.checked ? parseRequiredCountForCheckbox(cb) : 0),
0
);
itemsValue.textContent = `${completedItems}/${totalItems}`;
}
}
function wireEvents(regions, checkboxes, root) {
checkboxes.forEach((cb) => {
cb.addEventListener('change', () => {
applySingleSelectionPerRegion(regions);
saveState(checkboxes);
updateProgress(regions, checkboxes, root);
});
});
const resetBtn = safeQuerySelector(SELECTORS.resetButton, root);
if (resetBtn) {
resetBtn.addEventListener('click', () => {
checkboxes.forEach((cb) => {
cb.checked = false;
cb.disabled = false;
});
safeRemoveLocalStorage(STORAGE_KEY);
updateProgress(regions, checkboxes, root);
});
}
}
function init() {
const root = document.getElementById(SELECTORS.trackerRootId);
if (!root) return;
const checkboxes = safeQuerySelectorAll(SELECTORS.questCheckbox, root);
if (checkboxes.length === 0) return;
const regions = collectRegions(root);
if (regions.length === 0) return;
loadState(checkboxes);
applySingleSelectionPerRegion(regions);
saveState(checkboxes);
updateProgress(regions, checkboxes, root);
wireEvents(regions, checkboxes, root);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();