MediaWiki:Common.js: Difference between revisions
From EQOA Sandstorm Wiki
Jump to navigationJump to search
DevRenodor (talk | contribs) No edit summary |
DevRenodor (talk | contribs) No edit summary |
||
| Line 386: | Line 386: | ||
return link.textContent.trim(); | return link.textContent.trim(); | ||
} | } | ||
| Line 391: | Line 409: | ||
var table = document.getElementById('items-found-here-table'); | var table = document.getElementById('items-found-here-table'); | ||
var attempts = 0; | var attempts = 0; | ||
var itemLookup = {}; | |||
if (!table) return; | if (!table) return; | ||
function | function normalizeItemName(name) { | ||
return String(name || '') | |||
.replace(/’/g, "'") | |||
.replace(/’/g, "'") | |||
.replace(/`/g, "'") | |||
.replace(/\u00a0/g, ' ') | |||
.replace(/[“”]/g, '"') | |||
.replace(/[“â€]/g, '"') | |||
.replace(/\s+/g, ' ') | |||
.trim() | |||
.toLowerCase(); | |||
} | |||
function looseItemName(name) { | |||
return normalizeItemName(name).replace(/[^a-z0-9]/g, ''); | |||
} | } | ||
function normalizeRarity(rarity) { | function normalizeRarity(rarity) { | ||
| Line 433: | Line 445: | ||
} | } | ||
function | function buildItemLookup() { | ||
itemLookup = {}; | |||
var | |||
var | window.EQOA_GENERATED_ITEMS.forEach(function (item) { | ||
var normal; | |||
var loose; | |||
if (!item || !item.name) return; | |||
normal = normalizeItemName(item.name); | |||
loose = looseItemName(item.name); | |||
itemLookup[normal] = item; | |||
itemLookup[loose] = item; | |||
}); | |||
} | |||
function getItemNameFromHref(link) { | |||
var href = link.getAttribute('href') || ''; | |||
var title = ''; | |||
if (href.indexOf('title=') !== -1) { | |||
title = href.split('title=')[1].split('&')[0]; | |||
return decodeURIComponent(title).replace(/_/g, ' ').trim(); | |||
} | |||
if (href.indexOf('/index.php/') !== -1) { | |||
title = href.split('/index.php/')[1].split('?')[0]; | |||
return decodeURIComponent(title).replace(/_/g, ' ').trim(); | |||
} | |||
return ''; | |||
} | |||
function getPossibleItemNames(link, cell) { | |||
var names = []; | |||
var hrefName = getItemNameFromHref(link); | |||
var textName = link.textContent.trim(); | |||
var titleName = link.getAttribute('title') || ''; | |||
var cellText = cell ? cell.textContent.trim() : ''; | |||
if (hrefName) names.push(hrefName); | |||
if (textName) names.push(textName); | |||
if (titleName) names.push(titleName.replace(/\s*\(page does not exist\)\s*/i, '').trim()); | |||
if (cellText) names.push(cellText); | |||
return names; | |||
} | |||
function getItemByPossibleNames(names) { | |||
var i; | |||
var name; | |||
var normal; | |||
var loose; | |||
for (i = 0; i < names.length; i++) { | |||
name = names[i]; | |||
normal = normalizeItemName(name); | |||
loose = looseItemName(name); | |||
if (itemLookup[normal]) return itemLookup[normal]; | |||
} | if (itemLookup[loose]) return itemLookup[loose]; | ||
} | |||
return null; | |||
} | |||
function valueOf(item, keys) { | function valueOf(item, keys) { | ||
| Line 461: | Line 519: | ||
key = keys[i]; | key = keys[i]; | ||
if (item[key] !== undefined && item[key] !== null && String(item[key]) !== '' && String(item[key]) !== '0') { | if ( | ||
item[key] !== undefined && | |||
item[key] !== null && | |||
String(item[key]) !== '' && | |||
String(item[key]) !== '0' | |||
) { | |||
return item[key]; | return item[key]; | ||
} | } | ||
| Line 507: | Line 570: | ||
stats += addStat('', 'DMG', valueOf(item, ['dmg', 'damage'])); | stats += addStat('', 'DMG', valueOf(item, ['dmg', 'damage'])); | ||
stats += addStat( | stats += addStat('', 'AC', item.ac); | ||
stats += addStat('', 'STR', item.str); | stats += addStat('', 'STR', item.str); | ||
stats += addStat('', 'STA', item.sta); | stats += addStat('', 'STA', item.sta); | ||
| Line 595: | Line 658: | ||
Array.prototype.forEach.call(links, function (link) { | Array.prototype.forEach.call(links, function (link) { | ||
var | var possibleNames; | ||
var item; | var item; | ||
var rarity; | var rarity; | ||
| Line 601: | Line 664: | ||
if (link.classList.contains('items-found-tooltip-ready')) return; | if (link.classList.contains('items-found-tooltip-ready')) return; | ||
possibleNames = getPossibleItemNames(link, itemCell); | |||
item = | item = getItemByPossibleNames(possibleNames); | ||
if (!item) return; | if (!item) { | ||
console.log('ItemsFoundHere tooltip missing item:', possibleNames); | |||
return; | |||
} | |||
rarity = normalizeRarity(item.rarity); | rarity = normalizeRarity(item.rarity); | ||
| Line 616: | Line 682: | ||
link.addEventListener('mousemove', moveTooltip); | link.addEventListener('mousemove', moveTooltip); | ||
link.addEventListener('mouseleave', hideTooltip); | link.addEventListener('mouseleave', hideTooltip); | ||
}); | }); | ||
}); | }); | ||
} | |||
function waitForItems() { | |||
attempts += 1; | |||
if (!window.EQOA_GENERATED_ITEMS || !window.EQOA_GENERATED_ITEMS.length) { | |||
if (attempts < 40) { | |||
setTimeout(waitForItems, 250); | |||
} | |||
return; | |||
} | |||
buildItemLookup(); | |||
applyItemsFoundHereTooltips(); | |||
} | } | ||
waitForItems(); | waitForItems(); | ||
} | } | ||
function initRelatedItemTooltips($content) { | function initRelatedItemTooltips($content) { | ||
Revision as of 06:58, 1 June 2026
/* Any JavaScript here will be loaded for all users on every page load. */
(function () {
function rawPage(title) {
return mw.config.get('wgScript') +
'?title=' + encodeURIComponent(title) +
'&action=raw&ctype=text/javascript';
}
function loadScriptUrl(url) {
return $.getScript(url);
}
function loadWikiScript(title) {
return loadScriptUrl(rawPage(title));
}
function applyWikiTheme(theme) {
var isLight = theme === 'light';
var button = document.getElementById('eqoa-theme-toggle');
document.documentElement.classList.toggle('eqoa-theme-light', isLight);
document.documentElement.classList.toggle('eqoa-theme-dark', !isLight);
if (document.body) {
document.body.classList.toggle('eqoa-theme-light', isLight);
document.body.classList.toggle('eqoa-theme-dark', !isLight);
}
try {
localStorage.setItem('eqoa-wiki-theme', isLight ? 'light' : 'dark');
localStorage.setItem('eqoa-builder-theme', isLight ? 'light' : 'dark');
localStorage.setItem('eqoa-cm-theme', isLight ? 'light' : 'dark');
} catch (e) {}
if (button) {
button.innerHTML = isLight ? '☾' : '☼';
button.title = isLight ? 'Switch to Dark Theme' : 'Switch to White Theme';
button.setAttribute(
'aria-label',
isLight ? 'Switch to Dark Theme' : 'Switch to White Theme'
);
}
if (window.dispatchEvent) {
window.dispatchEvent(new CustomEvent('eqoa-wiki-theme-change', {
detail: {
theme: isLight ? 'light' : 'dark'
}
}));
}
setTimeout(styleRareEncounterRows, 50);
setTimeout(buildRareEncounterMobileLayout, 50);
}
function getSavedWikiTheme() {
try {
return localStorage.getItem('eqoa-wiki-theme') === 'light'
? 'light'
: 'dark';
} catch (e) {
return 'dark';
}
}
function ensureThemeButton() {
var button = document.getElementById('eqoa-theme-toggle');
if (!button) {
button = document.createElement('button');
button.id = 'eqoa-theme-toggle';
button.type = 'button';
button.className = 'eqoa-theme-toggle';
button.addEventListener('click', function () {
var isLight = document.body.classList.contains('eqoa-theme-light');
applyWikiTheme(isLight ? 'dark' : 'light');
});
document.body.appendChild(button);
}
applyWikiTheme(getSavedWikiTheme());
}
function initRaidChecks() {
var containers = document.querySelectorAll('.raid-checkboxify');
Array.prototype.forEach.call(containers, function (container) {
var links = container.querySelectorAll('a');
Array.prototype.forEach.call(links, function (a) {
var cb;
var label;
var key;
var stored;
if (a.classList.contains('raid-processed')) return;
cb = document.createElement('input');
cb.type = 'checkbox';
cb.className = 'raid-checkbox-input';
cb.style.marginRight = '8px';
cb.style.verticalAlign = 'middle';
label = document.createElement('label');
label.style.display = 'block';
label.style.cursor = 'pointer';
a.parentNode.insertBefore(label, a);
label.appendChild(cb);
label.appendChild(a);
a.classList.add('raid-processed');
key = 'raidcheck:' +
(document.title || location.pathname) +
':' +
a.getAttribute('href');
try {
stored = localStorage.getItem(key);
if (stored === '1') cb.checked = true;
} catch (e) {}
cb.addEventListener('change', function () {
try {
if (cb.checked) {
localStorage.setItem(key, '1');
} else {
localStorage.removeItem(key);
}
} catch (e) {}
});
});
});
}
function styleRareEncounterRows() {
var isLightTheme =
document.documentElement.classList.contains('eqoa-theme-light') ||
document.body.classList.contains('eqoa-theme-light');
var headerBg = isLightTheme ? '#3a2418' : '#3a3a3a';
var headerColor = isLightTheme ? '#ffffff' : '#ffd45a';
var headerBorder = isLightTheme ? '#5a3a25' : '#1f1f1f';
var rowBg1 = isLightTheme ? '#ffffff' : '#0d0c0b';
var rowBg2 = isLightTheme ? '#f7f5f1' : '#242428';
var rowColor = isLightTheme ? '#1f140c' : '#ffd45a';
var rowBorder = isLightTheme ? '#e6ded4' : '#252525';
document.querySelectorAll('.raid-encounter-table table').forEach(function (table) {
table.style.width = '100%';
table.style.tableLayout = 'fixed';
table.style.borderCollapse = 'collapse';
table.querySelectorAll('th').forEach(function (th) {
th.style.background = headerBg;
th.style.color = headerColor;
th.style.border = '1px solid ' + headerBorder;
th.style.padding = '8px';
th.style.textAlign = 'center';
th.style.fontWeight = 'bold';
});
table.querySelectorAll('td').forEach(function (td) {
td.style.verticalAlign = 'top';
td.style.padding = '0';
td.style.border = '1px solid ' + rowBorder;
td.style.background = rowBg1;
});
});
document.querySelectorAll('.raid-encounter-table .raid-checkboxify').forEach(function (container) {
var rowIndex = 0;
container.querySelectorAll('br').forEach(function (br) {
br.style.display = 'none';
});
container.querySelectorAll('b').forEach(function (zone) {
zone.style.display = 'block';
zone.style.background = headerBg;
zone.style.color = headerColor;
zone.style.borderTop = '1px solid ' + headerBorder;
zone.style.borderBottom = '1px solid ' + headerBorder;
zone.style.padding = '7px 8px';
zone.style.margin = '0';
zone.style.textAlign = 'center';
zone.style.fontSize = '15px';
zone.style.lineHeight = '1.15';
zone.style.fontWeight = 'bold';
});
container.querySelectorAll('label').forEach(function (label) {
rowIndex += 1;
label.style.display = 'block';
label.style.background = rowIndex % 2 === 0 ? rowBg2 : rowBg1;
label.style.color = rowColor;
label.style.borderBottom = '1px solid ' + rowBorder;
label.style.padding = '6px 8px';
label.style.margin = '0';
label.style.lineHeight = '1.25';
label.style.fontSize = '13px';
label.style.cursor = 'pointer';
label.querySelectorAll('a').forEach(function (link) {
link.style.color = rowColor;
link.style.fontWeight = 'bold';
link.style.textDecoration = 'none';
});
});
});
}
function initItemFilters() {
var table = document.getElementById('items-found-here-table');
var box;
var searchInput;
var classSelect;
var clearButton;
var activeMode = 'all';
if (!table) return;
if (document.getElementById('items-filter-box')) return;
box = document.createElement('div');
box.id = 'items-filter-box';
box.className = 'items-filter-box class-item-filter-panel';
box.innerHTML =
'<div class="items-filter-buttons">' +
'<button type="button" class="items-filter-mode is-active" data-mode="all">All</button>' +
'<button type="button" class="items-filter-mode" data-mode="weapon">Weapons</button>' +
'<button type="button" class="items-filter-mode" data-mode="armor">AC Gear</button>' +
'<button type="button" class="items-filter-mode" data-mode="stats">Stats</button>' +
'<button type="button" class="items-filter-mode" data-mode="resists">Resists</button>' +
'</div>' +
'<div class="items-filter-tools">' +
'<input type="search" id="filter-item-search" placeholder="Search items, mobs, stats...">' +
'<select id="filter-class-select">' +
'<option value="">All Classes</option>' +
'<option value="WAR">WAR</option>' +
'<option value="RAN">RAN</option>' +
'<option value="PAL">PAL</option>' +
'<option value="SK">SK</option>' +
'<option value="MNK">MNK</option>' +
'<option value="BRD">BRD</option>' +
'<option value="RGE">RGE</option>' +
'<option value="DRD">DRD</option>' +
'<option value="SHA">SHA</option>' +
'<option value="CL">CL</option>' +
'<option value="MAG">MAG</option>' +
'<option value="NEC">NEC</option>' +
'<option value="ENC">ENC</option>' +
'<option value="WIZ">WIZ</option>' +
'<option value="ALC">ALC</option>' +
'</select>' +
'<button id="clear-item-filters" type="button">Clear</button>' +
'</div>';
table.parentNode.insertBefore(box, table);
searchInput = document.getElementById('filter-item-search');
classSelect = document.getElementById('filter-class-select');
clearButton = document.getElementById('clear-item-filters');
function getCellText(row, index) {
return row.cells[index] ? row.cells[index].textContent.trim() : '';
}
function getCellNumber(row, index) {
var text = getCellText(row, index);
var num = parseFloat(text.replace(/[^\d.-]/g, ''));
return isNaN(num) ? 0 : num;
}
function rowHasAnyStat(row) {
var i;
for (i = 6; i <= 16; i++) {
if (getCellNumber(row, i) !== 0) return true;
}
return false;
}
function rowHasAnyResist(row) {
var i;
for (i = 17; i <= 22; i++) {
if (getCellNumber(row, i) !== 0) return true;
}
return false;
}
function classMatches(row, selectedClass) {
var classText = getCellText(row, 2).toUpperCase();
if (!selectedClass) return true;
if (classText === 'ALL') return true;
if (classText.indexOf('ALL') !== -1) return true;
return classText.split(/[\s,\/]+/).indexOf(selectedClass) !== -1;
}
function modeMatches(row) {
if (activeMode === 'weapon') return getCellNumber(row, 4) > 0;
if (activeMode === 'armor') return getCellNumber(row, 5) > 0;
if (activeMode === 'stats') return rowHasAnyStat(row);
if (activeMode === 'resists') return rowHasAnyResist(row);
return true;
}
function filterTable() {
var query = String(searchInput.value || '').toLowerCase();
var selectedClass = classSelect.value;
var i;
var row;
var rowText;
var show;
for (i = 1; i < table.rows.length; i++) {
row = table.rows[i];
rowText = row.textContent.toLowerCase();
show = true;
if (query && rowText.indexOf(query) === -1) show = false;
if (!classMatches(row, selectedClass)) show = false;
if (!modeMatches(row)) show = false;
row.style.display = show ? '' : 'none';
}
}
box.querySelectorAll('.items-filter-mode').forEach(function (button) {
button.addEventListener('click', function () {
box.querySelectorAll('.items-filter-mode').forEach(function (btn) {
btn.classList.remove('is-active');
});
button.classList.add('is-active');
activeMode = button.getAttribute('data-mode') || 'all';
filterTable();
});
});
searchInput.addEventListener('input', filterTable);
classSelect.addEventListener('change', filterTable);
clearButton.addEventListener('click', function () {
activeMode = 'all';
searchInput.value = '';
classSelect.value = '';
box.querySelectorAll('.items-filter-mode').forEach(function (btn) {
btn.classList.toggle('is-active', btn.getAttribute('data-mode') === 'all');
});
filterTable();
});
}
function getItemNameFromLink(link) {
var href = link.getAttribute('href') || '';
var title = '';
if (href.indexOf('title=') !== -1) {
title = href.split('title=')[1].split('&')[0];
return decodeURIComponent(title).replace(/_/g, ' ').trim();
}
if (href.indexOf('/index.php/') !== -1) {
title = href.split('/index.php/')[1].split('?')[0];
return decodeURIComponent(title).replace(/_/g, ' ').trim();
}
return link.textContent.trim();
}
function initItemsFoundHereRarityTooltips() {
var table = document.getElementById('items-found-here-table');
var attempts = 0;
var itemLookup = {};
if (!table) return;
function normalizeItemName(name) {
return String(name || '')
.replace(/’/g, "'")
.replace(/’/g, "'")
.replace(/`/g, "'")
.replace(/\u00a0/g, ' ')
.replace(/[“”]/g, '"')
.replace(/[“â€]/g, '"')
.replace(/\s+/g, ' ')
.trim()
.toLowerCase();
}
function looseItemName(name) {
return normalizeItemName(name).replace(/[^a-z0-9]/g, '');
}
function normalizeRarity(rarity) {
return String(rarity || 'common')
.toLowerCase()
.replace(/\s+/g, '-');
}
function escapeHtml(value) {
return String(value == null ? '' : value)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
function buildItemLookup() {
itemLookup = {};
window.EQOA_GENERATED_ITEMS.forEach(function (item) {
var normal;
var loose;
if (!item || !item.name) return;
normal = normalizeItemName(item.name);
loose = looseItemName(item.name);
itemLookup[normal] = item;
itemLookup[loose] = item;
});
}
function getItemNameFromHref(link) {
var href = link.getAttribute('href') || '';
var title = '';
if (href.indexOf('title=') !== -1) {
title = href.split('title=')[1].split('&')[0];
return decodeURIComponent(title).replace(/_/g, ' ').trim();
}
if (href.indexOf('/index.php/') !== -1) {
title = href.split('/index.php/')[1].split('?')[0];
return decodeURIComponent(title).replace(/_/g, ' ').trim();
}
return '';
}
function getPossibleItemNames(link, cell) {
var names = [];
var hrefName = getItemNameFromHref(link);
var textName = link.textContent.trim();
var titleName = link.getAttribute('title') || '';
var cellText = cell ? cell.textContent.trim() : '';
if (hrefName) names.push(hrefName);
if (textName) names.push(textName);
if (titleName) names.push(titleName.replace(/\s*\(page does not exist\)\s*/i, '').trim());
if (cellText) names.push(cellText);
return names;
}
function getItemByPossibleNames(names) {
var i;
var name;
var normal;
var loose;
for (i = 0; i < names.length; i++) {
name = names[i];
normal = normalizeItemName(name);
loose = looseItemName(name);
if (itemLookup[normal]) return itemLookup[normal];
if (itemLookup[loose]) return itemLookup[loose];
}
return null;
}
function valueOf(item, keys) {
var i;
var key;
for (i = 0; i < keys.length; i++) {
key = keys[i];
if (
item[key] !== undefined &&
item[key] !== null &&
String(item[key]) !== '' &&
String(item[key]) !== '0'
) {
return item[key];
}
}
return '';
}
function addStat(html, label, value) {
if (value === undefined || value === null || String(value) === '' || String(value) === '0') {
return html;
}
return html + '<div><b>' + label + ':</b> ' + escapeHtml(value) + '</div>';
}
function buildItemsFoundHereTooltip(item) {
var rarity = normalizeRarity(item.rarity);
var html = '';
var flags = '';
var stats = '';
html += '<div class="class-item-tooltip-title class-item-rarity-' + rarity + '">' +
escapeHtml(item.name) +
'</div>';
if (item.notrade || item.noTrade || item.trade === 'NO TRADE') {
flags += '<span>NO TRADE</span>';
}
if (item.lore) {
flags += '<span>LORE</span>';
}
if (flags) {
html += '<div class="class-item-tooltip-flags">' + flags + '</div>';
}
html += addStat('', 'Level', valueOf(item, ['level', 'lvl']));
html += addStat('', 'Slot', item.slot);
html += addStat('', 'DUR', item.dur);
html += addStat('', 'Gear HP', valueOf(item, ['gearhp', 'gearHp', 'gear_hp']));
html += addStat('', 'Race', item.race);
html += addStat('', 'Class', item.className || item.class || item.classes);
stats += addStat('', 'DMG', valueOf(item, ['dmg', 'damage']));
stats += addStat('', 'AC', item.ac);
stats += addStat('', 'STR', item.str);
stats += addStat('', 'STA', item.sta);
stats += addStat('', 'AGI', item.agi);
stats += addStat('', 'DEX', item.dex);
stats += addStat('', 'WIS', item.wis);
stats += addStat('', 'INT', item.int);
stats += addStat('', 'CHA', item.cha);
stats += addStat('', 'HP', item.hp);
stats += addStat('', 'HoT', item.hot);
stats += addStat('', 'POW', item.pow);
stats += addStat('', 'PoT', item.pot);
stats += addStat('', 'PR', item.pr);
stats += addStat('', 'DR', item.dr);
stats += addStat('', 'FR', item.fr);
stats += addStat('', 'CR', item.cr);
stats += addStat('', 'LR', item.lr);
stats += addStat('', 'AR', item.ar);
if (stats) {
html += '<div class="class-item-tooltip-section">Item Stats</div>';
html += '<div class="class-item-tooltip-stats">' + stats + '</div>';
}
return html;
}
function getTooltipBox() {
var tooltip = document.getElementById('items-found-here-tooltip');
if (!tooltip) {
tooltip = document.createElement('div');
tooltip.id = 'items-found-here-tooltip';
tooltip.className = 'class-item-tooltip';
tooltip.style.display = 'none';
document.body.appendChild(tooltip);
}
return tooltip;
}
function moveTooltip(event) {
var tooltip = getTooltipBox();
var left = event.pageX + 16;
var top = event.pageY + 16;
if (left + 380 > window.scrollX + window.innerWidth) {
left = event.pageX - 380;
}
tooltip.style.left = left + 'px';
tooltip.style.top = top + 'px';
}
function showTooltip(item, event) {
var tooltip = getTooltipBox();
var rarity = normalizeRarity(item.rarity);
tooltip.className = 'class-item-tooltip class-item-tooltip-rarity-' + rarity;
tooltip.innerHTML = buildItemsFoundHereTooltip(item);
tooltip.style.display = 'block';
moveTooltip(event);
}
function hideTooltip() {
var tooltip = document.getElementById('items-found-here-tooltip');
if (tooltip) {
tooltip.style.display = 'none';
}
}
function applyItemsFoundHereTooltips() {
var rows = table.querySelectorAll('tr');
Array.prototype.forEach.call(rows, function (row, index) {
var itemCell;
var links;
if (index === 0) return;
itemCell = row.cells && row.cells[0] ? row.cells[0] : null;
if (!itemCell) return;
links = itemCell.querySelectorAll('a');
Array.prototype.forEach.call(links, function (link) {
var possibleNames;
var item;
var rarity;
if (link.classList.contains('items-found-tooltip-ready')) return;
possibleNames = getPossibleItemNames(link, itemCell);
item = getItemByPossibleNames(possibleNames);
if (!item) {
console.log('ItemsFoundHere tooltip missing item:', possibleNames);
return;
}
rarity = normalizeRarity(item.rarity);
link.classList.add('items-found-tooltip-ready');
link.classList.add('class-item-rarity-' + rarity);
link.addEventListener('mouseenter', function (event) {
showTooltip(item, event);
});
link.addEventListener('mousemove', moveTooltip);
link.addEventListener('mouseleave', hideTooltip);
});
});
}
function waitForItems() {
attempts += 1;
if (!window.EQOA_GENERATED_ITEMS || !window.EQOA_GENERATED_ITEMS.length) {
if (attempts < 40) {
setTimeout(waitForItems, 250);
}
return;
}
buildItemLookup();
applyItemsFoundHereTooltips();
}
waitForItems();
}
function initRelatedItemTooltips($content) {
var root = $content && $content[0] ? $content[0] : document;
var relatedLinks = root.querySelectorAll(
'.eqoa-related-items a, ' +
'.zone-monster-table td:nth-child(3) a, ' +
'.zone-monster-drops a'
);
var tooltip = document.getElementById('eqoa-related-item-tooltip');
var cache = {};
if (!relatedLinks.length) return;
if (!tooltip) {
tooltip = document.createElement('div');
tooltip.id = 'eqoa-related-item-tooltip';
tooltip.className = 'eqoa-related-item-tooltip';
tooltip.style.display = 'none';
document.body.appendChild(tooltip);
}
function normalizeRarity(rarity) {
return String(rarity || 'common')
.toLowerCase()
.replace(/\s+/g, '-');
}
function getPageTitleFromLink(link) {
var href = link.getAttribute('href') || '';
var articlePath = mw.config.get('wgArticlePath') || '';
var title;
if (href.indexOf('/index.php/') !== -1) {
title = href.split('/index.php/')[1];
return decodeURIComponent(title).replace(/_/g, ' ');
}
if (articlePath && articlePath.indexOf('$1') !== -1) {
title = href.substring(href.lastIndexOf('/') + 1);
return decodeURIComponent(title).replace(/_/g, ' ');
}
return link.textContent.trim();
}
function parseItemTemplate(wikitext, fallbackName) {
var match = wikitext.match(/\{\{(?:ItemBox|ItemPage)([\s\S]*?)\}\}/i);
var item;
var lines;
if (!match) return null;
item = {
name: fallbackName
};
lines = match[1].split('\n');
lines.forEach(function (line) {
var eq;
var key;
var value;
line = line.trim();
if (line.indexOf('|') !== 0) return;
eq = line.indexOf('=');
if (eq === -1) return;
key = line.substring(1, eq).trim().toLowerCase();
value = line.substring(eq + 1).trim();
if (key) {
item[key] = value;
}
});
if (!item.name) item.name = fallbackName;
return item;
}
function statLine(item, label, key) {
if (!item[key] || item[key] === '0') return '';
return '<div><span>' + label + '</span> ' + item[key] + '</div>';
}
function buildTooltip(item) {
var rarity = normalizeRarity(item.rarity);
var rarityClass = 'cb-rarity-' + rarity;
var html = '';
html += '<div class="eqoa-related-tooltip-title ' + rarityClass + '">' +
item.name +
'</div>';
if (item.slot) html += '<div><b>Equip:</b> ' + item.slot + '</div>';
if (item.class) html += '<div><b>Class:</b> ' + item.class + '</div>';
if (item.lvl) html += '<div><b>Level:</b> ' + item.lvl + '</div>';
if (item.dur) html += '<div><b>DUR:</b> ' + item.dur + '</div>';
html += '<div class="eqoa-related-tooltip-stats">';
html += statLine(item, 'DMG:', 'dmg');
html += statLine(item, 'AC:', 'ac');
html += statLine(item, 'STR:', 'str');
html += statLine(item, 'STA:', 'sta');
html += statLine(item, 'AGI:', 'agi');
html += statLine(item, 'DEX:', 'dex');
html += statLine(item, 'WIS:', 'wis');
html += statLine(item, 'INT:', 'int');
html += statLine(item, 'CHA:', 'cha');
html += statLine(item, 'HP:', 'hp');
html += statLine(item, 'POW:', 'pow');
html += statLine(item, 'PR:', 'pr');
html += statLine(item, 'DR:', 'dr');
html += statLine(item, 'FR:', 'fr');
html += statLine(item, 'CR:', 'cr');
html += statLine(item, 'LR:', 'lr');
html += statLine(item, 'AR:', 'ar');
html += '</div>';
if (item.effect) html += '<div><b>Effect:</b> ' + item.effect + '</div>';
if (item.proc) html += '<div><b>Proc:</b> ' + item.proc + '</div>';
return html;
}
function moveTooltip(event) {
var left = event.pageX + 16;
var top = event.pageY + 16;
if (left + 360 > window.scrollX + window.innerWidth) {
left = event.pageX - 360;
}
tooltip.style.left = left + 'px';
tooltip.style.top = top + 'px';
}
function showTooltip(link, event) {
var title = getPageTitleFromLink(link);
tooltip.className = 'eqoa-related-item-tooltip';
tooltip.innerHTML = 'Loading...';
tooltip.style.display = 'block';
moveTooltip(event);
if (cache[title]) {
tooltip.className =
'eqoa-related-item-tooltip tooltip-rarity-' +
normalizeRarity(cache[title].rarity);
tooltip.innerHTML = buildTooltip(cache[title]);
return;
}
new mw.Api().get({
action: 'query',
prop: 'revisions',
titles: title,
rvprop: 'content',
rvslots: 'main',
formatversion: '2',
format: 'json'
}).then(function (data) {
var page = data.query.pages[0];
var text = '';
var item;
if (page && page.revisions && page.revisions[0]) {
if (page.revisions[0].slots && page.revisions[0].slots.main) {
text = page.revisions[0].slots.main.content || '';
} else {
text = page.revisions[0].content || '';
}
}
item = parseItemTemplate(text, title);
if (!item) {
tooltip.innerHTML = 'No item data found.';
return;
}
cache[title] = item;
tooltip.className =
'eqoa-related-item-tooltip tooltip-rarity-' +
normalizeRarity(item.rarity);
tooltip.innerHTML = buildTooltip(item);
});
}
Array.prototype.forEach.call(relatedLinks, function (link) {
if (link.classList.contains('eqoa-related-tooltip-ready')) return;
link.classList.add('eqoa-related-tooltip-ready');
link.addEventListener('mouseenter', function (event) {
showTooltip(link, event);
});
link.addEventListener('mousemove', moveTooltip);
link.addEventListener('mouseleave', function () {
tooltip.style.display = 'none';
});
});
}
function ensureMobileSidebarButton() {
if (document.getElementById('eqoa-mobile-sidebar-toggle')) return;
var button = document.createElement('button');
button.id = 'eqoa-mobile-sidebar-toggle';
button.type = 'button';
button.textContent = '☰';
button.setAttribute('aria-label', 'Open sidebar');
button.addEventListener('click', function () {
document.documentElement.classList.toggle('eqoa-mobile-sidebar-open');
setTimeout(removeMobilePageActions, 50);
setTimeout(removeMobilePageActions, 250);
});
document.body.appendChild(button);
}
function stackZoneColumnsOnMobile() {
if (!window.matchMedia || !window.matchMedia('(max-width: 700px)').matches) return;
if (document.querySelector('.eqoa-mobile-zone-stack')) return;
var left = document.querySelector('.zone-left');
var right = document.querySelector('.zone-right');
if (!left || !right) return;
var leftTable = left.closest('table');
var rightTable = right.closest('table');
var layoutTable = leftTable && leftTable === rightTable ? leftTable : null;
var stack = document.createElement('div');
stack.className = 'eqoa-mobile-zone-stack';
if (layoutTable) {
layoutTable.parentNode.insertBefore(stack, layoutTable);
if (left.tagName.toLowerCase() === 'td') {
var leftBox = document.createElement('div');
leftBox.className = left.className;
while (left.firstChild) leftBox.appendChild(left.firstChild);
stack.appendChild(leftBox);
} else {
stack.appendChild(left);
}
if (right.tagName.toLowerCase() === 'td') {
var rightBox = document.createElement('div');
rightBox.className = right.className;
while (right.firstChild) rightBox.appendChild(right.firstChild);
stack.appendChild(rightBox);
} else {
stack.appendChild(right);
}
layoutTable.style.setProperty('display', 'none', 'important');
} else {
left.parentNode.insertBefore(stack, left);
stack.appendChild(left);
stack.appendChild(right);
}
stack.querySelectorAll('.zone-left, .zone-right').forEach(function (el) {
el.style.setProperty('display', 'block', 'important');
el.style.setProperty('float', 'none', 'important');
el.style.setProperty('clear', 'both', 'important');
el.style.setProperty('width', '100%', 'important');
el.style.setProperty('max-width', '100%', 'important');
el.style.setProperty('margin', '0 0 12px 0', 'important');
});
}
function removeMobilePageActions() {
return;
if (!window.matchMedia || !window.matchMedia('(max-width: 700px)').matches) return;
var selectors = [
'#p-cactions',
'#p-views',
'#p-namespaces',
'#left-navigation',
'#right-navigation',
'#head',
'#mw-head',
'li[id^="ca-"]',
'a[id^="ca-"]',
'[id^="ca-"]'
];
selectors.forEach(function (selector) {
document.querySelectorAll(selector).forEach(function (el) {
el.remove();
});
});
document.querySelectorAll('#column-one .portlet, #column-one .generated-sidebar, #column-one div').forEach(function (el) {
var label = '';
var heading = el.querySelector('h3, h4, h5');
if (heading) {
label = heading.textContent.trim().toUpperCase();
} else {
label = el.textContent.trim().toUpperCase();
}
if (label.indexOf('PAGE ACTIONS') !== -1) {
el.remove();
}
});
document.querySelectorAll('#column-one a, #column-one button, #column-one input, #column-one img').forEach(function (el) {
if (el.closest('#p-search')) return;
if (el.id === 'eqoa-mobile-sidebar-toggle' || el.id === 'eqoa-theme-toggle') return;
var visibleText = (el.textContent || el.value || '').trim();
var rect = el.getBoundingClientRect();
if (
!visibleText &&
rect.width > 0 &&
rect.height > 0 &&
rect.width <= 70 &&
rect.height <= 70
) {
var removeTarget = el.closest('li, a, button, input, div') || el;
removeTarget.remove();
}
});
document.querySelectorAll('a, button, input, li, div, img').forEach(function (el) {
if (el.id === 'eqoa-mobile-sidebar-toggle' || el.id === 'eqoa-theme-toggle') return;
if (el.closest('#p-search')) return;
var rect = el.getBoundingClientRect();
var visibleText = (el.textContent || el.value || '').trim();
if (
!visibleText &&
rect.width > 0 &&
rect.height > 0 &&
rect.width <= 70 &&
rect.height <= 70 &&
rect.top >= 0 &&
rect.top <= 140
) {
var removeTarget = el.closest('li, a, button, input, div') || el;
removeTarget.remove();
}
});
}
function loadBuilderScripts() {
loadWikiScript('MediaWiki:EQOAConfig.js')
.always(function () {
loadWikiScript('MediaWiki:CharacterBuilderData.js')
.done(function () {
$.when(
loadScriptUrl('https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js'),
loadScriptUrl('https://cdnjs.cloudflare.com/ajax/libs/lz-string/1.5.0/lz-string.min.js')
).always(function () {
loadWikiScript('MediaWiki:CharacterBuilder.js')
.fail(function (error) {
console.error('Character Builder failed to load:', error);
});
});
})
.fail(function (error) {
console.error('Character Builder data failed to load:', error);
});
});
}
function loadClassMasteryScripts() {
loadWikiScript('MediaWiki:ClassMasteryData.js')
.done(function () {
loadWikiScript('MediaWiki:ClassMasteryRawData.js')
.always(function () {
loadWikiScript('MediaWiki:ClassMasteryIds.js')
.always(function () {
loadWikiScript('MediaWiki:ClassMasteryCalculator.js')
.fail(function (error) {
console.error('Class Mastery calculator failed to load:', error);
});
});
});
})
.fail(function (error) {
console.error('Class Mastery data failed to load:', error);
});
}
window.EQOA_APPLY_WIKI_THEME = applyWikiTheme;
window.EQOA_GET_WIKI_THEME = getSavedWikiTheme;
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', ensureThemeButton);
} else {
ensureThemeButton();
}
mw.hook('wikipage.content').add(function ($content) {
ensureThemeButton();
ensureMobileSidebarButton();
stackZoneColumnsOnMobile();
removeMobilePageActions();
ensureMobileSidebarButton();
setTimeout(stackZoneColumnsOnMobile, 250);
setTimeout(stackZoneColumnsOnMobile, 1000);
setTimeout(function () {
removeMobilePageActions();
ensureMobileSidebarButton();
}, 250);
setTimeout(function () {
removeMobilePageActions();
ensureMobileSidebarButton();
}, 1000);
initRaidChecks();
styleRareEncounterRows();
setTimeout(styleRareEncounterRows, 100);
setTimeout(styleRareEncounterRows, 500);
setTimeout(buildRareEncounterMobileLayout, 100);
setTimeout(buildRareEncounterMobileLayout, 500);
initItemFilters();
initItemsFoundHereRarityTooltips();
mw.loader.using('mediawiki.api').then(function () {
initRelatedItemTooltips($content);
});
});
mw.loader.using('mediawiki.util').then(function () {
loadBuilderScripts();
loadClassMasteryScripts();
});
})();
function buildRareEncounterMobileLayout() {
var isMobile = window.matchMedia('(max-width: 700px)').matches;
var tables = document.querySelectorAll('.raid-encounter-table table');
function styleRareMobileHeadings(root) {
var isLightTheme =
document.documentElement.classList.contains('eqoa-theme-light') ||
document.body.classList.contains('eqoa-theme-light');
Array.prototype.forEach.call(root.querySelectorAll('.rare-mobile-heading'), function (heading) {
heading.style.padding = '9px 10px';
heading.style.textAlign = 'center';
heading.style.fontSize = '18px';
heading.style.fontWeight = 'bold';
heading.style.borderBottom = isLightTheme ? '1px solid #5a3a25' : '1px solid #1f1f1f';
heading.style.background = isLightTheme ? '#3a2418' : '#3a3a3a';
heading.style.color = isLightTheme ? '#ffffff' : '#ffd45a';
});
}
Array.prototype.forEach.call(tables, function (table) {
var wrapper = table.closest('.raid-encounter-table');
if (!wrapper) return;
var existingMobile = wrapper.querySelector('.rare-encounter-mobile-layout');
if (!isMobile) {
table.style.display = '';
if (existingMobile) existingMobile.style.display = 'none';
return;
}
table.style.display = 'none';
if (existingMobile) {
existingMobile.style.display = 'block';
styleRareMobileHeadings(existingMobile);
return;
}
var mobile = document.createElement('div');
mobile.className = 'rare-encounter-mobile-layout';
var rows = table.querySelectorAll('tr');
function addMobileBox(title, cell) {
if (!cell || !cell.textContent.trim()) return;
var box = document.createElement('div');
box.className = 'rare-mobile-box';
var heading = document.createElement('div');
heading.className = 'rare-mobile-heading';
heading.textContent = title;
var body = document.createElement('div');
body.className = 'rare-mobile-body';
body.innerHTML = cell.innerHTML;
box.appendChild(heading);
box.appendChild(body);
mobile.appendChild(box);
}
if (rows[1]) {
var baseCells = rows[1].querySelectorAll('td');
addMobileBox('Base World 1', baseCells[0]);
addMobileBox('Base World 2', baseCells[1]);
addMobileBox('Base World 3', baseCells[2]);
addMobileBox('Base World 4', baseCells[3]);
}
if (rows[3]) {
var frontiersCells = rows[3].querySelectorAll('td');
addMobileBox('Frontiers 1', frontiersCells[0]);
addMobileBox('Frontiers 2', frontiersCells[1]);
addMobileBox('Frontiers 3', frontiersCells[2]);
}
wrapper.appendChild(mobile);
styleRareMobileHeadings(mobile);
});
}
window.addEventListener('resize', buildRareEncounterMobileLayout);
window.addEventListener('orientationchange', function () {
setTimeout(buildRareEncounterMobileLayout, 150);
});
function limitDevRenodorSidebarSections() {
var username = mw.config.get('wgUserName');
var isDevRenodor = username === 'DevRenodor';
['p-navigation', 'p-tb'].forEach(function (id) {
var section = document.getElementById(id);
if (section) {
section.style.display = isDevRenodor ? '' : 'none';
}
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', limitDevRenodorSidebarSections);
} else {
limitDevRenodorSidebarSections();
}
$(function () {
if (window.EQOA_APPLY_WIKI_THEME && window.EQOA_GET_WIKI_THEME) {
window.EQOA_APPLY_WIKI_THEME(window.EQOA_GET_WIKI_THEME());
}
});
function limitFlaggedRevsBadgeToDevRenodor() {
var username = mw.config.get('wgUserName');
var isDevRenodor = username === 'DevRenodor';
[
'#mw-fr-revisiontag',
'.flaggedrevs_short',
'.flaggedrevs_basic',
'.flaggedrevs_preview',
'.flaggedrevs_notice',
'.flaggedrevs_reviewform',
'.mw-fr-reviewlink'
].forEach(function (selector) {
document.querySelectorAll(selector).forEach(function (el) {
el.style.setProperty('display', isDevRenodor ? 'block' : 'none', 'important');
});
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', limitFlaggedRevsBadgeToDevRenodor);
} else {
limitFlaggedRevsBadgeToDevRenodor();
}
function fixPersonalNotificationButtons() {
function fixButton(id, labelText) {
var item = document.getElementById(id);
if (!item) return;
var link = item.querySelector('a');
if (!link) return;
var rawText = link.textContent || '';
var countMatch = rawText.match(/\d+/);
var count = countMatch ? countMatch[0] : '';
link.textContent = count ? labelText + ' (' + count + ')' : labelText;
link.setAttribute('title', labelText);
link.setAttribute('aria-label', labelText);
}
fixButton('pt-notifications-alert', 'alerts');
fixButton('pt-notifications-notice', 'notices');
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', fixPersonalNotificationButtons);
} else {
fixPersonalNotificationButtons();
}
mw.hook('wikipage.content').add(fixPersonalNotificationButtons);
setTimeout(fixPersonalNotificationButtons, 500);
setTimeout(fixPersonalNotificationButtons, 1500);
mw.hook('wikipage.content').add(limitFlaggedRevsBadgeToDevRenodor);
mw.hook('wikipage.content').add(limitDevRenodorSidebarSections);
mw.loader.load(mw.config.get('wgScript') + '?title=MediaWiki:CharacterBuilderGeneratedItems.js&action=raw&ctype=text/javascript');
mw.loader.using( 'mediawiki.util', function () {
var topbar = document.createElement( 'div' );
topbar.id = 'eqoa-topbar';
topbar.innerHTML =
'<a class="eqoa-top-title" href="' + mw.util.getUrl( 'Main Page' ) + '">EQOA: Sandstorm</a>' +
'<form class="eqoa-top-search" action="' + mw.util.getUrl( 'Special:Search' ) + '" method="get">' +
'<input type="search" name="search" placeholder="Search EQOA: Sandstorm" />' +
'<button type="submit">Search</button>' +
'</form>' +
'<a class="eqoa-discord-button" href="https://discord.gg/QK6r8Qd8Vu" target="_blank" rel="noopener">Join the Community</a>';
document.body.insertBefore( topbar, document.body.firstChild );
} );
mw.loader.using( 'mediawiki.util', function () {
if ( document.getElementById( 'eqoa-right-rail' ) ) {
return;
}
var rail = document.createElement( 'aside' );
rail.id = 'eqoa-right-rail';
rail.innerHTML =
'<iframe ' +
'src="https://discord.com/widget?id=1251536624467050628&theme=dark" ' +
'width="300" ' +
'height="500" ' +
'allowtransparency="true" ' +
'frameborder="0" ' +
'sandbox="allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts">' +
'</iframe>';
document.body.appendChild( rail );
} );
function eqoaKeepPersonalToolsVisible() {
var personal = document.getElementById( 'p-personal' );
if ( !personal ) {
return;
}
personal.style.display = 'block';
personal.style.visibility = 'visible';
personal.style.opacity = '1';
personal.style.position = 'fixed';
personal.style.top = '72px';
personal.style.right = '18px';
personal.style.left = 'auto';
personal.style.zIndex = '2147483647';
personal.style.background = 'transparent';
personal.style.overflow = 'visible';
var body = personal.querySelector( '.pBody' );
var list = personal.querySelector( 'ul' );
[ body, list ].forEach( function ( el ) {
if ( !el ) {
return;
}
el.style.display = 'flex';
el.style.visibility = 'visible';
el.style.opacity = '1';
el.style.gap = '4px';
el.style.alignItems = 'center';
el.style.justifyContent = 'flex-end';
el.style.background = 'transparent';
el.style.border = '0';
el.style.margin = '0';
el.style.padding = '0';
el.style.listStyle = 'none';
} );
personal.querySelectorAll( 'li, a' ).forEach( function ( el ) {
el.style.display = 'inline-flex';
el.style.visibility = 'visible';
el.style.opacity = '1';
} );
}
document.addEventListener( 'DOMContentLoaded', eqoaKeepPersonalToolsVisible );
mw.hook( 'wikipage.content' ).add( eqoaKeepPersonalToolsVisible );
setTimeout( eqoaKeepPersonalToolsVisible, 250 );
setTimeout( eqoaKeepPersonalToolsVisible, 1000 );
setTimeout( eqoaKeepPersonalToolsVisible, 2000 );
function eqoaBuildSidebarAccountButtons() {
var sidebar = document.getElementById('column-one');
var personal = document.getElementById('p-personal');
if (!sidebar || !personal) return;
var oldBox = document.getElementById('eqoa-sidebar-account');
if (oldBox) oldBox.remove();
var links = personal.querySelectorAll('a');
if (!links.length) return;
var box = document.createElement('div');
box.id = 'eqoa-sidebar-account';
var title = document.createElement('div');
title.className = 'eqoa-sidebar-account-title';
title.textContent = 'Account';
box.appendChild(title);
var grid = document.createElement('div');
grid.className = 'eqoa-sidebar-account-grid';
box.appendChild(grid);
links.forEach(function (link) {
var text = (link.textContent || '').trim();
if (!text) return;
var clone = document.createElement('a');
clone.href = link.href;
clone.textContent = text;
clone.title = link.title || text;
clone.className = 'eqoa-sidebar-account-button';
grid.appendChild(clone);
});
var logo = document.getElementById('p-logo');
if (logo && logo.parentNode === sidebar) {
logo.insertAdjacentElement('afterend', box);
} else {
sidebar.insertBefore(box, sidebar.firstChild);
}
}
/* =========================================================
EQOA Interactive Maps - Load Leaflet
========================================================= */
mw.loader.load('https://unpkg.com/leaflet@1.9.4/dist/leaflet.js');
mw.loader.load('https://unpkg.com/leaflet@1.9.4/dist/leaflet.css', 'text/css');
/* =========================================================
EQOA Interactive Maps
========================================================= */
function initEqoaInteractiveMaps() {
if (typeof L === 'undefined') {
setTimeout(initEqoaInteractiveMaps, 100);
return;
}
document.querySelectorAll('.eqoa-interactive-map').forEach(function (mapBox) {
if (mapBox.classList.contains('eqoa-map-ready')) return;
var imageUrl = mapBox.getAttribute('data-image');
var width = parseInt(mapBox.getAttribute('data-width'), 10) || 2048;
var height = parseInt(mapBox.getAttribute('data-height'), 10) || 2048;
var points = mapBox.querySelectorAll('.eqoa-map-point');
var bounds = [[0, 0], [height, width]];
var layersByType = {};
var markersByType = {};
var map;
if (!imageUrl) return;
mapBox.style.aspectRatio = width + ' / ' + height;
mapBox.style.height = 'auto';
mapBox.classList.add('eqoa-map-ready');
map = L.map(mapBox, {
crs: L.CRS.Simple,
minZoom: -2,
maxZoom: 3,
zoomSnap: 0.25,
attributionControl: false,
maxBounds: bounds,
maxBoundsViscosity: 1.0
});
mapBox._leaflet_map_instance = map;
map.on('click', function (event) {
console.log(
'EQOA map coordinate:',
'data-x="' + Math.round(event.latlng.lng) + '"',
'data-y="' + Math.round(event.latlng.lat) + '"'
);
});
var imageLayer = L.imageOverlay(imageUrl, bounds).addTo(map);
function getMapCenter() {
return [height / 2, width / 2];
}
function fitEqoaMapToBox() {
map.invalidateSize();
var fitZoom = map.getBoundsZoom(bounds, false);
map.setMinZoom(fitZoom);
map.setMaxZoom(fitZoom + 3);
map.setMaxBounds(bounds);
map.fitBounds(bounds, {
animate: false,
padding: [0, 0]
});
map.dragging.disable();
}
function updateMapDragging() {
var minZoom = map.getMinZoom();
if (map.getZoom() <= minZoom + 0.001) {
map.fitBounds(bounds, {
animate: false,
padding: [0, 0]
});
map.dragging.disable();
} else {
map.dragging.enable();
}
}
imageLayer.on('load', fitEqoaMapToBox);
setTimeout(fitEqoaMapToBox, 50);
setTimeout(fitEqoaMapToBox, 250);
setTimeout(fitEqoaMapToBox, 750);
map.on('zoomend', updateMapDragging);
window.addEventListener('resize', function () {
setTimeout(fitEqoaMapToBox, 100);
});
points.forEach(function (point) {
var type = point.getAttribute('data-type') || 'Marker';
var namesRaw = point.getAttribute('data-names') || point.getAttribute('data-name') || 'Unknown';
var pagesRaw = point.getAttribute('data-pages') || point.getAttribute('data-page') || namesRaw;
var names = namesRaw.split(',').map(function (name) {
return name.trim();
}).filter(Boolean);
var pages = pagesRaw.split(',').map(function (page) {
return page.trim();
}).filter(Boolean);
var name = names.join(' / ');
var page = pages[0] || names[0] || 'Unknown';
var level = point.getAttribute('data-level') || '';
var x = parseFloat(point.getAttribute('data-x'));
var y = parseFloat(point.getAttribute('data-y'));
var marker;
var label;
if (isNaN(x) || isNaN(y)) return;
if (!layersByType[type]) {
layersByType[type] = L.layerGroup().addTo(map);
markersByType[type] = [];
}
label = '<b>' + name + '</b>';
if (level) label += '<br>Level: ' + level;
label += '<br>Type: ' + type;
marker = L.circleMarker([y, x], {
radius: 4,
color: getEqoaMapMarkerOutlineColor(type),
fillColor: getEqoaMapMarkerColor(type),
fillOpacity: 0.95,
weight: 2,
className: 'eqoa-map-dot'
});
var typeLower = String(type || '').toLowerCase();
var isCamp = typeLower === 'camp' || typeLower === 'camps';
var hasMultiplePages = names.length > 1;
if (hasMultiplePages) {
label = '<b>Shared Spawn</b>';
if (level) label += '<br>Level: ' + level;
label += '<br>Type: ' + type;
label += '<br>';
names.forEach(function (npcName, index) {
var npcPage = pages[index] || npcName;
var href = window.mw && mw.util && mw.util.getUrl
? mw.util.getUrl(npcPage)
: '/index.php/' + encodeURIComponent(npcPage.replace(/ /g, '_'));
label += '<br><a href="' + href + '">' + npcName + '</a>';
});
marker.bindPopup(label, {
closeButton: true,
autoClose: false,
closeOnClick: false,
className: 'eqoa-map-popup'
});
var sharedPopupPinned = false;
var sharedPopupCloseTimer;
marker.on('mouseover', function () {
clearTimeout(sharedPopupCloseTimer);
marker.openPopup();
});
marker.on('mouseout', function () {
clearTimeout(sharedPopupCloseTimer);
sharedPopupCloseTimer = setTimeout(function () {
if (!sharedPopupPinned) {
marker.closePopup();
}
}, 350);
});
marker.on('click', function () {
sharedPopupPinned = true;
marker.openPopup();
});
marker.on('popupclose', function () {
sharedPopupPinned = false;
});
} else if (isCamp) {
marker.bindTooltip(label, {
direction: 'top',
sticky: true
});
} else {
marker.bindTooltip(label + '<br><span style="color:#ffd45a;">Click to open page</span>', {
direction: 'top',
sticky: true
});
marker.on('click', function () {
if (window.mw && mw.util && mw.util.getUrl) {
window.location.href = mw.util.getUrl(page);
} else {
window.location.href = '/index.php/' + encodeURIComponent(page.replace(/ /g, '_'));
}
});
}
/*
marker.addTo(layersByType[type]);
*/
markersByType[type].push({
marker: marker,
name: name,
type: type
}); });
var filterControl = L.control({
position: 'topright'
});
filterControl.onAdd = function () {
var wrapper = L.DomUtil.create('div', 'eqoa-map-filter-control');
var button = document.createElement('button');
var panel = document.createElement('div');
button.type = 'button';
button.className = 'eqoa-map-filter-icon';
button.setAttribute('aria-label', 'Map filters');
button.innerHTML =
'<span class="eqoa-map-layer-icon">' +
'<span></span><span></span><span></span>' +
'</span>';
panel.className = 'eqoa-map-filter-panel';
L.DomEvent.disableClickPropagation(wrapper);
L.DomEvent.disableScrollPropagation(wrapper);
Object.keys(markersByType).sort().forEach(function (type) {
var group = document.createElement('div');
group.className = 'eqoa-map-filter-group';
var header = document.createElement('div');
header.className = 'eqoa-map-filter-header';
var expand = document.createElement('button');
expand.type = 'button';
expand.className = 'eqoa-map-filter-expand';
expand.textContent = '+';
var typeCheck = document.createElement('input');
typeCheck.type = 'checkbox';
typeCheck.checked = false;
typeCheck.className = 'eqoa-map-filter-type-check';
var label = document.createElement('span');
label.className = 'eqoa-map-filter-type-label';
label.textContent = type;
var list = document.createElement('div');
list.className = 'eqoa-map-filter-list';
list.style.display = 'none';
header.appendChild(expand);
header.appendChild(typeCheck);
header.appendChild(label);
group.appendChild(header);
group.appendChild(list);
markersByType[type].forEach(function (entry) {
var row = document.createElement('label');
row.className = 'eqoa-map-filter-row';
var check = document.createElement('input');
check.type = 'checkbox';
check.checked = false;
entry.check = check;
var nameText = document.createElement('span');
nameText.textContent = entry.name;
row.appendChild(check);
row.appendChild(nameText);
list.appendChild(row);
check.addEventListener('change', function () {
refreshTypeMarkers();
updateTypeCheck();
});
});
function refreshTypeMarkers() {
layersByType[type].clearLayers();
markersByType[type].forEach(function (entry) {
if (entry.check && entry.check.checked) {
entry.marker.addTo(layersByType[type]);
}
});
}
function updateTypeCheck() {
var checks = list.querySelectorAll('input[type="checkbox"]');
var checked = list.querySelectorAll('input[type="checkbox"]:checked');
typeCheck.checked = checked.length > 0;
typeCheck.indeterminate = checked.length > 0 && checked.length < checks.length;
}
expand.addEventListener('click', function (event) {
event.preventDefault();
event.stopPropagation();
var isOpen = list.style.display !== 'none';
list.style.display = isOpen ? 'none' : 'block';
expand.textContent = isOpen ? '+' : '-';
});
typeCheck.addEventListener('change', function () {
var checks = list.querySelectorAll('input[type="checkbox"]');
Array.prototype.forEach.call(checks, function (check) {
check.checked = typeCheck.checked;
});
typeCheck.indeterminate = false;
refreshTypeMarkers();
});
panel.appendChild(group);
});
var closeTimer;
function openFilterPanel() {
clearTimeout(closeTimer);
wrapper.classList.add('eqoa-map-filter-open');
}
function closeFilterPanelSoon() {
clearTimeout(closeTimer);
closeTimer = setTimeout(function () {
wrapper.classList.remove('eqoa-map-filter-open');
}, 500);
}
button.addEventListener('mouseenter', openFilterPanel);
panel.addEventListener('mouseenter', openFilterPanel);
wrapper.addEventListener('mouseleave', closeFilterPanelSoon);
panel.addEventListener('mouseleave', closeFilterPanelSoon);
button.addEventListener('click', function (event) {
event.preventDefault();
event.stopPropagation();
wrapper.classList.toggle('eqoa-map-filter-open');
});
wrapper.appendChild(button);
wrapper.appendChild(panel);
return wrapper;
};
filterControl.addTo(map);
});
}
function getEqoaMapMarkerColor(type) {
type = String(type || '').toLowerCase();
if (type === 'npc') return '#3fcf5f';
if (type === 'quest') return '#ffd84d';
if (type === 'enemy') return '#e85b5b';
if (type === 'named') return '#b51f1f';
if (type === 'camps') return '#22d3ee';
if (type === 'camp') return '#22d3ee';
if (type === 'charm pet') return '#d946ef';
if (type === 'charm pets') return '#d946ef';
if (type === 'charmpet') return '#d946ef';
if (type === 'charmpets') return '#d946ef';
return '#f2ab31';
}
function getEqoaMapMarkerOutlineColor(type) {
type = String(type || '').toLowerCase();
if (type === 'npc') return '#0d4f24';
if (type === 'quest') return '#7a5100';
if (type === 'enemy') return '#5c1010';
if (type === 'named') return '#3a0505';
if (type === 'camps') return '#0e7490';
if (type === 'camp') return '#0e7490';
if (type === 'charm pet') return '#86198f';
if (type === 'charm pets') return '#86198f';
if (type === 'charmpet') return '#86198f';
if (type === 'charmpets') return '#86198f';
return '#3a2508';
}
mw.hook('wikipage.content').add(initEqoaInteractiveMaps);
document.addEventListener('DOMContentLoaded', initEqoaInteractiveMaps);
setTimeout(initEqoaInteractiveMaps, 500);
setTimeout(initEqoaInteractiveMaps, 1500);
document.addEventListener('DOMContentLoaded', eqoaBuildSidebarAccountButtons);
mw.hook('wikipage.content').add(eqoaBuildSidebarAccountButtons);
setTimeout(eqoaBuildSidebarAccountButtons, 250);
setTimeout(eqoaBuildSidebarAccountButtons, 1000);
setTimeout(eqoaBuildSidebarAccountButtons, 2000);
/* =========================================================
FLAGGEDREVS ACCEPT BUTTON FIX
Fixes broken review.js Accept revision button for DevRenodor.
Does not grant permissions. It only uses the normal review API.
========================================================= */
function eqoaFixFlaggedRevsAcceptButton() {
if (!window.mw || !mw.Api || mw.config.get('wgUserName') !== 'DevRenodor') {
return;
}
var acceptButton = document.getElementById('mw-fr-submit-accept');
if (!acceptButton || acceptButton.getAttribute('data-eqoa-fixed') === '1') {
return;
}
acceptButton.setAttribute('data-eqoa-fixed', '1');
acceptButton.addEventListener('click', function (event) {
var api;
var form;
var oldidInput;
var commentInput;
var revid;
var comment;
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation();
form = acceptButton.closest('form');
oldidInput = document.getElementById('mw-fr-input-oldid');
commentInput = document.getElementById('mw-fr-commentbox');
revid = oldidInput && oldidInput.value
? oldidInput.value
: mw.config.get('wgRevisionId');
comment = commentInput && commentInput.value
? commentInput.value
: 'Approved by DevRenodor';
if (!revid) {
alert('Could not find revision ID to approve.');
return;
}
acceptButton.disabled = true;
acceptButton.value = 'Approving...';
api = new mw.Api();
api.get({
action: 'query',
meta: 'tokens',
type: 'csrf',
format: 'json'
}).then(function (tokenData) {
return api.post({
action: 'review',
revid: revid,
token: tokenData.query.tokens.csrftoken,
flag_accuracy: 1,
comment: comment,
format: 'json'
});
}).then(function () {
acceptButton.value = 'Approved';
window.location.reload();
}).catch(function (error) {
console.error('EQOA FlaggedRevs approve failed:', error);
acceptButton.disabled = false;
acceptButton.value = 'Accept revision';
alert('Review failed. Check console for EQOA FlaggedRevs approve failed.');
});
}, true);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', eqoaFixFlaggedRevsAcceptButton);
} else {
eqoaFixFlaggedRevsAcceptButton();
}
mw.hook('wikipage.content').add(eqoaFixFlaggedRevsAcceptButton);
setTimeout(eqoaFixFlaggedRevsAcceptButton, 250);
setTimeout(eqoaFixFlaggedRevsAcceptButton, 1000);
setTimeout(eqoaFixFlaggedRevsAcceptButton, 2000);
/* =========================================================
EQOA ZONE RELATED TABS
Turns zone page sections into Wowhead-style tabs.
No Screenshots tab.
========================================================= */
function initEqoaZoneTabs() {
var tabSections = [
{ id: 'quests', label: 'Quests' },
{ id: 'npcs', label: 'NPCs Found Here' },
{ id: 'rare', label: 'Rare Monsters' },
{ id: 'regular', label: 'Regular Monsters' },
{ id: 'items', label: 'Items Found Here' }
];
var headings = [];
function getHeadingById(id) {
var el = document.getElementById(id);
if (!el) {
return null;
}
if (/^h2$/i.test(el.tagName)) {
return el;
}
return el.closest('h2');
}
tabSections.forEach(function (section) {
var heading = getHeadingById(section.id);
if (heading) {
headings.push({
id: section.id,
label: section.label,
heading: heading
});
}
});
if (!headings.length) {
return;
}
if (document.querySelector('.eqoa-zone-tabs')) {
return;
}
var wrapper = document.createElement('div');
wrapper.className = 'eqoa-zone-tabs';
var title = document.createElement('div');
title.className = 'eqoa-zone-tabs-title';
title.textContent = 'Related';
var nav = document.createElement('div');
nav.className = 'eqoa-zone-tabs-nav';
var body = document.createElement('div');
body.className = 'eqoa-zone-tabs-body';
wrapper.appendChild(title);
wrapper.appendChild(nav);
wrapper.appendChild(body);
headings[0].heading.parentNode.insertBefore(wrapper, headings[0].heading);
headings.forEach(function (section, index) {
var button = document.createElement('button');
var panel = document.createElement('div');
var current = section.heading.nextSibling;
var next;
button.type = 'button';
button.className = 'eqoa-zone-tab-button';
button.textContent = section.label;
button.setAttribute('data-tab', section.id);
panel.className = 'eqoa-zone-tab-panel';
panel.setAttribute('data-tab-panel', section.id);
if (index === 0) {
button.classList.add('is-active');
panel.classList.add('is-active');
}
section.heading.parentNode.removeChild(section.heading);
while (current) {
next = current.nextSibling;
if (
current.nodeType === 1 &&
/^h2$/i.test(current.tagName) &&
headings.some(function (other) {
return other.heading === current;
})
) {
break;
}
panel.appendChild(current);
current = next;
}
function activateTab() {
wrapper.querySelectorAll('.eqoa-zone-tab-button').forEach(function (btn) {
btn.classList.remove('is-active');
});
wrapper.querySelectorAll('.eqoa-zone-tab-panel').forEach(function (tabPanel) {
tabPanel.classList.remove('is-active');
});
button.classList.add('is-active');
panel.classList.add('is-active');
wrapper.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
button.addEventListener('click', function () {
activateTab();
});
document.querySelectorAll('a[href="#' + section.id + '"]').forEach(function (link) {
link.addEventListener('click', function (event) {
event.preventDefault();
activateTab();
if (window.history && window.history.pushState) {
window.history.pushState(null, '', '#' + section.id);
}
});
});
panel.id = section.id;
nav.appendChild(button);
body.appendChild(panel);
});
}
mw.hook('wikipage.content').add(initEqoaZoneTabs);
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initEqoaZoneTabs);
} else {
initEqoaZoneTabs();
if (window.location.hash) {
var hashId = window.location.hash.replace('#', '');
var hashButton = wrapper.querySelector('.eqoa-zone-tab-button[data-tab="' + hashId + '"]');
if (hashButton) {
hashButton.click();
}
}
}
setTimeout(initEqoaZoneTabs, 500);
setTimeout(initEqoaZoneTabs, 1500);
/* =========================================================
EQOA CLASS PAGE TABS
Durable delegated handler for class tabs + URL hashes
========================================================= */
function eqoaActivateClassTab(tabs, tabName, updateUrl) {
var button;
var panel;
if (!tabs || !tabName) return;
button = tabs.querySelector('.eqoa-class-tab-button[data-class-tab="' + tabName + '"]');
panel = tabs.querySelector('.eqoa-class-tab-panel[data-class-tab-panel="' + tabName + '"]');
if (!button || !panel) return;
tabs.querySelectorAll('.eqoa-class-tab-button').forEach(function (btn) {
btn.classList.remove('is-active');
});
tabs.querySelectorAll('.eqoa-class-tab-panel').forEach(function (tabPanel) {
tabPanel.classList.remove('is-active');
});
button.classList.add('is-active');
panel.classList.add('is-active');
if (updateUrl && window.history && window.history.pushState) {
window.history.pushState(null, '', '#' + tabName);
}
}
function initEqoaClassTabs() {
document.querySelectorAll('.eqoa-class-tabs').forEach(function (tabs) {
var initialTab;
if (tabs.getAttribute('data-eqoa-class-tabs-ready') === '1') {
return;
}
tabs.setAttribute('data-eqoa-class-tabs-ready', '1');
initialTab = window.location.hash
? window.location.hash.replace('#', '')
: 'races';
if (!tabs.querySelector('.eqoa-class-tab-button[data-class-tab="' + initialTab + '"]')) {
initialTab = 'races';
}
eqoaActivateClassTab(tabs, initialTab, false);
});
}
document.addEventListener('click', function (event) {
var button = event.target.closest('.eqoa-class-tab-button');
var tabs;
if (!button) return;
tabs = button.closest('.eqoa-class-tabs');
if (!tabs) return;
event.preventDefault();
event.stopPropagation();
eqoaActivateClassTab(tabs, button.getAttribute('data-class-tab'), true);
});
document.addEventListener('click', function (event) {
var link = event.target.closest('a[href^="#"]');
var tabs;
var tabName;
if (!link) return;
tabName = link.getAttribute('href').replace('#', '');
tabs = document.querySelector('.eqoa-class-tabs');
if (!tabs) return;
if (!tabs.querySelector('.eqoa-class-tab-button[data-class-tab="' + tabName + '"]')) {
return;
}
event.preventDefault();
eqoaActivateClassTab(tabs, tabName, true);
tabs.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
});
window.addEventListener('hashchange', function () {
var tabs = document.querySelector('.eqoa-class-tabs');
var tabName = window.location.hash.replace('#', '');
if (!tabs || !tabName) return;
eqoaActivateClassTab(tabs, tabName, false);
});
mw.hook('wikipage.content').add(initEqoaClassTabs);
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initEqoaClassTabs);
} else {
initEqoaClassTabs();
}
setTimeout(initEqoaClassTabs, 250);
setTimeout(initEqoaClassTabs, 1000);
setTimeout(initEqoaClassTabs, 2000);
mw.hook('wikipage.content').add(initEqoaClassTabs);
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initEqoaClassTabs);
} else {
initEqoaClassTabs();
}
setTimeout(initEqoaClassTabs, 250);
setTimeout(initEqoaClassTabs, 1000);
setTimeout(initEqoaClassTabs, 2000);
/* =========================================================
EQOA CLASS ITEM LISTS
Loads class item tabs from Category:MAG Items, etc.
========================================================= */
function initClassItemLists() {
if (!window.mw || !mw.Api) return;
document.querySelectorAll('.class-item-list').forEach(function (box) {
var classCode = (box.getAttribute('data-class') || '').trim().toUpperCase();
var loading = box.querySelector('.class-item-list-loading');
var table = box.querySelector('.class-item-table');
if (!classCode || box.classList.contains('class-item-list-ready')) return;
box.classList.add('class-item-list-ready');
loadClassCategoryItems(classCode).then(function (items) {
renderClassItemList(box, table, loading, items);
}).catch(function (error) {
console.error('Class item list failed:', error);
if (loading) loading.textContent = 'Failed to load class items.';
});
});
}
function loadClassCategoryItems(classCode) {
var api = new mw.Api();
var category = 'Category:' + classCode + ' Items';
var pages = [];
function getBatch(cmcontinue) {
var request = {
action: 'query',
list: 'categorymembers',
cmtitle: category,
cmnamespace: 0,
cmlimit: 'max',
format: 'json'
};
if (cmcontinue) {
request.cmcontinue = cmcontinue;
}
return api.get(request).then(function (data) {
pages = pages.concat(data.query.categorymembers || []);
if (data.continue && data.continue.cmcontinue) {
return getBatch(data.continue.cmcontinue);
}
return pages;
});
}
return getBatch().then(function (categoryPages) {
return loadClassItemPageData(categoryPages.map(function (page) {
return page.title;
}));
});
}
function loadClassItemPageData(titles) {
var api = new mw.Api();
var chunks = [];
var i;
for (i = 0; i < titles.length; i += 50) {
chunks.push(titles.slice(i, i + 50));
}
return Promise.all(chunks.map(function (chunk) {
return api.get({
action: 'query',
prop: 'revisions',
titles: chunk.join('|'),
rvprop: 'content',
rvslots: 'main',
formatversion: '2',
format: 'json'
});
})).then(function (responses) {
var items = [];
responses.forEach(function (data) {
(data.query.pages || []).forEach(function (page) {
var text = '';
if (page.revisions && page.revisions[0]) {
if (page.revisions[0].slots && page.revisions[0].slots.main) {
text = page.revisions[0].slots.main.content || '';
} else {
text = page.revisions[0].content || '';
}
}
items.push(parseClassItemPage(page.title, text));
});
});
items.sort(function (a, b) {
if (a.level !== b.level) return a.level - b.level;
if (a.slot !== b.slot) return a.slot.localeCompare(b.slot);
return a.name.localeCompare(b.name);
});
return items;
});
}
function parseClassItemPage(title, text) {
function field(name) {
var match = text.match(new RegExp('\\|' + name + '\\s*=\\s*([^\\n]*)', 'i'));
return match ? match[1].trim() : '';
}
function numberField(name) {
var value = field(name);
var number = parseFloat(value);
return isNaN(number) ? 0 : number;
}
var name = field('name') || title;
var rawSlot = field('slot') || '';
var slot = normalizeClassItemSlot(rawSlot);
var level = parseInt(field('lvl'), 10) || 0;
var droppedby = cleanClassItemSource(field('droppedby'));
var zone = cleanClassItemSource(field('zone'));
var quests = cleanClassItemSource(field('quests'));
var notes = cleanClassItemSource(field('notes'));
return {
name: name,
rarity: field('rarity'),
slot: slot,
level: level,
race: field('race') || 'ALL',
className: field('class') || '',
notrade: field('notrade'),
lore: field('lore'),
dmg: numberField('dmg'),
ac: numberField('ac'),
str: numberField('str'),
sta: numberField('sta'),
agi: numberField('agi'),
dex: numberField('dex'),
wis: numberField('wis'),
int: numberField('int'),
cha: numberField('cha'),
hp: numberField('hp'),
hot: numberField('hot'),
pow: numberField('pow'),
pot: numberField('pot'),
pr: numberField('pr'),
dr: numberField('dr'),
fr: numberField('fr'),
cr: numberField('cr'),
lr: numberField('lr'),
ar: numberField('ar'),
dur: numberField('dur'),
gearhp: numberField('gearhp'),
effect: field('effect'),
proc: field('proc'),
desc: field('desc'),
droppedby: droppedby,
zone: zone,
quests: quests,
notes: notes,
type: getClassItemType(name, slot, droppedby, zone, quests, notes),
source: getClassItemSource(droppedby, zone, quests, notes)
};
}
function normalizeClassItemSlot(slot) {
slot = String(slot || '').trim();
if (!slot) return '';
if (/^\d+$/.test(slot)) return '';
if (slot === 'Helm') return 'Head';
if (slot === 'Helmet') return 'Head';
if (slot === 'Necklace') return 'Neck';
if (slot === 'Earring') return 'Ear';
if (slot === 'Leggings') return 'Legs';
if (slot === 'Bow') return 'Range';
if (slot === 'Thrown') return 'Range';
return slot;
}
function cleanClassItemSource(value) {
value = String(value || '').trim();
if (!value) return '';
if (value.toLowerCase() === 'unknown') return '';
if (value.toLowerCase() === 'none') return '';
if (value.toLowerCase() === 'no screenshots uploaded.') return '';
if (value.toLowerCase() === 'no additional lore available.') return '';
return value;
}
function getClassItemSource(droppedby, zone, quests, notes) {
var parts = [];
if (droppedby) parts.push(droppedby);
if (zone) parts.push(zone);
if (quests && quests !== 'None') {
parts.push('Quest: ' + quests);
}
if (!parts.length && notes && /quest reward/i.test(notes)) {
parts.push('Quest Reward');
}
return parts.join('<br>');
}
function getClassItemType(name, slot, droppedby, zone, quests, notes) {
var text = [
name,
slot,
droppedby,
zone,
quests,
notes
].join(' ').toLowerCase();
if (text.indexOf('recipe') !== -1) return 'Recipe';
if (text.indexOf('quest') !== -1 || text.indexOf('reward') !== -1) return 'Quest';
if (text.indexOf('clicky') !== -1 || text.indexOf('clickies') !== -1 || text.indexOf('effect:') !== -1) return 'Clickies';
if (slot) return slot;
return 'Miscellaneous';
}
function renderClassItemList(box, table, loading, items) {
var tbody;
var controls;
var searchInput;
var filterBox;
var activeFilter = 'All';
if (!table) return;
tbody = table.querySelector('tbody') || table;
controls = box.querySelector('.class-item-controls');
var searchHolder = box.querySelector('.class-item-search-holder');
var searchHolder = box.querySelector('.class-item-search-holder');
searchInput = box.querySelector('.class-item-search');
if (!searchInput && searchHolder) {
searchInput = document.createElement('input');
searchInput.className = 'class-item-search';
searchInput.type = 'search';
searchInput.placeholder = 'Search items';
searchHolder.appendChild(searchInput);
}
filterBox = box.querySelector('.class-item-filter-buttons');
if (loading) {
loading.style.display = 'none';
}
if (controls) {
controls.style.display = '';
}
table.style.display = '';
buildClassItemFilters(filterBox, items, activeFilter, function (filterName) {
activeFilter = filterName;
drawClassItemRows(tbody, items, activeFilter, searchInput ? searchInput.value : '');
});
if (searchInput) {
searchInput.addEventListener('input', function () {
drawClassItemRows(tbody, items, activeFilter, searchInput.value);
});
}
drawClassItemRows(tbody, items, activeFilter, '');
}
function buildClassItemFilters(filterBox, items, activeFilter, onFilterClick) {
var orderedFilters = [
'All',
'Head',
'Chest',
'Robe',
'Hands',
'Legs',
'Feet',
'Forearm',
'Bracelet',
'Waist',
'Neck',
'Ear',
'Ring',
'Primary',
'Secondary',
'Shield',
'Held',
'Range',
'Recipe',
'Quest',
'Clickies',
'Miscellaneous'
];
var counts = {};
if (!filterBox) return;
items.forEach(function (item) {
counts.All = (counts.All || 0) + 1;
counts[item.type] = (counts[item.type] || 0) + 1;
});
filterBox.innerHTML = '';
orderedFilters.forEach(function (filterName) {
var button;
if (filterName !== 'All' && !counts[filterName]) return;
button = document.createElement('button');
button.type = 'button';
button.className = 'class-item-filter-button';
if (filterName === activeFilter) {
button.className += ' is-active';
}
button.textContent = filterName + ' (' + (counts[filterName] || 0) + ')';
button.addEventListener('click', function () {
filterBox.querySelectorAll('.class-item-filter-button').forEach(function (btn) {
btn.classList.remove('is-active');
});
button.classList.add('is-active');
onFilterClick(filterName);
});
filterBox.appendChild(button);
});
}
function drawClassItemRows(tbody, items, activeFilter, searchText) {
var filtered;
var query = String(searchText || '').toLowerCase();
tbody.innerHTML = '';
filtered = items.filter(function (item) {
var haystack;
if (activeFilter && activeFilter !== 'All' && item.type !== activeFilter) {
return false;
}
if (!query) return true;
haystack = [
item.name,
item.slot,
item.level,
item.source,
item.type
].join(' ').toLowerCase();
return haystack.indexOf(query) !== -1;
});
filtered.forEach(function (item) {
var row = document.createElement('tr');
row.innerHTML =
'<td><a class="class-item-tooltip-link class-item-rarity-' + normalizeClassItemRarity(item.rarity) + '" data-class-item-name="' + encodeURIComponent(item.name) + '" href="' + mw.util.getUrl(item.name) + '">' + escapeClassItemHtml(item.name) + '</a></td>' +
'<td>' + (item.level || '') + '</td>' +
'<td>' + escapeClassItemHtml(item.slot || '') + '</td>' +
'<td>' + item.source + '</td>';
tbody.appendChild(row);
bindClassItemTooltip(row, item);
});
if (!filtered.length) {
var emptyRow = document.createElement('tr');
emptyRow.innerHTML = '<td colspan="4">No class items found.</td>';
tbody.appendChild(emptyRow);
}
}
function bindClassItemTooltip(row, item) {
var link = row.querySelector('.class-item-tooltip-link');
if (!link || !item) return;
link.addEventListener('mouseenter', function (event) {
showClassItemTooltip(item, event);
});
link.addEventListener('mousemove', moveClassItemTooltip);
link.addEventListener('mouseleave', function () {
hideClassItemTooltip();
});
}
function getClassItemTooltipBox() {
var tooltip = document.getElementById('class-item-tooltip');
if (!tooltip) {
tooltip = document.createElement('div');
tooltip.id = 'class-item-tooltip';
tooltip.className = 'class-item-tooltip';
tooltip.style.display = 'none';
document.body.appendChild(tooltip);
}
return tooltip;
}
function showClassItemTooltip(item, event) {
var tooltip = getClassItemTooltipBox();
var rarity = normalizeClassItemRarity(item.rarity);
var html = '';
function statLine(label, value) {
if (!value || parseFloat(value) === 0) return '';
return '<div><span>' + label + ':</span> ' + escapeClassItemHtml(value) + '</div>';
}
tooltip.className = 'class-item-tooltip class-item-tooltip-rarity-' + rarity;
html += '<div class="class-item-tooltip-title class-item-rarity-' + rarity + '">' +
escapeClassItemHtml(item.name) +
'</div>';
if (item.notrade || item.lore) {
html += '<div class="class-item-tooltip-flags">';
if (item.notrade) html += '<span>NO TRADE</span>';
if (item.lore) html += '<span>LORE</span>';
html += '</div>';
}
if (item.level) html += '<div><b>Level:</b> ' + item.level + '</div>';
if (item.slot) html += '<div><b>Slot:</b> ' + escapeClassItemHtml(item.slot) + '</div>';
if (item.dur) html += '<div><b>DUR:</b> ' + item.dur + '</div>';
if (item.gearhp) html += '<div><b>Gear HP:</b> ' + item.gearhp + '</div>';
if (item.race) html += '<div><b>Race:</b> ' + escapeClassItemHtml(item.race) + '</div>';
if (item.className) html += '<div><b>Class:</b> ' + escapeClassItemHtml(item.className) + '</div>';
html += '<div class="class-item-tooltip-section">Item Stats</div>';
html += '<div class="class-item-tooltip-stats">';
html += statLine('DMG', item.dmg);
html += statLine('AC', item.ac);
html += statLine('STR', item.str);
html += statLine('STA', item.sta);
html += statLine('AGI', item.agi);
html += statLine('DEX', item.dex);
html += statLine('WIS', item.wis);
html += statLine('INT', item.int);
html += statLine('CHA', item.cha);
html += statLine('HP', item.hp);
html += statLine('HoT', item.hot);
html += statLine('POW', item.pow);
html += statLine('PoT', item.pot);
html += statLine('PR', item.pr);
html += statLine('DR', item.dr);
html += statLine('FR', item.fr);
html += statLine('CR', item.cr);
html += statLine('LR', item.lr);
html += statLine('AR', item.ar);
html += '</div>';
if (item.effect) html += '<div><b>Effect:</b> ' + escapeClassItemHtml(item.effect) + '</div>';
if (item.proc) html += '<div><b>Proc:</b> ' + escapeClassItemHtml(item.proc) + '</div>';
if (item.source) {
html += '<div class="class-item-tooltip-section">Source</div>';
html += '<div>' + item.source + '</div>';
}
tooltip.innerHTML = html;
tooltip.style.display = 'block';
moveClassItemTooltip(event);
}
function normalizeClassItemRarity(rarity) {
return String(rarity || 'common')
.toLowerCase()
.replace(/\s+/g, '-');
}
function moveClassItemTooltip(event) {
var tooltip = getClassItemTooltipBox();
var left = event.pageX + 16;
var top = event.pageY + 16;
if (left + 360 > window.scrollX + window.innerWidth) {
left = event.pageX - 360;
}
tooltip.style.left = left + 'px';
tooltip.style.top = top + 'px';
}
function hideClassItemTooltip() {
var tooltip = document.getElementById('class-item-tooltip');
if (tooltip) {
tooltip.style.display = 'none';
}
}
function escapeClassItemHtml(value) {
return String(value == null ? '' : value)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
mw.hook('wikipage.content').add(initClassItemLists);
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initClassItemLists);
} else {
initClassItemLists();
}
setTimeout(initClassItemLists, 500);
setTimeout(initClassItemLists, 1500);