MediaWiki:DailyQuestTracker.js: Difference between revisions
Jump to navigation
Jump to search
Created page with " '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, de..." |
No edit summary |
||
| Line 1: | Line 1: | ||
(function () { | |||
'use strict'; | |||
const STORAGE_KEY = 'cora.questTracker.dailyQuest.v1'; | |||
function | const SELECTORS = { | ||
try { return document. | locationSection: '.location-section', | ||
questCheckbox: '.quest-checkbox', | |||
trackerRootId: 'tab-tracker', | |||
totalProgress: '#total-progress', | |||
completionPercentage: '#completion-percentage', | |||
progressFill: '#progress-fill', | |||
resetButton: '#reset-progress' | |||
}; | |||
function safeQuerySelector(selector, context) { | |||
try { | |||
return (context || document).querySelector(selector); | |||
} catch (_) { | |||
return null; | |||
} | |||
} | } | ||
function safeQuerySelectorAll(selector, context | |||
try { return context.querySelectorAll(selector); } catch (_) { return []; } | function safeQuerySelectorAll(selector, context) { | ||
try { | |||
return Array.from((context || document).querySelectorAll(selector)); | |||
} catch (_) { | |||
return []; | |||
} | |||
} | } | ||
function | |||
try { return | function safeJsonParse(value) { | ||
try { | |||
return JSON.parse(value); | |||
} catch (_) { | |||
return null; | |||
} | |||
} | } | ||
function safeGetLocalStorage(key | |||
try { return localStorage.getItem(key) | function safeGetLocalStorage(key) { | ||
try { | |||
return localStorage.getItem(key); | |||
} catch (_) { | |||
return null; | |||
} | |||
} | } | ||
function safeSetLocalStorage(key, value) { | function safeSetLocalStorage(key, value) { | ||
try { localStorage.setItem(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 collectSections(root) { | |||
const sections = safeQuerySelectorAll(SELECTORS.locationSection, root); | |||
return sections | |||
.map((section) => ({ | |||
section, | |||
checkboxes: safeQuerySelectorAll(SELECTORS.questCheckbox, section) | |||
})) | |||
.filter((x) => x.checkboxes.length > 0); | |||
} | |||
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 applySingleSelectionPerSection(sections) { | |||
sections.forEach(({ checkboxes }) => { | |||
const checked = checkboxes.filter((cb) => cb.checked); | |||
const anyChecked = checked.length > 0; | |||
if (!anyChecked) { | |||
checkboxes.forEach((cb) => { | |||
cb.disabled = false; | |||
if (! | |||
}); | }); | ||
return; | |||
} | } | ||
const chosen = checked[0]; | |||
if (checked.length > 1) { | |||
const | checked.slice(1).forEach((cb) => { | ||
cb.checked = false; | |||
}); | |||
if (checked) { | |||
} | } | ||
checkboxes.forEach((cb) => { | |||
cb.disabled = cb !== chosen; | |||
}); | }); | ||
}); | |||
} | |||
function updateProgress(sections, root) { | |||
const totalSections = 12; | |||
const completedSectionsRaw = sections.filter(({ checkboxes }) => checkboxes.some((cb) => cb.checked)).length; | |||
const completedSections = Math.min(completedSectionsRaw, totalSections); | |||
const percentage = totalSections > 0 ? Math.round((completedSections / totalSections) * 100) : 0; | |||
const totalProgressEl = safeQuerySelector(SELECTORS.totalProgress, root); | |||
const completionEl = safeQuerySelector(SELECTORS.completionPercentage, root); | |||
const fillEl = safeQuerySelector(SELECTORS.progressFill, root); | |||
if (totalProgressEl) totalProgressEl.textContent = `${completedSections}/${totalSections}`; | |||
if (completionEl) completionEl.textContent = `${percentage}%`; | |||
if (fillEl) fillEl.style.width = `${percentage}%`; | |||
} | } | ||
function wireEvents(sections, allCheckboxes, root) { | |||
allCheckboxes.forEach((cb) => { | |||
cb.addEventListener('change', () => { | |||
applySingleSelectionPerSection(sections); | |||
saveState(allCheckboxes); | |||
updateProgress(sections, root); | |||
}); | }); | ||
}); | |||
} | |||
const resetBtn = safeQuerySelector(SELECTORS.resetButton, root); | |||
if (resetBtn) { | |||
resetBtn.addEventListener('click', () => { | |||
allCheckboxes.forEach((cb) => { | |||
cb.checked = false; | |||
cb.disabled = false; | |||
}); | |||
safeRemoveLocalStorage(STORAGE_KEY); | |||
updateProgress(sections, root); | |||
}); | }); | ||
} | } | ||
}; | } | ||
function init() { | |||
const root = document.getElementById(SELECTORS.trackerRootId); | |||
if (!root) return; | |||
const allCheckboxes = safeQuerySelectorAll(SELECTORS.questCheckbox, root); | |||
if (allCheckboxes.length === 0) return; | |||
const sections = collectSections(root); | |||
if (sections.length === 0) return; | |||
loadState(allCheckboxes); | |||
applySingleSelectionPerSection(sections); | |||
saveState(allCheckboxes); | |||
updateProgress(sections, root); | |||
wireEvents(sections, allCheckboxes, root); | |||
} | |||
if (document.readyState === 'loading') { | if (document.readyState === 'loading') { | ||
document.addEventListener('DOMContentLoaded', | document.addEventListener('DOMContentLoaded', init); | ||
} else { | } else { | ||
init(); | |||
} | } | ||
})(); | })(); | ||
Revision as of 05:20, 30 December 2025
(function () {
'use strict';
const STORAGE_KEY = 'cora.questTracker.dailyQuest.v1';
const SELECTORS = {
locationSection: '.location-section',
questCheckbox: '.quest-checkbox',
trackerRootId: 'tab-tracker',
totalProgress: '#total-progress',
completionPercentage: '#completion-percentage',
progressFill: '#progress-fill',
resetButton: '#reset-progress'
};
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 collectSections(root) {
const sections = safeQuerySelectorAll(SELECTORS.locationSection, root);
return sections
.map((section) => ({
section,
checkboxes: safeQuerySelectorAll(SELECTORS.questCheckbox, section)
}))
.filter((x) => x.checkboxes.length > 0);
}
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 applySingleSelectionPerSection(sections) {
sections.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 updateProgress(sections, root) {
const totalSections = 12;
const completedSectionsRaw = sections.filter(({ checkboxes }) => checkboxes.some((cb) => cb.checked)).length;
const completedSections = Math.min(completedSectionsRaw, totalSections);
const percentage = totalSections > 0 ? Math.round((completedSections / totalSections) * 100) : 0;
const totalProgressEl = safeQuerySelector(SELECTORS.totalProgress, root);
const completionEl = safeQuerySelector(SELECTORS.completionPercentage, root);
const fillEl = safeQuerySelector(SELECTORS.progressFill, root);
if (totalProgressEl) totalProgressEl.textContent = `${completedSections}/${totalSections}`;
if (completionEl) completionEl.textContent = `${percentage}%`;
if (fillEl) fillEl.style.width = `${percentage}%`;
}
function wireEvents(sections, allCheckboxes, root) {
allCheckboxes.forEach((cb) => {
cb.addEventListener('change', () => {
applySingleSelectionPerSection(sections);
saveState(allCheckboxes);
updateProgress(sections, root);
});
});
const resetBtn = safeQuerySelector(SELECTORS.resetButton, root);
if (resetBtn) {
resetBtn.addEventListener('click', () => {
allCheckboxes.forEach((cb) => {
cb.checked = false;
cb.disabled = false;
});
safeRemoveLocalStorage(STORAGE_KEY);
updateProgress(sections, root);
});
}
}
function init() {
const root = document.getElementById(SELECTORS.trackerRootId);
if (!root) return;
const allCheckboxes = safeQuerySelectorAll(SELECTORS.questCheckbox, root);
if (allCheckboxes.length === 0) return;
const sections = collectSections(root);
if (sections.length === 0) return;
loadState(allCheckboxes);
applySingleSelectionPerSection(sections);
saveState(allCheckboxes);
updateProgress(sections, root);
wireEvents(sections, allCheckboxes, root);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();