MediaWiki:DailyQuestTracker.js
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.
'use strict';
(function() {
function safeGetElementById(id) {
try { return document.getElementById(id); } catch (_) { return null; }
}
function safeQuerySelectorAll(selector, context = document) {
try { return context.querySelectorAll(selector); } catch (_) { return []; }
}
function safeQuerySelector(selector, context = document) {
try { return context.querySelector(selector); } catch (_) { return null; }
}
function safeGetLocalStorage(key, defaultValue = '') {
try { return localStorage.getItem(key) || defaultValue; } catch (_) { return defaultValue; }
}
function safeSetLocalStorage(key, value) {
try { localStorage.setItem(key, value); return true; } catch (_) { return false; }
}
const STORAGE_KEY = 'shamanJiaQuestProgress';
const SELECTORS = {
QUEST_CHECKBOX: '.quest-checkbox',
REGION_CHECKBOX: '.region-checkbox',
LOCATION_SECTION: '.location-section',
LOCATION_TITLE: '.location-title'
};
const QuestTracker = {
initialized: false,
init: function() {
if (this.initialized) return;
const questCheckboxes = safeQuerySelectorAll(SELECTORS.QUEST_CHECKBOX);
if (!questCheckboxes || questCheckboxes.length === 0) return;
this.registerRegions();
this.loadQuestProgress();
this.setupEventListeners();
this.updateProgressDisplay();
this.initialized = true;
},
registerRegions: function() {
const sections = safeQuerySelectorAll(SELECTORS.LOCATION_SECTION);
Array.from(sections).forEach(section => {
const titleEl = safeQuerySelector(SELECTORS.LOCATION_TITLE, section);
const name = titleEl ? titleEl.textContent.trim() : '';
const regionId = name ? name.toLowerCase().replace(/\s+/g, '-') : '';
const cbs = safeQuerySelectorAll(SELECTORS.QUEST_CHECKBOX, section);
Array.from(cbs).forEach(cb => { if (regionId) cb.setAttribute('data-region', regionId); });
const rc = safeQuerySelector(SELECTORS.REGION_CHECKBOX, section);
if (rc && regionId) rc.setAttribute('data-region', regionId);
});
},
setupEventListeners: function() {
const questCheckboxes = safeQuerySelectorAll(SELECTORS.QUEST_CHECKBOX);
Array.from(questCheckboxes).forEach(cb => {
cb.addEventListener('change', () => {
const section = cb.closest('.location-section');
const items = section ? safeQuerySelectorAll(SELECTORS.QUEST_CHECKBOX, section) : [];
const arr = Array.from(items);
if (cb.checked) {
arr.forEach(other => { if (other !== cb) { other.checked = false; other.disabled = true; } });
} else {
const anyChecked = arr.some(x => x.checked);
if (!anyChecked) arr.forEach(other => { other.disabled = false; });
}
this.saveQuestProgress();
this.syncRegionToggles();
this.applyDisabledStateAllRegions();
this.updateProgressDisplay();
});
});
const regionCheckboxes = safeQuerySelectorAll(SELECTORS.REGION_CHECKBOX);
Array.from(regionCheckboxes).forEach(rcb => {
rcb.addEventListener('change', () => { this.handleRegionToggle(rcb); });
});
const resetButton = safeGetElementById('reset-progress');
if (resetButton) {
resetButton.addEventListener('click', () => { this.resetAllProgress(); });
}
},
handleRegionToggle: function(regionCb) {
const region = regionCb ? regionCb.getAttribute('data-region') : '';
const checked = !!(regionCb && regionCb.checked);
if (!region) return;
this.setRegionChecked(region, checked);
},
setRegionChecked: function(region, checked) {
const items = safeQuerySelectorAll(SELECTORS.QUEST_CHECKBOX + `[data-region="${region}"]`);
const arr = Array.from(items);
if (checked) {
arr.forEach(cb => { cb.checked = false; cb.disabled = true; });
if (arr.length > 0) { arr[0].checked = true; arr[0].disabled = false; }
} else {
arr.forEach(cb => { cb.checked = false; cb.disabled = false; });
}
this.saveQuestProgress();
this.syncRegionToggles();
this.applyDisabledStateAllRegions();
this.updateProgressDisplay();
},
collectRegions: function() {
const result = {};
const questCheckboxes = safeQuerySelectorAll(SELECTORS.QUEST_CHECKBOX);
Array.from(questCheckboxes).forEach(cb => {
const region = cb.getAttribute('data-region') || 'global';
if (!result[region]) result[region] = [];
result[region].push(cb);
});
return result;
},
loadQuestProgress: function() {
const saved = safeGetLocalStorage(STORAGE_KEY);
if (saved) {
try {
const data = JSON.parse(saved);
Object.keys(data).forEach(id => {
const cb = safeQuerySelector(`[data-quest="${id}"]`);
if (cb) cb.checked = !!data[id];
});
} catch (_) {}
}
this.enforceSingleSelectionPerRegion();
this.syncRegionToggles();
this.applyDisabledStateAllRegions();
},
saveQuestProgress: function() {
const questCheckboxes = safeQuerySelectorAll(SELECTORS.QUEST_CHECKBOX);
const out = {};
Array.from(questCheckboxes).forEach(cb => {
const id = cb.getAttribute('data-quest');
if (id) out[id] = !!cb.checked;
});
try { safeSetLocalStorage(STORAGE_KEY, JSON.stringify(out)); } catch (_) {}
},
updateProgressDisplay: function() {
const regions = this.collectRegions();
const keys = Object.keys(regions).filter(r => r !== 'global');
const regionCount = keys.length;
const completedRegions = keys.filter(r => regions[r].some(cb => cb.checked)).length;
const completionPercentage = regionCount > 0 ? Math.round((completedRegions / regionCount) * 100) : 0;
const totalProgressElement = safeGetElementById('total-progress');
const completionPercentageElement = safeGetElementById('completion-percentage');
const progressFillElement = safeGetElementById('progress-fill');
if (totalProgressElement) totalProgressElement.textContent = `${completedRegions}/${regionCount}`;
if (completionPercentageElement) completionPercentageElement.textContent = `${completionPercentage}%`;
if (progressFillElement) progressFillElement.style.width = `${completionPercentage}%`;
},
resetAllProgress: function() {
const questCheckboxes = safeQuerySelectorAll(SELECTORS.QUEST_CHECKBOX);
Array.from(questCheckboxes).forEach(cb => { cb.checked = false; cb.disabled = false; });
try { localStorage.removeItem(STORAGE_KEY); } catch (_) {}
this.syncRegionToggles();
this.applyDisabledStateAllRegions();
this.updateProgressDisplay();
},
syncRegionToggles: function() {
const regionCheckboxes = safeQuerySelectorAll(SELECTORS.REGION_CHECKBOX);
Array.from(regionCheckboxes).forEach(rc => {
const region = rc.getAttribute('data-region');
if (!region) return;
const items = safeQuerySelectorAll(SELECTORS.QUEST_CHECKBOX + `[data-region="${region}"]`);
const arr = Array.from(items);
rc.checked = arr.some(cb => cb.checked);
});
},
enforceSingleSelectionPerRegion: function() {
const sections = safeQuerySelectorAll(SELECTORS.LOCATION_SECTION);
Array.from(sections).forEach(section => {
const items = Array.from(safeQuerySelectorAll(SELECTORS.QUEST_CHECKBOX, section));
const checked = items.filter(cb => cb.checked);
if (checked.length > 1) {
checked.slice(1).forEach(cb => { cb.checked = false; });
}
});
this.saveQuestProgress();
},
applyDisabledStateAllRegions: function() {
const sections = safeQuerySelectorAll(SELECTORS.LOCATION_SECTION);
Array.from(sections).forEach(section => {
const items = Array.from(safeQuerySelectorAll(SELECTORS.QUEST_CHECKBOX, section));
const selected = items.find(cb => cb.checked);
if (selected) {
items.forEach(cb => { cb.disabled = cb !== selected; });
} else {
items.forEach(cb => { cb.disabled = false; });
}
});
}
};
window.QuestTracker = QuestTracker;
window.initQuestTracker = QuestTracker.init.bind(QuestTracker);
window.loadQuestProgress = QuestTracker.loadQuestProgress.bind(QuestTracker);
window.saveQuestProgress = QuestTracker.saveQuestProgress.bind(QuestTracker);
window.updateProgressDisplay = QuestTracker.updateProgressDisplay.bind(QuestTracker);
window.resetAllProgress = QuestTracker.resetAllProgress.bind(QuestTracker);
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function() { QuestTracker.init(); });
} else {
QuestTracker.init();
}
})();