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 |
||
| (155 intermediate revisions by the same user not shown) | |||
| Line 1: | Line 1: | ||
mw.loader.load( | |||
mw.config.get('wgScript') + | |||
'?title=MediaWiki:AchievementTracker.js' + | |||
'&action=raw' + | |||
'&ctype=text/javascript' + | |||
'&v=20260716-1' | |||
); | |||
/* Any JavaScript here will be loaded for all users on every page load. */ | /* Any JavaScript here will be loaded for all users on every page load. */ | ||
| Line 224: | Line 232: | ||
var searchInput; | var searchInput; | ||
var classSelect; | var classSelect; | ||
var sortSelect; | |||
var sortButton; | |||
var clearButton; | var clearButton; | ||
if (!table) return; | if (!table) return; | ||
| Line 235: | Line 244: | ||
box.innerHTML = | box.innerHTML = | ||
'<div class="items-filter- | '<div class="items-filter-left">' + | ||
'< | '<input type="search" id="filter-item-search" placeholder="Search item">' + | ||
'</div>' + | '</div>' + | ||
'<div class="items-filter- | '<div class="items-filter-right">' + | ||
'< | '<select id="items-found-here-sort-select">' + | ||
'<option value="name">Sort: Name</option>' + | |||
'<option value="level">Sort: Level</option>' + | |||
'<option value="class">Sort: Class</option>' + | |||
'<option value="dmg">Sort: DMG</option>' + | |||
'<option value="ac">Sort: AC</option>' + | |||
'<option value="str">Sort: STR</option>' + | |||
'<option value="sta">Sort: STA</option>' + | |||
'<option value="agi">Sort: AGI</option>' + | |||
'<option value="wis">Sort: WIS</option>' + | |||
'<option value="dex">Sort: DEX</option>' + | |||
'<option value="cha">Sort: CHA</option>' + | |||
'<option value="int">Sort: INT</option>' + | |||
'<option value="hp">Sort: HP</option>' + | |||
'<option value="pow">Sort: POW</option>' + | |||
'<option value="pr">Sort: PR</option>' + | |||
'<option value="dr">Sort: DR</option>' + | |||
'<option value="fr">Sort: FR</option>' + | |||
'<option value="cr">Sort: CR</option>' + | |||
'<option value="lr">Sort: LR</option>' + | |||
'<option value="ar">Sort: AR</option>' + | |||
'<option value="dur">Sort: DUR</option>' + | |||
'</select>' + | |||
'<button id="items-found-here-sort-button" type="button">Sort</button>' + | |||
'<select id="filter-class-select">' + | '<select id="filter-class-select">' + | ||
'<option value="">All Classes</option>' + | '<option value="">All Classes</option>' + | ||
| Line 269: | Line 297: | ||
searchInput = document.getElementById('filter-item-search'); | searchInput = document.getElementById('filter-item-search'); | ||
classSelect = document.getElementById('filter-class-select'); | classSelect = document.getElementById('filter-class-select'); | ||
sortSelect = document.getElementById('items-found-here-sort-select'); | |||
sortButton = document.getElementById('items-found-here-sort-button'); | |||
clearButton = document.getElementById('clear-item-filters'); | clearButton = document.getElementById('clear-item-filters'); | ||
box.style.setProperty('display', 'grid', 'important'); | |||
box.style.setProperty('grid-template-columns', '1fr auto', 'important'); | |||
box.style.setProperty('align-items', 'center', 'important'); | |||
box.style.setProperty('gap', '12px', 'important'); | |||
box.querySelector('.items-filter-left').style.setProperty('display', 'flex', 'important'); | |||
box.querySelector('.items-filter-left').style.setProperty('justify-content', 'flex-start', 'important'); | |||
box.querySelector('.items-filter-right').style.setProperty('display', 'flex', 'important'); | |||
box.querySelector('.items-filter-right').style.setProperty('justify-content', 'flex-end', 'important'); | |||
box.querySelector('.items-filter-right').style.setProperty('gap', '8px', 'important'); | |||
function | [searchInput, classSelect, sortSelect, sortButton, clearButton].forEach(function (control) { | ||
if (!control) return; | |||
control.style.setProperty('height', '34px', 'important'); | |||
control.style.setProperty('box-sizing', 'border-box', 'important'); | |||
control.style.setProperty('margin', '0', 'important'); | |||
control.style.setProperty('vertical-align', 'middle', 'important'); | |||
}); | |||
searchInput.style.setProperty('width', '660px', 'important'); | |||
searchInput.style.setProperty('max-width', '100%', 'important'); | |||
sortSelect.style.setProperty('width', '128px', 'important'); | |||
classSelect.style.setProperty('width', '128px', 'important'); | |||
sortButton.style.setProperty('width', '58px', 'important'); | |||
clearButton.style.setProperty('width', '64px', 'important'); | |||
function getCellText(row, index) { | |||
return row.cells[index] ? row.cells[index].textContent.trim() : ''; | |||
} | |||
return | function getRowClassText(row) { | ||
return ( | |||
row.getAttribute('data-class') || | |||
getCellText(row, 2) || | |||
'' | |||
).toUpperCase(); | |||
} | } | ||
function classMatches(row, selectedClass) { | function classMatches(row, selectedClass) { | ||
var classText = | var classText = getRowClassText(row); | ||
if (!selectedClass) return true; | if (!selectedClass) return true; | ||
| Line 309: | Line 351: | ||
return classText.split(/[\s,\/]+/).indexOf(selectedClass) !== -1; | return classText.split(/[\s,\/]+/).indexOf(selectedClass) !== -1; | ||
} | } | ||
| Line 323: | Line 356: | ||
var query = String(searchInput.value || '').toLowerCase(); | var query = String(searchInput.value || '').toLowerCase(); | ||
var selectedClass = classSelect.value; | var selectedClass = classSelect.value; | ||
var | var rows = table.querySelectorAll('tr.items-found-here-row'); | ||
Array.prototype.forEach.call(rows, function (row) { | |||
var rowText = [ | |||
row.textContent, | |||
row.getAttribute('data-name'), | |||
row.getAttribute('data-level'), | |||
row.getAttribute('data-class'), | |||
row.getAttribute('data-slot'), | |||
row.getAttribute('data-rarity') | |||
].join(' ').toLowerCase(); | |||
show = true; | var show = true; | ||
if (query && rowText.indexOf(query) === -1) show = false; | if (query && rowText.indexOf(query) === -1) show = false; | ||
if (!classMatches(row, selectedClass)) show = false; | if (!classMatches(row, selectedClass)) show = false; | ||
row.style.display = show ? '' : 'none'; | row.style.display = show ? '' : 'none'; | ||
}); | |||
} | |||
function getSortValue(row, key) { | |||
var value = row.getAttribute('data-' + key) || ''; | |||
if (!value) { | |||
if (key === 'name') value = getCellText(row, 0); | |||
if (key === 'level') value = getCellText(row, 1); | |||
if (key === 'class') value = getCellText(row, 2); | |||
} | } | ||
return String(value || ''); | |||
} | } | ||
function sortTable() { | |||
var tbody = table.querySelector('tbody') || table; | |||
var rows = Array.prototype.slice.call(table.querySelectorAll('tr.items-found-here-row')); | |||
var key = sortSelect.value; | |||
} | |||
rows.sort(function (a, b) { | |||
var aValue = getSortValue(a, key); | |||
var bValue = getSortValue(b, key); | |||
var aNumber = parseFloat(aValue.replace(/[^\d.-]/g, '')); | |||
var bNumber = parseFloat(bValue.replace(/[^\d.-]/g, '')); | |||
if (!isNaN(aNumber) || !isNaN(bNumber)) { | |||
return (bNumber || 0) - (aNumber || 0); | |||
} | |||
return aValue.localeCompare(bValue); | |||
}); | }); | ||
searchInput.addEventListener('input', filterTable); | rows.forEach(function (row) { | ||
tbody.appendChild(row); | |||
}); | |||
filterTable(); | |||
} | |||
searchInput.addEventListener('input', filterTable); | |||
classSelect.addEventListener('change', filterTable); | classSelect.addEventListener('change', filterTable); | ||
sortButton.addEventListener('click', sortTable); | |||
clearButton.addEventListener('click', function () { | clearButton.addEventListener('click', function () { | ||
searchInput.value = ''; | searchInput.value = ''; | ||
classSelect.value = ''; | classSelect.value = ''; | ||
sortSelect.value = 'name'; | |||
filterTable(); | filterTable(); | ||
}); | }); | ||
| Line 371: | Line 427: | ||
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(); | |||
return | |||
} | } | ||
function initEqoaGeneratedItemLinks($content) { | |||
var | var root = $content && $content[0] ? $content[0] : document; | ||
var links = root.querySelectorAll( | |||
'#items-found-here-table .items-found-here-name a:not([data-npc-item-tooltip-bound="1"]):not([data-quest-reward-tooltip-bound="1"]), ' + | |||
'#items-found-here-table td:first-child a:not([data-npc-item-tooltip-bound="1"]):not([data-quest-reward-tooltip-bound="1"]), ' + | |||
'.zone-monster-table .zone-monster-drops a:not([data-npc-item-tooltip-bound="1"]):not([data-quest-reward-tooltip-bound="1"]), ' + | |||
'.zone-monster-table td:nth-child(3) a:not([data-npc-item-tooltip-bound="1"]):not([data-quest-reward-tooltip-bound="1"]), ' + | |||
'.eqoa-related-items a:not([data-npc-item-tooltip-bound="1"]):not([data-quest-reward-tooltip-bound="1"])' | |||
); | |||
var items = window.EQOA_GENERATED_ITEMS || []; | |||
var lookup = {}; | |||
if (!links.length) return; | |||
if (!items.length) { | |||
setTimeout(function () { | |||
initEqoaGeneratedItemLinks($content); | |||
} | }, 250); | ||
return; | |||
} | |||
return ''; | function normalizeName(value) { | ||
return String(value || '') | |||
.replace(/\u00a0/g, ' ') | |||
.replace(/[’`]/g, "'") | |||
.replace(/\s+/g, ' ') | |||
.trim() | |||
.toLowerCase(); | |||
} | } | ||
function | function looseName(value) { | ||
return normalizeName(value).replace(/[^a-z0-9]/g, ''); | |||
} | |||
return | function stripExtraText(value) { | ||
return String(value || '') | |||
.replace(/\(\d+%\)/g, '') | |||
.replace(/\([^)]*\)$/g, '') | |||
.replace(/\s+/g, ' ') | |||
.trim(); | |||
} | } | ||
function | function rarityRank(item) { | ||
var rarity = | var rarity = normalizeClassItemRarity(item && item.rarity); | ||
if (rarity === 'ultra-rare') return 4; | |||
if (rarity === 'rare') return 3; | |||
if (rarity === 'uncommon') return 2; | |||
return 1; | |||
} | |||
function storeItem(key, item) { | |||
var current; | |||
if ( | if (!key) return; | ||
current = lookup[key]; | |||
if ( | if (!current || rarityRank(item) > rarityRank(current)) { | ||
lookup[key] = item; | |||
} | } | ||
} | |||
function getHrefTitle(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 ( | if (href.indexOf('/index.php/') !== -1) { | ||
title = href.split('/index.php/')[1].split('?')[0]; | |||
return decodeURIComponent(title).replace(/_/g, ' ').trim(); | |||
} | } | ||
return | return ''; | ||
} | } | ||
function | function getCandidates(link) { | ||
var | var row = link.closest('tr'); | ||
var names = [ | |||
link.getAttribute('data-eqoa-item-name'), | |||
row && row.getAttribute('data-eqoa-item-name'), | |||
link.textContent, | |||
stripExtraText(link.textContent), | |||
getHrefTitle(link), | |||
stripExtraText(getHrefTitle(link)) | |||
]; | |||
return names.filter(function (name) { | |||
return name && String(name).trim(); | |||
}); | |||
} | |||
function findItem(link) { | |||
var candidates = getCandidates(link); | |||
var i; | |||
var name; | |||
var item; | |||
for (i = 0; i < candidates.length; i++) { | |||
name = candidates[i]; | |||
item = lookup[normalizeName(name)] || lookup[looseName(name)]; | |||
if (item) return item; | |||
} | } | ||
return null; | |||
} | } | ||
function | function normalizeGeneratedItem(item) { | ||
return { | |||
name: item.name || '', | |||
rarity: item.rarity || 'common', | |||
slot: item.slot || '', | |||
level: item.level || item.lvl || '', | |||
race: item.race || '', | |||
className: Array.isArray(item.class) ? item.class.join(', ') : (item.class || item.classes || item.className || ''), | |||
notrade: item.notrade || item.noTrade || item.trade === 'NO TRADE', | |||
lore: item.lore || '', | |||
dmg: item.dmg || item.damage || '', | |||
ac: item.ac || '', | |||
str: item.str || '', | |||
sta: item.sta || '', | |||
agi: item.agi || '', | |||
dex: item.dex || '', | |||
wis: item.wis || '', | |||
int: item.int || '', | |||
cha: item.cha || '', | |||
hp: item.hp || '', | |||
hot: item.hot || '', | |||
pow: item.pow || '', | |||
pot: item.pot || '', | |||
pr: item.pr || '', | |||
dr: item.dr || '', | |||
fr: item.fr || '', | |||
cr: item.cr || '', | |||
lr: item.lr || '', | |||
ar: item.ar || '', | |||
dur: item.dur || '', | |||
gearhp: item.gearhp || item.gearHp || item.gear_hp || '', | |||
effect: item.effect || '', | |||
proc: item.proc || '', | |||
source: item.source || '' | |||
}; | |||
} | |||
items.forEach(function (item) { | |||
if (!item || !item.name) return; | |||
storeItem(normalizeName(item.name), item); | |||
} | storeItem(looseName(item.name), item); | ||
}); | |||
function | Array.prototype.forEach.call(links, function (link) { | ||
var | var item = findItem(link); | ||
var tooltipItem; | |||
var rarity; | |||
if ( | if (!item) return; | ||
tooltipItem = normalizeGeneratedItem(item); | |||
rarity = normalizeClassItemRarity(tooltipItem.rarity); | |||
link.classList.remove( | |||
'class-item-rarity-common', | |||
'class-item-rarity-uncommon', | |||
'class-item-rarity-rare', | |||
'class-item-rarity-ultra-rare', | |||
'eqoa-related-tooltip-ready', | |||
'items-found-tooltip-ready' | |||
); | |||
link.classList.add('class-item-tooltip-link'); | |||
link.classList.add('class-item-rarity-' + rarity); | |||
link.classList.add('cb-rarity-' + rarity); | |||
applyClassItemRarityStyle(link, rarity); | |||
if (link._eqoaGeneratedTooltipBound) return; | |||
link._eqoaGeneratedTooltipBound = true; | |||
link.addEventListener('mouseenter', function (event) { | |||
showClassItemTooltip(tooltipItem, event); | |||
}); | |||
link.addEventListener('mousemove', moveClassItemTooltip); | |||
link.addEventListener('mouseleave', function () { | |||
hideClassItemTooltip(); | |||
}); | }); | ||
} | }); | ||
} | |||
function initItemsFoundHereRarityTooltips() { | |||
initEqoaGeneratedItemLinks(); | |||
} | } | ||
function initRelatedItemTooltips($content) { | |||
initEqoaGeneratedItemLinks($content); | |||
} | |||
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 | 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(); | |||
}); | }); | ||
} | })(); | ||
var | 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 | var wrapper = table.closest('.raid-encounter-table'); | ||
var | if (!wrapper) return; | ||
var existingMobile = wrapper.querySelector('.rare-encounter-mobile-layout'); | |||
if ( | 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 | |||
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 | function fixPersonalNotificationButtons() { | ||
var | 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 | 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' ); | |||
var | 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() { | |||
document. | var personal = document.getElementById( 'p-personal' ); | ||
if ( !personal ) { | |||
if ( | 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' ); | |||
if ( | [ 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 ( | 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.width = '100%'; | |||
mapBox.style.height = 'auto'; | |||
mapBox.classList.add('eqoa-map-ready'); | |||
function | 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); | |||
if ( ! | mapBox._leaflet_image_layer = imageLayer; | ||
function sizeEqoaMobileMapBox() { | |||
var parent; | |||
var boxWidth; | |||
var boxHeight; | |||
if (!window.matchMedia || !window.matchMedia('(max-width: 700px)').matches) { | |||
return; | return; | ||
} | } | ||
parent = mapBox.closest('.zone-interactive-map-content'); | |||
if (parent) { | |||
parent.style.setProperty('padding', '0', 'important'); | |||
parent.style.setProperty('height', 'auto', 'important'); | |||
parent.style.setProperty('min-height', '0', 'important'); | |||
parent.style.setProperty('background', 'transparent', 'important'); | |||
} | |||
mapBox.style.setProperty('display', 'block', 'important'); | |||
mapBox.style.setProperty('width', '100%', 'important'); | |||
mapBox.style.setProperty('max-width', '650px', 'important'); | |||
mapBox.style.setProperty('margin', '0 auto', 'important'); | |||
mapBox.style.setProperty('padding', '0', 'important'); | |||
mapBox.style.setProperty('aspect-ratio', width + ' / ' + height, 'important'); | |||
mapBox.style.setProperty('overflow', 'hidden', 'important'); | |||
mapBox.style.setProperty('background', 'transparent', 'important'); | |||
boxWidth = mapBox.clientWidth || mapBox.getBoundingClientRect().width; | |||
if (!boxWidth) { | |||
return; | |||
} | |||
boxHeight = Math.round(boxWidth * height / width); | |||
mapBox.style.setProperty('height', boxHeight + 'px', 'important'); | |||
mapBox.style.setProperty('min-height', boxHeight + 'px', 'important'); | |||
mapBox.style.setProperty('max-height', boxHeight + 'px', 'important'); | |||
} | } | ||
function repairEqoaMobileMapImage() { | |||
var imageElement; | |||
var northWest; | |||
var southEast; | |||
var imageWidth; | |||
var imageHeight; | |||
if (!window.matchMedia || !window.matchMedia('(max-width: 700px)').matches) { | |||
return; | |||
} | |||
imageLayer.setBounds(bounds); | |||
imageElement = imageLayer.getElement && imageLayer.getElement(); | |||
if (!imageElement) { | |||
return; | |||
} | |||
northWest = map.latLngToLayerPoint([height, 0]); | |||
southEast = map.latLngToLayerPoint([0, width]); | |||
imageWidth = Math.ceil(Math.abs(southEast.x - northWest.x)); | |||
imageHeight = Math.ceil(Math.abs(southEast.y - northWest.y)); | |||
imageElement.style.setProperty('display', 'block', 'important'); | |||
imageElement.style.setProperty('visibility', 'visible', 'important'); | |||
imageElement.style.setProperty('opacity', '1', 'important'); | |||
imageElement.style.setProperty('width', imageWidth + 'px', 'important'); | |||
imageElement.style.setProperty('height', imageHeight + 'px', 'important'); | |||
imageElement.style.setProperty('max-width', 'none', 'important'); | |||
imageElement.style.setProperty('max-height', 'none', 'important'); | |||
} | |||
function getMapCenter() { | |||
return [height / 2, width / 2]; | |||
} | |||
var | function fitEqoaMapToBox() { | ||
var fitZoom; | |||
var isMobileMap = | |||
window.matchMedia && | |||
window.matchMedia('(max-width: 700px)').matches; | |||
sizeEqoaMobileMapBox(); | |||
if (isMobileMap) { | |||
map.options.zoomSnap = 0; | |||
} | |||
map.invalidateSize(); | |||
fitZoom = map.getBoundsZoom(bounds, false); | |||
map.setMinZoom(fitZoom); | |||
map.setMaxZoom(fitZoom + 3); | |||
map.setMaxBounds(bounds); | |||
if ( | map.fitBounds(bounds, { | ||
animate: false, | |||
padding: [0, 0] | |||
}); | |||
repairEqoaMobileMapImage(); | |||
updateMapDragging(); | |||
} | |||
function updateMapDragging() { | |||
var minZoom = map.getMinZoom(); | |||
if (map.getZoom() <= minZoom + 0.001) { | |||
sizeEqoaMobileMapBox(); | |||
map.invalidateSize(); | |||
map.fitBounds(bounds, { | |||
animate: false, | |||
padding: [0, 0] | |||
}); | |||
repairEqoaMobileMapImage(); | |||
map.dragging.disable(); | |||
} else { | } else { | ||
repairEqoaMobileMapImage(); | |||
map.dragging.enable(); | |||
} | } | ||
} | } | ||
imageLayer.on('load', function () { | |||
sizeEqoaMobileMapBox(); | |||
fitEqoaMapToBox(); | |||
}); | |||
setTimeout(function () { | |||
fitEqoaMapToBox(); | |||
repairEqoaMobileMapImage(); | |||
}, 50); | |||
setTimeout(function () { | |||
fitEqoaMapToBox(); | |||
repairEqoaMobileMapImage(); | |||
}, 250); | |||
setTimeout(function () { | |||
fitEqoaMapToBox(); | |||
repairEqoaMobileMapImage(); | |||
}, 750); | |||
map.on('zoomend', updateMapDragging); | |||
window.addEventListener('resize', function () { | |||
setTimeout(function () { | |||
fitEqoaMapToBox(); | |||
repairEqoaMobileMapImage(); | |||
}, 100); | |||
setTimeout(function () { | |||
fitEqoaMapToBox(); | |||
repairEqoaMobileMapImage(); | |||
}, 300); | |||
}); | |||
window.addEventListener('orientationchange', function () { | |||
setTimeout(fitEqoaMapToBox, 250); | |||
setTimeout(fitEqoaMapToBox, 600); | |||
}); | |||
function getEqoaMapTooltipDirection(x, y) { | |||
var edgePadding = 120; | |||
if (y < edgePadding) return 'bottom'; | |||
if (y > height - edgePadding) return 'top'; | |||
if (x < edgePadding) return 'right'; | |||
if (x > width - edgePadding) return 'left'; | |||
return 'top'; | |||
} | |||
} | |||
function getEqoaMapTooltipOffset(direction) { | |||
if (direction === 'bottom') return [0, 18]; | |||
if (direction === 'top') return [0, -18]; | |||
if (direction === 'left') return [-18, 0]; | |||
if (direction === 'right') return [18, 0]; | |||
return [0, -18]; | |||
} | |||
} | |||
var | points.forEach(function (point) { | ||
var type = point.getAttribute('data-type') || 'Marker'; | |||
function | var namesRaw = point.getAttribute('data-names') || point.getAttribute('data-name') || 'Unknown'; | ||
return | var pagesRaw = point.getAttribute('data-pages') || point.getAttribute('data-page') || namesRaw; | ||
} | |||
var names = namesRaw.split(',').map(function (name) { | |||
return name.trim(); | |||
}).filter(Boolean); | |||
function | 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; | |||
tooltipDirection = getEqoaMapTooltipDirection(x, y); | |||
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 | var npcPage = pages[index] || npcName; | ||
var | 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, | |||
autoPan: true, | |||
keepInView: true, | |||
autoPanPaddingTopLeft: [24, 80], | |||
autoPanPaddingBottomRight: [24, 24], | |||
offset: tooltipDirection === 'bottom' ? [0, 22] : [0, -8], | |||
className: 'eqoa-map-popup' | |||
}); | |||
var | var sharedPopupPinned = false; | ||
var | 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 | marker.on('click', function () { | ||
sharedPopupPinned = true; | |||
marker.openPopup(); | |||
}); | }); | ||
marker.on('popupclose', function () { | |||
sharedPopupPinned = false; | |||
}); | |||
} else if (isCamp) { | |||
marker.bindTooltip(label, { | |||
direction: tooltipDirection, | |||
sticky: false, | |||
offset: getEqoaMapTooltipOffset(tooltipDirection), | |||
opacity: 1 | |||
}); | |||
} else { | |||
marker.bindTooltip(label + '<br><span style="color:#ffd45a;">Click to open page</span>', { | |||
direction: tooltipDirection, | |||
sticky: false, | |||
offset: getEqoaMapTooltipOffset(tooltipDirection), | |||
opacity: 1 | |||
}); | |||
if ( | 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' | |||
}); | }); | ||
var | filterControl.onAdd = function () { | ||
var | 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(); | 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(); | |||
button.addEventListener('click', function (event) { | |||
event.preventDefault(); | |||
event.stopPropagation(); | event.stopPropagation(); | ||
wrapper.classList.toggle('eqoa-map-filter-open'); | wrapper.classList.toggle('eqoa-map-filter-open'); | ||
| Line 1,941: | Line 1,945: | ||
var tabSections = [ | var tabSections = [ | ||
{ id: 'quests', label: 'Quests' }, | { id: 'quests', label: 'Quests' }, | ||
{ id: 'npcs', label: 'NPCs | { id: 'npcs', label: 'NPCs' }, | ||
{ id: ' | { id: 'items', label: 'Drops' }, | ||
{ id: 'achievements', label: 'Achievements' } | |||
{ id: ' | |||
]; | ]; | ||
var headings = []; | var headings = []; | ||
var activators = {}; | |||
var initialHash = window.location.hash ? window.location.hash.replace('#', '') : ''; | |||
function getHeadingById(id) { | function getHeadingById(id) { | ||
| Line 2,015: | Line 2,020: | ||
panel.className = 'eqoa-zone-tab-panel'; | panel.className = 'eqoa-zone-tab-panel'; | ||
panel.setAttribute('data-tab-panel', section.id); | panel.setAttribute('data-tab-panel', section.id); | ||
panel.id = section.id; | |||
if (index === 0) { | if (index === 0) { | ||
| Line 2,040: | Line 2,046: | ||
} | } | ||
function activateTab() { | function activateTab(pushHash) { | ||
wrapper.querySelectorAll('.eqoa-zone-tab-button').forEach(function (btn) { | wrapper.querySelectorAll('.eqoa-zone-tab-button').forEach(function (btn) { | ||
btn.classList.remove('is-active'); | btn.classList.remove('is-active'); | ||
| Line 2,051: | Line 2,057: | ||
button.classList.add('is-active'); | button.classList.add('is-active'); | ||
panel.classList.add('is-active'); | panel.classList.add('is-active'); | ||
if (pushHash && window.history && window.history.pushState) { | |||
window.history.pushState(null, '', '#' + section.id); | |||
} | |||
wrapper.scrollIntoView({ | wrapper.scrollIntoView({ | ||
| Line 2,057: | Line 2,067: | ||
}); | }); | ||
} | } | ||
activators[section.id] = activateTab; | |||
button.addEventListener('click', function () { | button.addEventListener('click', function () { | ||
activateTab(); | activateTab(true); | ||
}); | }); | ||
| Line 2,065: | Line 2,077: | ||
link.addEventListener('click', function (event) { | link.addEventListener('click', function (event) { | ||
event.preventDefault(); | event.preventDefault(); | ||
activateTab(); | activateTab(true); | ||
}); | }); | ||
}); | }); | ||
nav.appendChild(button); | nav.appendChild(button); | ||
body.appendChild(panel); | body.appendChild(panel); | ||
}); | }); | ||
if (initialHash && activators[initialHash]) { | |||
activators[initialHash](false); | |||
} | |||
} | } | ||
| Line 2,086: | Line 2,096: | ||
} else { | } else { | ||
initEqoaZoneTabs(); | initEqoaZoneTabs(); | ||
} | } | ||
setTimeout(initEqoaZoneTabs, 500); | setTimeout(initEqoaZoneTabs, 500); | ||
setTimeout(initEqoaZoneTabs, 1500); | setTimeout(initEqoaZoneTabs, 1500); | ||
| Line 2,679: | Line 2,680: | ||
tooltip.className = 'class-item-tooltip class-item-tooltip-rarity-' + rarity; | tooltip.className = 'class-item-tooltip class-item-tooltip-rarity-' + rarity; | ||
html += '<div class="class-item-tooltip-title class-item-rarity-' + rarity + '">' + | html += '<div class="class-item-tooltip-title class-item-rarity-' + rarity + ' cb-rarity-' + rarity + '" style="' + getClassItemRarityInlineStyle(rarity) + '">' + | ||
escapeClassItemHtml(item.name) + | escapeClassItemHtml(item.name) + | ||
'</div>'; | '</div>'; | ||
| Line 2,735: | Line 2,736: | ||
function normalizeClassItemRarity(rarity) { | function normalizeClassItemRarity(rarity) { | ||
rarity = String(rarity || 'common') | |||
.toLowerCase() | .toLowerCase() | ||
.replace(/\s+/g, '-'); | .replace(/\s+/g, '-') | ||
.trim(); | |||
if (rarity === 'ultrarare') return 'ultra-rare'; | |||
if (rarity === 'ultra-rare') return 'ultra-rare'; | |||
if (rarity === 'legendary') return 'legendary'; | |||
if (rarity === 'unique') return 'unique'; | |||
if (rarity === 'rare') return 'rare'; | |||
if (rarity === 'uncommon') return 'uncommon'; | |||
if (rarity === 'common') return 'common'; | |||
return 'common'; | |||
} | } | ||
if ( | function getClassItemRarityInlineStyle(rarity) { | ||
rarity = normalizeClassItemRarity(rarity); | |||
if (rarity === 'unique') { | |||
return 'color:#ffd700!important;text-shadow:0 0 8px rgba(255,215,0,0.85)!important;'; | |||
} | |||
if (rarity === 'legendary') { | |||
return 'color:#ff8000!important;text-shadow:0 0 8px rgba(255,128,0,0.85)!important;'; | |||
} | } | ||
if (rarity === 'ultra-rare') { | |||
return 'color:#ff33ff!important;text-shadow:0 0 8px rgba(255,51,255,0.85)!important;'; | |||
} | } | ||
if (rarity === 'rare') { | |||
return 'color:#1e90ff!important;text-shadow:0 0 8px rgba(30,144,255,0.85)!important;'; | |||
} | |||
if ( | if (rarity === 'uncommon') { | ||
return 'color:#00ff55!important;text-shadow:0 0 8px rgba(0,255,85,0.85)!important;'; | |||
} | } | ||
return 'color:#ffffff!important;text-shadow:0 0 6px rgba(255,255,255,0.75)!important;'; | |||
} | } | ||
function | function applyClassItemRarityStyle(element, rarity) { | ||
return | rarity = normalizeClassItemRarity(rarity); | ||
. | |||
. | if (!element) return; | ||
. | |||
. | if (rarity === 'unique') { | ||
. | element.style.setProperty('color', '#ffd700', 'important'); | ||
element.style.setProperty('text-shadow', '0 0 8px rgba(255,215,0,0.85)', 'important'); | |||
return; | |||
} | |||
if (rarity === 'legendary') { | |||
element.style.setProperty('color', '#ff8000', 'important'); | |||
element.style.setProperty('text-shadow', '0 0 8px rgba(255,128,0,0.85)', 'important'); | |||
return; | |||
} | |||
if (rarity === 'ultra-rare') { | |||
element.style.setProperty('color', '#ff33ff', 'important'); | |||
element.style.setProperty('text-shadow', '0 0 8px rgba(255,51,255,0.85)', 'important'); | |||
return; | |||
} | |||
if (rarity === 'rare') { | |||
element.style.setProperty('color', '#1e90ff', 'important'); | |||
element.style.setProperty('text-shadow', '0 0 8px rgba(30,144,255,0.85)', 'important'); | |||
return; | |||
} | |||
if (rarity === 'uncommon') { | |||
element.style.setProperty('color', '#00ff55', 'important'); | |||
element.style.setProperty('text-shadow', '0 0 8px rgba(0,255,85,0.85)', 'important'); | |||
return; | |||
} | |||
element.style.setProperty('color', '#ffffff', 'important'); | |||
element.style.setProperty('text-shadow', '0 0 6px rgba(255,255,255,0.75)', 'important'); | |||
} | } | ||
function moveClassItemTooltip(event) { | |||
var tooltip = getClassItemTooltipBox(); | |||
var left = event.pageX + 16; | |||
var top = event.pageY + 16; | |||
if ( | if (left + 360 > window.scrollX + window.innerWidth) { | ||
left = event.pageX - 360; | |||
} | |||
} | |||
setTimeout(initClassItemLists, 500); | 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, '''); | |||
} | |||
function initGeneratedItemTooltipsForWiki($content) { | |||
var root = $content && $content[0] ? $content[0] : document; | |||
var items = window.EQOA_GENERATED_ITEMS || []; | |||
var lookup = {}; | |||
var links; | |||
if (!items.length) { | |||
setTimeout(function () { | |||
initGeneratedItemTooltipsForWiki($content); | |||
}, 250); | |||
return; | |||
} | |||
function normalizeItemName(value) { | |||
return String(value || '') | |||
.replace(/\u00a0/g, ' ') | |||
.replace(/[’`]/g, "'") | |||
.replace(/\s+/g, ' ') | |||
.trim() | |||
.toLowerCase(); | |||
} | |||
function looseItemName(value) { | |||
return normalizeItemName(value).replace(/[^a-z0-9]/g, ''); | |||
} | |||
function stripDropText(value) { | |||
return String(value || '') | |||
.replace(/\(\d+%\)/g, '') | |||
.replace(/\([^)]*\)$/g, '') | |||
.replace(/\s+/g, ' ') | |||
.trim(); | |||
} | |||
function rarityRank(item) { | |||
var rarity = normalizeClassItemRarity(item && item.rarity); | |||
if (rarity === 'ultra-rare') return 4; | |||
if (rarity === 'rare') return 3; | |||
if (rarity === 'uncommon') return 2; | |||
return 1; | |||
} | |||
function storeItem(key, item) { | |||
var current; | |||
if (!key) return; | |||
current = lookup[key]; | |||
if (!current || rarityRank(item) > rarityRank(current)) { | |||
lookup[key] = item; | |||
} | |||
} | |||
function getItemFromLink(link) { | |||
var text = stripDropText(link.textContent); | |||
var href = link.getAttribute('href') || ''; | |||
var title = ''; | |||
if (href.indexOf('title=') !== -1) { | |||
title = decodeURIComponent(href.split('title=')[1].split('&')[0]).replace(/_/g, ' '); | |||
} else if (href.indexOf('/index.php?title=') !== -1) { | |||
title = decodeURIComponent(href.split('/index.php?title=')[1].split('&')[0]).replace(/_/g, ' '); | |||
} | |||
return ( | |||
lookup[normalizeItemName(text)] || | |||
lookup[looseItemName(text)] || | |||
lookup[normalizeItemName(title)] || | |||
lookup[looseItemName(title)] || | |||
null | |||
); | |||
} | |||
function normalizeGeneratedItem(item) { | |||
return { | |||
name: item.name || '', | |||
rarity: item.rarity || 'common', | |||
slot: item.slot || '', | |||
level: item.level || item.lvl || '', | |||
race: item.race || '', | |||
className: Array.isArray(item.class) ? item.class.join(', ') : (item.class || item.classes || item.className || ''), | |||
notrade: item.notrade || item.noTrade || item.trade === 'NO TRADE', | |||
lore: item.lore || '', | |||
dmg: item.dmg || item.damage || '', | |||
ac: item.ac || '', | |||
str: item.str || '', | |||
sta: item.sta || '', | |||
agi: item.agi || '', | |||
dex: item.dex || '', | |||
wis: item.wis || '', | |||
int: item.int || '', | |||
cha: item.cha || '', | |||
hp: item.hp || '', | |||
hot: item.hot || '', | |||
pow: item.pow || '', | |||
pot: item.pot || '', | |||
pr: item.pr || '', | |||
dr: item.dr || '', | |||
fr: item.fr || '', | |||
cr: item.cr || '', | |||
lr: item.lr || '', | |||
ar: item.ar || '', | |||
dur: item.dur || '', | |||
gearhp: item.gearhp || item.gearHp || item.gear_hp || '', | |||
effect: item.effect || '', | |||
proc: item.proc || '', | |||
source: item.source || '' | |||
}; | |||
} | |||
items.forEach(function (item) { | |||
if (!item || !item.name) return; | |||
storeItem(normalizeItemName(item.name), item); | |||
storeItem(looseItemName(item.name), item); | |||
}); | |||
links = root.querySelectorAll( | |||
'#items-found-here-table td:first-child a, ' + | |||
'#items-found-here-table .items-found-here-name a, ' + | |||
'.zone-monster-table .zone-monster-drops a, ' + | |||
'.zone-monster-table td:nth-child(3) a, ' + | |||
'.eqoa-related-items a' | |||
); | |||
Array.prototype.forEach.call(links, function (link) { | |||
var item = getItemFromLink(link); | |||
var tooltipItem; | |||
var rarity; | |||
if (!item) return; | |||
tooltipItem = normalizeGeneratedItem(item); | |||
rarity = normalizeClassItemRarity(tooltipItem.rarity); | |||
link.classList.remove( | |||
'class-item-rarity-common', | |||
'class-item-rarity-uncommon', | |||
'class-item-rarity-rare', | |||
'class-item-rarity-ultra-rare', | |||
'class-item-rarity-legendary', | |||
'class-item-rarity-unique', | |||
'cb-rarity-common', | |||
'cb-rarity-uncommon', | |||
'cb-rarity-rare', | |||
'cb-rarity-ultra-rare', | |||
'cb-rarity-legendary', | |||
'cb-rarity-unique', | |||
'eqoa-related-tooltip-ready', | |||
'items-found-tooltip-ready' | |||
); | |||
link.classList.add('class-item-tooltip-link'); | |||
link.classList.add('class-item-rarity-' + rarity); | |||
link.classList.add('cb-rarity-' + rarity); | |||
applyClassItemRarityStyle(link, rarity); | |||
link.onmouseenter = function (event) { | |||
showClassItemTooltip(tooltipItem, event); | |||
}; | |||
link.onmousemove = moveClassItemTooltip; | |||
link.onmouseleave = function () { | |||
hideClassItemTooltip(); | |||
}; | |||
}); | |||
} | |||
mw.hook('wikipage.content').add(initGeneratedItemTooltipsForWiki); | |||
document.addEventListener('DOMContentLoaded', initGeneratedItemTooltipsForWiki); | |||
setTimeout(initGeneratedItemTooltipsForWiki, 500); | |||
setTimeout(initGeneratedItemTooltipsForWiki, 1500); | |||
mw.hook('wikipage.content').add(initClassItemLists); | |||
if (document.readyState === 'loading') { | |||
document.addEventListener('DOMContentLoaded', initClassItemLists); | |||
} else { | |||
initClassItemLists(); | |||
} | |||
setTimeout(initClassItemLists, 500); | |||
setTimeout(initClassItemLists, 1500); | setTimeout(initClassItemLists, 1500); | ||
function initItemsFoundHereSortControls(root) { | |||
root = root && root.jquery ? root[0] : (root || document); | |||
var table = root.querySelector('#items-found-here-table'); | |||
if (!table) return; | |||
var wrapper = table.closest('.zone-drops-auto-wrapper') || table.parentElement; | |||
if (!wrapper) return; | |||
if (wrapper.querySelector('.zone-drops-controls')) return; | |||
var controls = document.createElement('div'); | |||
controls.className = 'zone-drops-controls'; | |||
controls.innerHTML = | |||
'<div class="zone-drops-controls-left">' + | |||
'<input class="zone-drops-search" type="search" placeholder="Search item">' + | |||
'</div>' + | |||
'<div class="zone-drops-controls-right">' + | |||
'<select class="zone-drops-sort-select">' + | |||
'<option value="name">Sort: Name</option>' + | |||
'<option value="level">Sort: Level</option>' + | |||
'<option value="class">Sort: Class</option>' + | |||
'<option value="dmg">Sort: DMG</option>' + | |||
'<option value="ac">Sort: AC</option>' + | |||
'<option value="slot">Sort: Slot</option>' + | |||
'<option value="rarity">Sort: Rarity</option>' + | |||
'</select>' + | |||
'<button type="button" class="zone-drops-sort-button">Sort</button>' + | |||
'<select class="zone-drops-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 type="button" class="zone-drops-clear">Clear</button>' + | |||
'</div>'; | |||
wrapper.insertBefore(controls, table); | |||
var searchInput = controls.querySelector('.zone-drops-search'); | |||
var sortSelect = controls.querySelector('.zone-drops-sort-select'); | |||
var sortButton = controls.querySelector('.zone-drops-sort-button'); | |||
var classSelect = controls.querySelector('.zone-drops-class-select'); | |||
var clearButton = controls.querySelector('.zone-drops-clear'); | |||
function getRows() { | |||
return Array.from(table.querySelectorAll('tbody tr')); | |||
} | |||
function getCellText(row, index) { | |||
var cell = row.children[index]; | |||
return cell ? cell.textContent.trim() : ''; | |||
} | |||
function getSortValue(row, key) { | |||
if (key === 'name') return row.getAttribute('data-name') || getCellText(row, 0); | |||
if (key === 'level') return row.getAttribute('data-level') || getCellText(row, 1); | |||
if (key === 'class') return row.getAttribute('data-class') || getCellText(row, 2); | |||
return row.getAttribute('data-' + key) || ''; | |||
} | |||
function numberOrText(value) { | |||
var number = parseFloat(String(value).replace(/[^\d.-]/g, '')); | |||
return isNaN(number) ? String(value).toLowerCase() : number; | |||
} | |||
function applyFilters() { | |||
var query = searchInput.value.trim().toLowerCase(); | |||
var classFilter = classSelect.value.trim().toUpperCase(); | |||
getRows().forEach(function (row) { | |||
var rowText = row.textContent.toLowerCase(); | |||
var rowClass = String(row.getAttribute('data-class') || getCellText(row, 2)).toUpperCase(); | |||
var matchesSearch = !query || rowText.indexOf(query) !== -1; | |||
var matchesClass = !classFilter || rowClass.indexOf(classFilter) !== -1; | |||
row.style.display = matchesSearch && matchesClass ? '' : 'none'; | |||
}); | |||
} | |||
function sortRows() { | |||
var tbody = table.querySelector('tbody'); | |||
var key = sortSelect.value; | |||
var rows = getRows(); | |||
rows.sort(function (a, b) { | |||
var aValue = numberOrText(getSortValue(a, key)); | |||
var bValue = numberOrText(getSortValue(b, key)); | |||
if (typeof aValue === 'number' && typeof bValue === 'number') { | |||
return aValue - bValue; | |||
} | |||
return String(aValue).localeCompare(String(bValue)); | |||
}); | |||
rows.forEach(function (row) { | |||
tbody.appendChild(row); | |||
}); | |||
applyFilters(); | |||
} | |||
searchInput.addEventListener('input', applyFilters); | |||
classSelect.addEventListener('change', applyFilters); | |||
sortButton.addEventListener('click', sortRows); | |||
clearButton.addEventListener('click', function () { | |||
searchInput.value = ''; | |||
classSelect.value = ''; | |||
sortSelect.value = 'name'; | |||
applyFilters(); | |||
}); | |||
} | |||
function initEqoaCategoryLists() { | |||
document.querySelectorAll('.eqoa-category-list').forEach(function (box) { | |||
var category = box.getAttribute('data-category'); | |||
if (!category || box.classList.contains('eqoa-category-list-ready')) return; | |||
box.classList.add('eqoa-category-list-ready'); | |||
box.innerHTML = 'Loading...'; | |||
new mw.Api().get({ | |||
action: 'query', | |||
list: 'categorymembers', | |||
cmtitle: 'Category:' + category, | |||
cmlimit: 500, | |||
format: 'json' | |||
}).then(function (data) { | |||
var pages = data.query.categorymembers || []; | |||
if (!pages.length) { | |||
box.innerHTML = 'No entries found.'; | |||
return; | |||
} | |||
pages.sort(function (a, b) { | |||
return a.title.localeCompare(b.title); | |||
}); | |||
box.innerHTML = | |||
'<table class="zone-data-table zone-quest-table">' + | |||
'<tr>' + | |||
'<th class="zone-quest-name">Quest</th>' + | |||
'<th class="zone-quest-type">Type</th>' + | |||
'<th class="zone-quest-starter">Starter</th>' + | |||
'<th class="zone-quest-location">Location</th>' + | |||
'</tr>' + | |||
pages.map(function (page) { | |||
return '<tr>' + | |||
'<td class="zone-quest-name"><a href="' + mw.util.getUrl(page.title) + '">' + page.title + '</a></td>' + | |||
'<td class="zone-quest-type"></td>' + | |||
'<td class="zone-quest-starter"></td>' + | |||
'<td class="zone-quest-location"></td>' + | |||
'</tr>'; | |||
}).join('') + | |||
'</table>'; | |||
}); | |||
}); | |||
} | |||
mw.hook('wikipage.content').add(initEqoaCategoryLists); | |||
document.addEventListener('DOMContentLoaded', initEqoaCategoryLists); | |||
function initEqoaClassIndex() { | |||
document.querySelectorAll('.eqoa-class-index').forEach(function (root) { | |||
if (root.classList.contains('eqoa-class-index-ready')) return; | |||
var grid = root.querySelector('.eqoa-class-index-grid'); | |||
var cards = root.querySelectorAll('.eqoa-class-card'); | |||
var toolbar = document.createElement('div'); | |||
var filters = document.createElement('div'); | |||
var roles; | |||
var activeRole = 'all'; | |||
if (!grid) return; | |||
root.classList.add('eqoa-class-index-ready'); | |||
toolbar.className = 'eqoa-class-index-toolbar'; | |||
filters.className = 'eqoa-class-index-filters'; | |||
if (root.classList.contains('eqoa-race-index')) { | |||
roles = [ | |||
['all', 'All'], | |||
['good', 'Good'], | |||
['evil', 'Evil'], | |||
['neutral', 'Neutral'] | |||
]; | |||
} else { | |||
roles = [ | |||
['all', 'All'], | |||
['tank', 'Tank'], | |||
['melee', 'Melee'], | |||
['healer', 'Healer'], | |||
['support', 'Support'], | |||
['caster', 'Caster'] | |||
]; | |||
} | |||
roles.forEach(function (role) { | |||
var button = document.createElement('button'); | |||
button.type = 'button'; | |||
button.textContent = role[1]; | |||
button.setAttribute('data-role-filter', role[0]); | |||
if (role[0] === 'all') { | |||
button.classList.add('is-active'); | |||
} | |||
button.addEventListener('click', function () { | |||
activeRole = role[0]; | |||
filters.querySelectorAll('button').forEach(function (btn) { | |||
btn.classList.remove('is-active'); | |||
}); | |||
button.classList.add('is-active'); | |||
applyFilters(); | |||
}); | |||
filters.appendChild(button); | |||
}); | |||
toolbar.appendChild(filters); | |||
root.insertBefore(toolbar, grid); | |||
function cardHasRole(card, role) { | |||
if (role === 'all') return true; | |||
return card.classList.contains('eqoa-role-' + role); | |||
} | |||
function applyFilters() { | |||
cards.forEach(function (card) { | |||
card.style.display = cardHasRole(card, activeRole) ? '' : 'none'; | |||
}); | |||
} | |||
applyFilters(); | |||
}); | |||
} | |||
mw.loader.load(mw.config.get('wgScript') + '?title=MediaWiki:ClassSpellGeneratedSpells1.js&action=raw&ctype=text/javascript'); | |||
mw.loader.load(mw.config.get('wgScript') + '?title=MediaWiki:ClassSpellGeneratedSpells2.js&action=raw&ctype=text/javascript'); | |||
mw.loader.load(mw.config.get('wgScript') + '?title=MediaWiki:ClassSpellGeneratedSpells3.js&action=raw&ctype=text/javascript'); | |||
mw.loader.load(mw.config.get('wgScript') + '?title=MediaWiki:ClassSpellGeneratedSpells4.js&action=raw&ctype=text/javascript'); | |||
mw.loader.load(mw.config.get('wgScript') + '?title=MediaWiki:ClassSpellGeneratedSpells5.js&action=raw&ctype=text/javascript'); | |||
mw.loader.load(mw.config.get('wgScript') + '?title=MediaWiki:ClassSpellGroupOverrides.js&action=raw&ctype=text/javascript&v=20260604'); | |||
mw.loader.load(mw.config.get('wgScript') + '?title=MediaWiki:ClassSpellGroupOverrides.js&action=raw&ctype=text/javascript'); | |||
mw.hook('wikipage.content').add(initEqoaClassIndex); | |||
document.addEventListener('DOMContentLoaded', initEqoaClassIndex); | |||
setTimeout(initEqoaClassIndex, 500); | |||
function initEqoaSpellLists() { | |||
var lists = document.querySelectorAll('[data-eqoa-spell-list]'); | |||
var spells = window.EQOA_GENERATED_SPELLS || []; | |||
if (!lists.length) return; | |||
if (!spells.length) { | |||
setTimeout(initEqoaSpellLists, 250); | |||
return; | |||
} | |||
function escapeHtml(value) { | |||
return String(value == null ? '' : value) | |||
.replace(/&/g, '&') | |||
.replace(/</g, '<') | |||
.replace(/>/g, '>') | |||
.replace(/"/g, '"') | |||
.replace(/'/g, '''); | |||
} | |||
function fileUrl(fileName) { | |||
fileName = String(fileName || '').trim(); | |||
if (!fileName) return ''; | |||
if (/^https?:\/\//i.test(fileName)) return fileName; | |||
return mw.config.get('wgScript') + '?title=Special:Redirect/file/' + encodeURIComponent(fileName); | |||
} | |||
function pageUrl(title) { | |||
if (window.mw && mw.util && mw.util.getUrl) { | |||
return mw.util.getUrl(title); | |||
} | |||
return '/index.php?title=' + encodeURIComponent(String(title || '').replace(/ /g, '_')); | |||
} | |||
function iconHtml(fileName, className, altText) { | |||
var url = fileUrl(fileName); | |||
if (!url) return ''; | |||
return '<img class="' + className + '" src="' + url + '" alt="' + escapeHtml(altText || '') + '">'; | |||
} | |||
function classIconFile(classCode) { | |||
return 'ClassIcon' + classCode + '.png'; | |||
} | |||
function classIconsHtml(spell) { | |||
return (spell.classes || []).map(function (classCode) { | |||
return iconHtml(classIconFile(classCode), 'eqoa-spell-class-icon', classCode); | |||
}).join(''); | |||
} | |||
function spellIconFile(spell) { | |||
var iconName = String(spell.icon || '').trim(); | |||
if (!iconName) return ''; | |||
iconName = iconName | |||
.replace(/\.[a-z0-9]+$/i, '') | |||
.replace(/[^a-z0-9]+/gi, ''); | |||
return 'SpellIcon' + iconName + '.png'; | |||
} | |||
function classText(spell) { | |||
return (spell.classNames || spell.classes || []).join(', '); | |||
} | |||
function sortSpells(rows, sortKey) { | |||
rows.sort(function (a, b) { | |||
if (sortKey === 'name') { | |||
return String(a.name || '').localeCompare(String(b.name || '')); | |||
} | |||
if (sortKey === 'level') { | |||
return (parseFloat(a.level) || 0) - (parseFloat(b.level) || 0); | |||
} | |||
return String(classText(a)).localeCompare(String(classText(b))); | |||
}); | |||
} | |||
function getSpellTooltipBox() { | |||
var tooltip = document.getElementById('eqoa-spell-tooltip'); | |||
if (!tooltip) { | |||
tooltip = document.createElement('div'); | |||
tooltip.id = 'eqoa-spell-tooltip'; | |||
tooltip.className = 'eqoa-spell-tooltip'; | |||
tooltip.style.display = 'none'; | |||
document.body.appendChild(tooltip); | |||
} | |||
return tooltip; | |||
} | |||
function formatSpellValue(value) { | |||
if (value === undefined || value === null || value === '') { | |||
return '0'; | |||
} | |||
return escapeHtml(value); | |||
} | |||
function showSpellTooltip(spell, event) { | |||
var tooltip = getSpellTooltipBox(); | |||
var name = escapeHtml(spell.name || ''); | |||
var scope = escapeHtml(spell.scope || ''); | |||
var cast = formatSpellValue(spell.castTime); | |||
var recast = formatSpellValue(spell.recast); | |||
var power = formatSpellValue(spell.powerCost); | |||
var equipReq = escapeHtml((spell.classNames || spell.classes || ['ANY']).join(', ') || 'ANY'); | |||
var level = formatSpellValue(spell.level); | |||
var range = formatSpellValue(spell.range); | |||
var description = escapeHtml(spell.description || ''); | |||
tooltip.innerHTML = | |||
'<div class="eqoa-spell-tooltip-main">' + | |||
'<div class="eqoa-spell-tooltip-name">' + name + '</div>' + | |||
'<div>Scope: ' + scope + ' <span>Cast: ' + cast + '</span></div>' + | |||
'<div>Recast: ' + recast + ' <span>Power: ' + power + '</span></div>' + | |||
'<div>EquipReq: ' + equipReq + '</div>' + | |||
'<div>Level: ' + level + ' <span>Range: ' + range + '</span></div>' + | |||
'</div>' + | |||
'<div class="eqoa-spell-tooltip-desc">' + | |||
description + | |||
'</div>'; | |||
tooltip.style.display = 'block'; | |||
moveSpellTooltip(event); | |||
} | |||
function moveSpellTooltip(event) { | |||
var tooltip = getSpellTooltipBox(); | |||
var left = event.pageX + 18; | |||
var top = event.pageY + 18; | |||
if (left + 430 > window.scrollX + window.innerWidth) { | |||
left = event.pageX - 430; | |||
} | |||
tooltip.style.left = left + 'px'; | |||
tooltip.style.top = top + 'px'; | |||
} | |||
function hideSpellTooltip() { | |||
var tooltip = document.getElementById('eqoa-spell-tooltip'); | |||
if (tooltip) { | |||
tooltip.style.display = 'none'; | |||
} | |||
} | |||
function bindSpellTooltip(row, spell) { | |||
row.addEventListener('mouseenter', function (event) { | |||
showSpellTooltip(spell, event); | |||
}); | |||
row.addEventListener('mousemove', moveSpellTooltip); | |||
row.addEventListener('mouseleave', hideSpellTooltip); | |||
} | |||
function renderRows(tbody, rows) { | |||
tbody.innerHTML = ''; | |||
rows.forEach(function (spell) { | |||
var row = document.createElement('tr'); | |||
var name = escapeHtml(spell.name || ''); | |||
var level = escapeHtml(spell.level || ''); | |||
var classes = classIconsHtml(spell); | |||
var skill = escapeHtml(classText(spell)); | |||
var icon = iconHtml(spellIconFile(spell), 'eqoa-spell-icon', spell.name); | |||
row.className = 'eqoa-spell-row'; | |||
row.innerHTML = | |||
'<td class="eqoa-spell-name-cell">' + icon + '<a href="' + pageUrl(spell.name) + '">' + name + '</a></td>' + | |||
'<td class="eqoa-spell-level-cell">' + level + '</td>' + | |||
'<td class="eqoa-spell-classes-cell">' + classes + '</td>'; | |||
bindSpellTooltip(row, spell); | |||
tbody.appendChild(row); | |||
}); | |||
} | |||
lists.forEach(function (box) { | |||
var classCode = String(box.getAttribute('data-class') || '').toUpperCase().trim(); | |||
var rows; | |||
var table; | |||
var tbody; | |||
var sortSelect; | |||
var loading; | |||
if (box.classList.contains('eqoa-spell-list-ready')) return; | |||
box.classList.add('eqoa-spell-list-ready'); | |||
rows = spells.filter(function (spell) { | |||
if (String(spell.spellGroup || 'Base') !== 'Base') return false; | |||
if (!classCode) return true; | |||
return (spell.classes || []).indexOf(classCode) !== -1; | |||
}); | |||
sortSpells(rows, 'level'); | |||
if (!rows.length) { | |||
box.innerHTML += '<div class="eqoa-spell-empty">No spells found.</div>'; | |||
return; | |||
} | |||
loading = box.querySelector('.eqoa-spell-list__loading'); | |||
if (loading) loading.remove(); | |||
box.insertAdjacentHTML('beforeend', | |||
'<div class="eqoa-spell-toolbar">' + | |||
'<div class="eqoa-spell-count">' + rows.length + ' spells</div>' + | |||
'<div class="eqoa-spell-sort-wrap">' + | |||
'<select class="eqoa-spell-sort">' + | |||
'<option value="level">Sort: Level</option>' + | |||
'<option value="name">Sort: Name</option>' + | |||
'<option value="class">Sort: Class</option>' + | |||
'</select>' + | |||
'</div>' + | |||
'</div>' + | |||
'<table class="eqoa-spell-list-table">' + | |||
'<tr>' + | |||
'<th>Name</th>' + | |||
'<th>Level</th>' + | |||
'<th>Classes</th>' + | |||
'</tr>' + | |||
'</table>' | |||
); | |||
table = box.querySelector('.eqoa-spell-list-table'); | |||
tbody = document.createElement('tbody'); | |||
table.appendChild(tbody); | |||
renderRows(tbody, rows); | |||
sortSelect = box.querySelector('.eqoa-spell-sort'); | |||
sortSelect.addEventListener('change', function () { | |||
sortSpells(rows, sortSelect.value); | |||
renderRows(tbody, rows); | |||
}); | |||
}); | |||
} | |||
mw.hook('wikipage.content').add(initEqoaSpellLists); | |||
document.addEventListener('DOMContentLoaded', initEqoaSpellLists); | |||
setTimeout(initEqoaSpellLists, 500); | |||
setTimeout(initEqoaSpellLists, 1500); | |||
function initEqoaSpellPathLists() { | |||
var spellData = window.EQOA_GENERATED_SPELLS || []; | |||
document.querySelectorAll('[data-eqoa-spell-path-list]').forEach(function (box) { | |||
var classCode; | |||
var spellGroup; | |||
var title; | |||
var rows; | |||
var path1; | |||
var path2; | |||
var loading; | |||
classCode = String(box.getAttribute('data-class') || '').trim().toUpperCase(); | |||
spellGroup = normalizeEqoaSpellPathGroup(box.getAttribute('data-spell-group')); | |||
title = box.querySelector('.eqoa-spell-list__title') | |||
? box.querySelector('.eqoa-spell-list__title').textContent.trim() | |||
: 'Spell Path'; | |||
if (!spellData.length) { | |||
setTimeout(initEqoaSpellPathLists, 250); | |||
return; | |||
} | |||
loading = box.querySelector('.eqoa-spell-list__loading'); | |||
if (loading) loading.remove(); | |||
rows = spellData.filter(function (spell) { | |||
var currentGroup = normalizeEqoaSpellPathGroup(spell.spellGroup); | |||
if (currentGroup !== spellGroup) return false; | |||
if (!classCode) return true; | |||
return (spell.classes || []).map(function (code) { | |||
return String(code || '').toUpperCase(); | |||
}).indexOf(classCode) !== -1; | |||
}); | |||
path1 = rows.filter(function (spell) { | |||
var spellPath = String(spell.spellPath || ''); | |||
return spellPath === '1' || spellPath === 'both'; | |||
}); | |||
path2 = rows.filter(function (spell) { | |||
var spellPath = String(spell.spellPath || ''); | |||
return spellPath === '2' || spellPath === 'both'; | |||
}); | |||
box.innerHTML = | |||
'<div class="eqoa-spell-list__title">' + escapeEqoaSpellPathHtml(title) + '</div>' + | |||
'<div class="eqoa-spell-path-grid">' + | |||
buildEqoaSpellPathSection('Path 1', path1) + | |||
buildEqoaSpellPathSection('Path 2', path2) + | |||
'</div>'; | |||
bindEqoaSpellPathTooltipEvents(box, rows); | |||
}); | |||
} | |||
function normalizeEqoaSpellPathGroup(value) { | |||
value = String(value || '').toLowerCase().replace(/\s+/g, ''); | |||
if (value === 'classpath' || value === 'path') return 'ClassPath'; | |||
if (value === 'exemplarpath' || value === 'exemplar') return 'ExemplarPath'; | |||
return value; | |||
} | |||
function buildEqoaSpellPathSection(title, spells) { | |||
var html = ''; | |||
html += '<div class="eqoa-spell-path-section">'; | |||
html += '<div class="eqoa-spell-path-title">' + escapeEqoaSpellPathHtml(title) + '</div>'; | |||
html += '<table class="eqoa-spell-list-table">'; | |||
html += '<tr>'; | |||
html += '<th>Name</th>'; | |||
html += '<th>Level</th>'; | |||
html += '<th>Classes</th>'; | |||
html += '</tr>'; | |||
if (!spells.length) { | |||
html += '<tr class="eqoa-spell-row"><td colspan="4">No spells found.</td></tr>'; | |||
} else { | |||
spells.forEach(function (spell) { | |||
html += '<tr class="eqoa-spell-row" data-eqoa-spell-name="' + escapeEqoaSpellPathHtml(spell.name) + '">'; | |||
html += '<td class="eqoa-spell-name-cell">'; | |||
html += buildEqoaSpellPathIcon(spell); | |||
html += '<a class="eqoa-spell-link" href="' + eqoaSpellPathPageUrl(spell.name) + '" data-eqoa-spell-name="' + escapeEqoaSpellPathHtml(spell.name) + '">'; | |||
html += escapeEqoaSpellPathHtml(spell.name); | |||
html += '</a>'; | |||
html += '</td>'; | |||
html += '<td class="eqoa-spell-level-cell">' + escapeEqoaSpellPathHtml(spell.level || '') + '</td>'; | |||
html += '<td class="eqoa-spell-classes-cell">' + buildEqoaSpellPathClassIcons(spell) + '</td>'; | |||
html += '</tr>'; | |||
}); | |||
} | |||
html += '</table>'; | |||
html += '</div>'; | |||
return html; | |||
} | |||
function buildEqoaSpellPathIcon(spell) { | |||
var fileName = spellIconNameToFile(spell.icon); | |||
if (!fileName) return ''; | |||
return '<img class="eqoa-spell-icon" src="' + eqoaSpellPathFileUrl(fileName) + '" alt="' + escapeEqoaSpellPathHtml(spell.name || '') + '">'; | |||
} | |||
function spellIconNameToFile(iconName) { | |||
iconName = String(iconName || '').trim(); | |||
if (!iconName) return ''; | |||
iconName = iconName | |||
.replace(/\.[a-z0-9]+$/i, '') | |||
.replace(/[^a-z0-9]+/gi, ''); | |||
return 'SpellIcon' + iconName + '.png'; | |||
} | |||
function buildEqoaSpellPathClassIcons(spell) { | |||
var classes = spell.classes || []; | |||
if (!classes.length) return ''; | |||
return classes.map(function (classCode) { | |||
return '<img class="eqoa-spell-class-icon" src="' + eqoaSpellPathFileUrl('ClassIcon' + classCode + '.png') + '" alt="' + escapeEqoaSpellPathHtml(classCode) + '" title="' + escapeEqoaSpellPathHtml(classCode) + '">'; | |||
}).join(''); | |||
} | |||
function bindEqoaSpellPathTooltipEvents(root, spells) { | |||
var lookup = {}; | |||
spells.forEach(function (spell) { | |||
lookup[String(spell.name || '').toLowerCase()] = spell; | |||
}); | |||
root.querySelectorAll('.eqoa-spell-row, .eqoa-spell-link').forEach(function (el) { | |||
var spellName = String(el.getAttribute('data-eqoa-spell-name') || el.textContent || '').trim(); | |||
var spell = lookup[spellName.toLowerCase()]; | |||
if (!spell) return; | |||
el.onmouseenter = function (event) { | |||
showEqoaSpellPathTooltip(spell, event); | |||
}; | |||
el.onmousemove = function (event) { | |||
moveEqoaSpellPathTooltip(event); | |||
}; | |||
el.onmouseleave = function () { | |||
hideEqoaSpellPathTooltip(); | |||
}; | |||
}); | |||
} | |||
function getEqoaSpellPathTooltipBox() { | |||
var tooltip = document.getElementById('eqoa-spell-tooltip'); | |||
if (!tooltip) { | |||
tooltip = document.createElement('div'); | |||
tooltip.id = 'eqoa-spell-tooltip'; | |||
tooltip.className = 'eqoa-spell-tooltip'; | |||
tooltip.style.display = 'none'; | |||
document.body.appendChild(tooltip); | |||
} | |||
return tooltip; | |||
} | |||
function showEqoaSpellPathTooltip(spell, event) { | |||
var tooltip = getEqoaSpellPathTooltipBox(); | |||
var name = escapeEqoaSpellPathHtml(spell.name || ''); | |||
var scope = escapeEqoaSpellPathHtml(spell.scope || ''); | |||
var cast = formatEqoaSpellPathValue(spell.castTime); | |||
var recast = formatEqoaSpellPathValue(spell.recast); | |||
var power = formatEqoaSpellPathValue(spell.powerCost); | |||
var equipReq = escapeEqoaSpellPathHtml(spell.reqEquipment || 'ANY'); | |||
var level = formatEqoaSpellPathValue(spell.level); | |||
var range = formatEqoaSpellPathValue(spell.range); | |||
var description = escapeEqoaSpellPathHtml(spell.description || ''); | |||
tooltip.innerHTML = | |||
'<div class="eqoa-spell-tooltip-main">' + | |||
'<div class="eqoa-spell-tooltip-name">' + name + '</div>' + | |||
'<div>Scope: ' + scope + ' <span>Cast: ' + cast + '</span></div>' + | |||
'<div>Recast: ' + recast + ' <span>Power: ' + power + '</span></div>' + | |||
'<div>EquipReq: ' + equipReq + '</div>' + | |||
'<div>Level: ' + level + ' <span>Range: ' + range + '</span></div>' + | |||
'</div>' + | |||
'<div class="eqoa-spell-tooltip-desc">' + | |||
description + | |||
'</div>'; | |||
tooltip.style.display = 'block'; | |||
moveEqoaSpellPathTooltip(event); | |||
} | |||
function moveEqoaSpellPathTooltip(event) { | |||
var tooltip = getEqoaSpellPathTooltipBox(); | |||
var left = event.pageX + 18; | |||
var top = event.pageY + 18; | |||
if (left + 430 > window.scrollX + window.innerWidth) { | |||
left = event.pageX - 430; | |||
} | |||
tooltip.style.left = left + 'px'; | |||
tooltip.style.top = top + 'px'; | |||
} | |||
function hideEqoaSpellPathTooltip() { | |||
var tooltip = document.getElementById('eqoa-spell-tooltip'); | |||
if (tooltip) { | |||
tooltip.style.display = 'none'; | |||
} | |||
} | |||
function formatEqoaSpellPathValue(value) { | |||
if (value === undefined || value === null || value === '') { | |||
return '0'; | |||
} | |||
return escapeEqoaSpellPathHtml(value); | |||
} | |||
function eqoaSpellPathFileUrl(fileName) { | |||
fileName = String(fileName || '').trim(); | |||
if (!fileName) return ''; | |||
if (/^https?:\/\//i.test(fileName)) return fileName; | |||
return mw.config.get('wgScript') + '?title=Special:Redirect/file/' + encodeURIComponent(fileName); | |||
} | |||
function eqoaSpellPathPageUrl(title) { | |||
if (window.mw && mw.util && mw.util.getUrl) { | |||
return mw.util.getUrl(title); | |||
} | |||
return '/index.php?title=' + encodeURIComponent(String(title || '').replace(/ /g, '_')); | |||
} | |||
function escapeEqoaSpellPathHtml(value) { | |||
return String(value || '') | |||
.replace(/&/g, '&') | |||
.replace(/</g, '<') | |||
.replace(/>/g, '>') | |||
.replace(/"/g, '"') | |||
.replace(/'/g, '''); | |||
} | |||
function initZoneNpcListFilters() { | |||
document.querySelectorAll('.zone-npc-list-wrapper').forEach(function (wrapper) { | |||
var table = wrapper.querySelector('.zone-monster-table'); | |||
var bar; | |||
var input; | |||
var clear; | |||
if (!table) return; | |||
if (wrapper.classList.contains('zone-npc-filter-ready')) return; | |||
wrapper.classList.add('zone-npc-filter-ready'); | |||
bar = document.createElement('div'); | |||
bar.className = 'zone-npc-filter-bar'; | |||
input = document.createElement('input'); | |||
input.className = 'zone-npc-search-input'; | |||
input.type = 'search'; | |||
input.placeholder = 'Search NPCs'; | |||
clear = document.createElement('button'); | |||
clear.className = 'zone-npc-search-clear'; | |||
clear.type = 'button'; | |||
clear.textContent = 'Clear'; | |||
bar.appendChild(input); | |||
bar.appendChild(clear); | |||
wrapper.insertBefore(bar, table); | |||
function filterRows() { | |||
var query = String(input.value || '').toLowerCase(); | |||
var rows = table.querySelectorAll('tr'); | |||
Array.prototype.forEach.call(rows, function (row, index) { | |||
var text; | |||
if (index === 0) return; | |||
text = String(row.textContent || '').toLowerCase(); | |||
row.style.display = !query || text.indexOf(query) !== -1 ? '' : 'none'; | |||
}); | |||
} | |||
input.addEventListener('input', filterRows); | |||
clear.addEventListener('click', function () { | |||
input.value = ''; | |||
filterRows(); | |||
input.focus(); | |||
}); | |||
}); | |||
} | |||
function initEqoaCategoryPageLists() { | |||
var boxes = document.querySelectorAll('.eqoa-category-page-list'); | |||
if (!boxes.length) return; | |||
boxes.forEach(function (box) { | |||
var category = box.getAttribute('data-category'); | |||
var api; | |||
if (!category) return; | |||
if (box.classList.contains('eqoa-category-page-list-ready')) return; | |||
box.classList.add('eqoa-category-page-list-ready'); | |||
box.textContent = 'Loading...'; | |||
api = new mw.Api(); | |||
api.get({ | |||
action: 'query', | |||
list: 'categorymembers', | |||
cmtitle: 'Category:' + category, | |||
cmnamespace: 0, | |||
cmlimit: 500, | |||
cmsort: 'sortkey', | |||
cmdir: 'asc', | |||
format: 'json' | |||
}).then(function (data) { | |||
var pages = data.query && data.query.categorymembers | |||
? data.query.categorymembers | |||
: []; | |||
var list; | |||
box.innerHTML = ''; | |||
if (!pages.length) { | |||
box.textContent = 'No pages found.'; | |||
return; | |||
} | |||
list = document.createElement('ul'); | |||
list.className = 'eqoa-category-page-list-items'; | |||
pages.forEach(function (page) { | |||
var item = document.createElement('li'); | |||
var link = document.createElement('a'); | |||
link.href = mw.util.getUrl(page.title); | |||
link.textContent = page.title; | |||
item.appendChild(link); | |||
list.appendChild(item); | |||
}); | |||
box.appendChild(list); | |||
}).catch(function () { | |||
box.textContent = 'Unable to load category.'; | |||
}); | |||
}); | |||
} | |||
function initQuestRewardItemTooltips() { | |||
var links = document.querySelectorAll('.quest-reward-items a'); | |||
var items = window.EQOA_GENERATED_ITEMS || []; | |||
if (!links.length) return; | |||
if (!items.length) { | |||
setTimeout(initQuestRewardItemTooltips, 500); | |||
return; | |||
} | |||
function normalizeQuestRewardRarity(rarity) { | |||
return String(rarity || 'common') | |||
.toLowerCase() | |||
.replace(/\s+/g, '-'); | |||
} | |||
function getQuestRewardRarityStyle(rarity) { | |||
rarity = normalizeQuestRewardRarity(rarity); | |||
if (rarity === 'uncommon') { | |||
return { | |||
color: '#20ff45', | |||
shadow: '0 0 6px rgba(32, 255, 69, 0.55)' | |||
}; | |||
} | |||
if (rarity === 'rare') { | |||
return { | |||
color: '#1e88ff', | |||
shadow: '0 0 6px rgba(30, 136, 255, 0.65)' | |||
}; | |||
} | |||
if (rarity === 'ultra-rare') { | |||
return { | |||
color: '#ff3cff', | |||
shadow: '0 0 8px rgba(255, 60, 255, 0.75)' | |||
}; | |||
} | |||
if (rarity === 'legendary') { | |||
return { | |||
color: '#ff8000', | |||
shadow: '0 0 8px rgba(255, 128, 0, 0.85)' | |||
}; | |||
} | |||
if (rarity === 'unique') { | |||
return { | |||
color: '#ffd700', | |||
shadow: '0 0 8px rgba(255, 215, 0, 0.85)' | |||
}; | |||
} | |||
return { | |||
color: '#ffffff', | |||
shadow: '0 0 6px rgba(255, 255, 255, 0.45)' | |||
}; | |||
} | |||
function applyQuestRewardRarityStyle(link, item) { | |||
var rarity = normalizeQuestRewardRarity(item.rarity); | |||
var style = getQuestRewardRarityStyle(rarity); | |||
[ | |||
'class-item-rarity-common', | |||
'class-item-rarity-uncommon', | |||
'class-item-rarity-rare', | |||
'class-item-rarity-ultra-rare', | |||
'class-item-rarity-legendary', | |||
'class-item-rarity-unique' | |||
].forEach(function (className) { | |||
link.classList.remove(className); | |||
}); | |||
link.classList.add('class-item-rarity-' + rarity); | |||
link.style.setProperty('color', style.color, 'important'); | |||
link.style.setProperty('font-weight', '800', 'important'); | |||
link.style.setProperty('text-shadow', style.shadow, 'important'); | |||
link.style.setProperty('text-decoration-color', style.color, 'important'); | |||
} | |||
links.forEach(function (link) { | |||
var itemName; | |||
var item; | |||
itemName = (link.getAttribute('data-class-item-name') || link.textContent || '') | |||
.replace(/_/g, ' ') | |||
.trim(); | |||
try { | |||
itemName = decodeURIComponent(itemName); | |||
} catch (e) {} | |||
if (!itemName) return; | |||
item = items.find(function (entry) { | |||
return String(entry.name || '').toLowerCase() === itemName.toLowerCase(); | |||
}); | |||
if (!item) return; | |||
link.classList.add('quest-reward-tooltip-ready'); | |||
link.classList.add('class-item-tooltip-link'); | |||
link.setAttribute('data-class-item-name', encodeURIComponent(item.name)); | |||
applyQuestRewardRarityStyle(link, item); | |||
if (link.getAttribute('data-quest-reward-tooltip-bound') === '1') return; | |||
link.setAttribute('data-quest-reward-tooltip-bound', '1'); | |||
link.addEventListener('mouseenter', function (event) { | |||
applyQuestRewardRarityStyle(link, item); | |||
if (typeof showClassItemTooltip === 'function') { | |||
showClassItemTooltip(item, event); | |||
} | |||
}); | |||
link.addEventListener('mousemove', function (event) { | |||
if (typeof moveClassItemTooltip === 'function') { | |||
moveClassItemTooltip(event); | |||
} | |||
}); | |||
link.addEventListener('mouseleave', function () { | |||
applyQuestRewardRarityStyle(link, item); | |||
if (typeof hideClassItemTooltip === 'function') { | |||
hideClassItemTooltip(); | |||
} | |||
}); | |||
}); | |||
} | |||
function initEqoaQuestIndexFilters() { | |||
document.querySelectorAll('.eqoa-quest-index-section').forEach(function (section) { | |||
var filterRow = section.querySelector('.eqoa-quest-filter-row'); | |||
var filters = section.querySelectorAll('.eqoa-quest-filter'); | |||
var panels = section.querySelectorAll('.eqoa-quest-panel'); | |||
var search; | |||
if (!filterRow || !filters.length || !panels.length) return; | |||
search = section.querySelector('.eqoa-quest-search'); | |||
if (!search) { | |||
search = document.createElement('input'); | |||
search.type = 'search'; | |||
search.className = 'eqoa-quest-search'; | |||
search.placeholder = 'Search quests'; | |||
filterRow.insertAdjacentElement('afterend', search); | |||
} | |||
function setActive(target) { | |||
filters.forEach(function (filter) { | |||
filter.classList.toggle( | |||
'is-active', | |||
filter.getAttribute('data-quest-filter') === target | |||
); | |||
}); | |||
panels.forEach(function (panel) { | |||
var active = panel.getAttribute('data-quest-panel') === target; | |||
panel.classList.toggle('is-active', active); | |||
panel.style.display = active ? 'block' : 'none'; | |||
}); | |||
filterQuestList(); | |||
} | |||
function filterQuestList() { | |||
var activePanel = section.querySelector('.eqoa-quest-panel.is-active'); | |||
var query = String(search.value || '').toLowerCase(); | |||
if (!activePanel) return; | |||
activePanel.querySelectorAll('li').forEach(function (li) { | |||
li.style.display = li.textContent.toLowerCase().indexOf(query) !== -1 | |||
? '' | |||
: 'none'; | |||
}); | |||
} | |||
if (!section.classList.contains('eqoa-quest-index-ready')) { | |||
section.classList.add('eqoa-quest-index-ready'); | |||
filters.forEach(function (filter) { | |||
filter.addEventListener('click', function () { | |||
setActive(filter.getAttribute('data-quest-filter')); | |||
}); | |||
}); | |||
search.addEventListener('input', filterQuestList); | |||
} | |||
var activeFilter = section.querySelector('.eqoa-quest-filter.is-active') || filters[0]; | |||
setActive(activeFilter.getAttribute('data-quest-filter')); | |||
}); | |||
} | |||
mw.hook('wikipage.content').add(initEqoaQuestIndexFilters); | |||
document.addEventListener('DOMContentLoaded', initEqoaQuestIndexFilters); | |||
setTimeout(initEqoaQuestIndexFilters, 500); | |||
setTimeout(initEqoaQuestIndexFilters, 1500); | |||
function initEqoaQuestIndexExpandableSections() { | |||
document.querySelectorAll('.eqoa-quest-index-section').forEach(function (section) { | |||
var title = section.querySelector(':scope > .eqoa-section-title'); | |||
if (!title) return; | |||
if (section.classList.contains('eqoa-quest-expand-ready')) return; | |||
section.classList.add('eqoa-quest-expand-ready'); | |||
title.addEventListener('click', function () { | |||
section.classList.toggle('is-open'); | |||
}); | |||
}); | |||
} | |||
mw.hook('wikipage.content').add(initEqoaQuestIndexExpandableSections); | |||
document.addEventListener('DOMContentLoaded', initEqoaQuestIndexExpandableSections); | |||
setTimeout(initEqoaQuestIndexExpandableSections, 500); | |||
setTimeout(initEqoaQuestIndexExpandableSections, 1500); | |||
function centerSoaHomeBanner() { | |||
document.querySelectorAll('.soa-home-banner-wrap').forEach(function (wrap) { | |||
var image = wrap.querySelector('img'); | |||
if (!image) return; | |||
wrap.style.display = 'flex'; | |||
wrap.style.justifyContent = 'center'; | |||
wrap.style.alignItems = 'center'; | |||
wrap.style.width = '100%'; | |||
wrap.style.overflow = 'hidden'; | |||
wrap.style.background = 'transparent'; | |||
image.style.display = 'block'; | |||
image.style.width = '100%'; | |||
image.style.maxWidth = '1200px'; | |||
image.style.height = 'auto'; | |||
image.style.margin = '0 auto'; | |||
image.parentElement.style.display = 'block'; | |||
image.parentElement.style.margin = '0 auto'; | |||
}); | |||
} | |||
document.addEventListener('DOMContentLoaded', centerSoaHomeBanner); | |||
mw.hook('wikipage.content').add(centerSoaHomeBanner); | |||
setTimeout(centerSoaHomeBanner, 250); | |||
setTimeout(centerSoaHomeBanner, 1000); | |||
function moveMobilePageActionsAfterNavigation() { | |||
var isMobile = window.matchMedia && window.matchMedia('(max-width: 700px)').matches; | |||
var actions = document.getElementById('p-cactions'); | |||
var sidebar = document.getElementById('sidebar'); | |||
var navigation = document.getElementById('p-navigation'); | |||
var column = document.getElementById('column-one'); | |||
var logo = document.getElementById('p-logo'); | |||
if (!actions || !column) return; | |||
if (isMobile) { | |||
if (navigation && navigation.parentNode) { | |||
navigation.insertAdjacentElement('afterend', actions); | |||
} else if (sidebar) { | |||
sidebar.appendChild(actions); | |||
} else { | |||
column.appendChild(actions); | |||
} | |||
actions.classList.add('eqoa-mobile-page-actions-bottom'); | |||
return; | |||
} | |||
actions.classList.remove('eqoa-mobile-page-actions-bottom'); | |||
if (logo && logo.parentNode === column) { | |||
column.insertBefore(actions, logo); | |||
} else { | |||
column.insertBefore(actions, column.firstChild); | |||
} | |||
} | |||
document.addEventListener('DOMContentLoaded', moveMobilePageActionsAfterNavigation); | |||
mw.hook('wikipage.content').add(moveMobilePageActionsAfterNavigation); | |||
window.addEventListener('resize', moveMobilePageActionsAfterNavigation); | |||
window.addEventListener('orientationchange', function () { | |||
setTimeout(moveMobilePageActionsAfterNavigation, 150); | |||
}); | |||
setTimeout(moveMobilePageActionsAfterNavigation, 250); | |||
setTimeout(moveMobilePageActionsAfterNavigation, 1000); | |||
function initNpcPageItemTooltips() { | |||
var links = document.querySelectorAll('.eqoa-npc-page .eqoa-npc-zone-table td:nth-child(4) a'); | |||
var items = window.EQOA_GENERATED_ITEMS || []; | |||
if (!links.length) return; | |||
if (!items.length) { | |||
setTimeout(initNpcPageItemTooltips, 500); | |||
return; | |||
} | |||
function normalizeNpcDropName(text) { | |||
return String(text || '') | |||
.replace(/\u2019/g, "'") | |||
.replace(/\s*\(\s*\d+%\s*\)\s*$/g, '') | |||
.replace(/\s+/g, ' ') | |||
.trim() | |||
.toLowerCase(); | |||
} | |||
function normalizeNpcDropRarity(rarity) { | |||
return String(rarity || 'common') | |||
.toLowerCase() | |||
.replace(/\s+/g, '-'); | |||
} | |||
function getNpcDropRarityStyle(rarity) { | |||
rarity = normalizeNpcDropRarity(rarity); | |||
if (rarity === 'uncommon') { | |||
return { color: '#20ff45', shadow: '0 0 6px rgba(32, 255, 69, 0.55)' }; | |||
} | |||
if (rarity === 'rare') { | |||
return { color: '#1e88ff', shadow: '0 0 6px rgba(30, 136, 255, 0.65)' }; | |||
} | |||
if (rarity === 'ultra-rare') { | |||
return { color: '#ff3cff', shadow: '0 0 8px rgba(255, 60, 255, 0.75)' }; | |||
} | |||
if (rarity === 'legendary') { | |||
return { color: '#ff8000', shadow: '0 0 8px rgba(255, 128, 0, 0.85)' }; | |||
} | |||
if (rarity === 'unique') { | |||
return { color: '#ffd700', shadow: '0 0 8px rgba(255, 215, 0, 0.85)' }; | |||
} | |||
return { color: '#ffffff', shadow: '0 0 6px rgba(255, 255, 255, 0.45)' }; | |||
} | |||
function getNpcDropItemFromLink(link) { | |||
var names = []; | |||
var href = link.getAttribute('href') || ''; | |||
names.push(link.textContent || ''); | |||
names.push(link.getAttribute('title') || ''); | |||
names.push(link.getAttribute('data-class-item-name') || ''); | |||
try { | |||
if (href.indexOf('title=') !== -1) { | |||
names.push(decodeURIComponent(href.split('title=')[1].split('&')[0]).replace(/_/g, ' ')); | |||
} else { | |||
names.push(decodeURIComponent(href.substring(href.lastIndexOf('/') + 1)).replace(/_/g, ' ')); | |||
} | |||
} catch (e) {} | |||
names = names.map(normalizeNpcDropName).filter(Boolean); | |||
return items.find(function (item) { | |||
var itemName = normalizeNpcDropName(item.name); | |||
return names.indexOf(itemName) !== -1; | |||
}); | |||
} | |||
function applyNpcDropRarityStyle(link, item) { | |||
var rarity = normalizeNpcDropRarity(item.rarity); | |||
var style = getNpcDropRarityStyle(rarity); | |||
var textSpan; | |||
[ | |||
'class-item-rarity-common', | |||
'class-item-rarity-uncommon', | |||
'class-item-rarity-rare', | |||
'class-item-rarity-ultra-rare', | |||
'class-item-rarity-legendary', | |||
'class-item-rarity-unique' | |||
].forEach(function (className) { | |||
link.classList.remove(className); | |||
}); | |||
link.classList.add('class-item-tooltip-link'); | |||
link.classList.add('class-item-rarity-' + rarity); | |||
link.setAttribute('data-class-item-name', encodeURIComponent(item.name)); | |||
/* | |||
* Remove browser-native hover tooltip. | |||
* The CB-style tooltip handles item display instead. | |||
*/ | |||
link.removeAttribute('title'); | |||
textSpan = link.querySelector('.npc-drop-rarity-text'); | |||
if (!textSpan) { | |||
textSpan = document.createElement('span'); | |||
textSpan.className = 'npc-drop-rarity-text'; | |||
textSpan.textContent = link.textContent; | |||
link.textContent = ''; | |||
link.appendChild(textSpan); | |||
} | |||
textSpan.textContent = item.name; | |||
link.style.setProperty('font-weight', '800', 'important'); | |||
link.style.setProperty('text-decoration-color', style.color, 'important'); | |||
textSpan.style.setProperty('color', style.color, 'important'); | |||
textSpan.style.setProperty('-webkit-text-fill-color', style.color, 'important'); | |||
textSpan.style.setProperty('font-weight', '800', 'important'); | |||
textSpan.style.setProperty('text-shadow', style.shadow, 'important'); | |||
textSpan.style.setProperty('text-decoration-color', style.color, 'important'); | |||
} | |||
links.forEach(function (link) { | |||
var item = getNpcDropItemFromLink(link); | |||
if (!item) return; | |||
applyNpcDropRarityStyle(link, item); | |||
if (link.getAttribute('data-npc-item-tooltip-bound') === '1') return; | |||
link.setAttribute('data-npc-item-tooltip-bound', '1'); | |||
link.addEventListener('mouseenter', function (event) { | |||
applyNpcDropRarityStyle(link, item); | |||
if (typeof showClassItemTooltip === 'function') { | |||
showClassItemTooltip(item, event); | |||
} | |||
}); | |||
link.addEventListener('mousemove', function (event) { | |||
applyNpcDropRarityStyle(link, item); | |||
if (typeof moveClassItemTooltip === 'function') { | |||
moveClassItemTooltip(event); | |||
} | |||
}); | |||
link.addEventListener('mouseleave', function () { | |||
applyNpcDropRarityStyle(link, item); | |||
if (typeof hideClassItemTooltip === 'function') { | |||
hideClassItemTooltip(); | |||
} | |||
}); | |||
}); | |||
} | |||
mw.hook('wikipage.content').add(initNpcPageItemTooltips); | |||
document.addEventListener('DOMContentLoaded', initNpcPageItemTooltips); | |||
setTimeout(initNpcPageItemTooltips, 500); | |||
setTimeout(initNpcPageItemTooltips, 1500); | |||
setTimeout(initNpcPageItemTooltips, 3000); | |||
setTimeout(initNpcPageItemTooltips, 5000); | |||
mw.hook('wikipage.content').add(initQuestRewardItemTooltips); | |||
document.addEventListener('DOMContentLoaded', initQuestRewardItemTooltips); | |||
setTimeout(initQuestRewardItemTooltips, 500); | |||
setTimeout(initQuestRewardItemTooltips, 1500); | |||
setTimeout(initQuestRewardItemTooltips, 3000); | |||
mw.hook('wikipage.content').add(initQuestRewardItemTooltips); | |||
document.addEventListener('DOMContentLoaded', initQuestRewardItemTooltips); | |||
setTimeout(initQuestRewardItemTooltips, 500); | |||
setTimeout(initQuestRewardItemTooltips, 1500); | |||
mw.hook('wikipage.content').add(initEqoaCategoryPageLists); | |||
document.addEventListener('DOMContentLoaded', initEqoaCategoryPageLists); | |||
setTimeout(initEqoaCategoryPageLists, 500); | |||
mw.hook('wikipage.content').add(initZoneNpcListFilters); | |||
document.addEventListener('DOMContentLoaded', initZoneNpcListFilters); | |||
setTimeout(initZoneNpcListFilters, 500); | |||
mw.hook('wikipage.content').add(initEqoaSpellPathLists); | |||
document.addEventListener('DOMContentLoaded', initEqoaSpellPathLists); | |||
setTimeout(initEqoaSpellPathLists, 500); | |||
setTimeout(initEqoaSpellPathLists, 1500); | |||
setTimeout(initEqoaSpellPathLists, 3000); | |||
function hidePageActionButtonsForNonContributors() { | |||
var username = mw.config.get('wgUserName') || ''; | |||
var groups = mw.config.get('wgUserGroups') || []; | |||
var canSeePageActions = | |||
username === 'DevRenodor' || | |||
groups.indexOf('contributor') !== -1 || | |||
groups.indexOf('administrator') !== -1 || | |||
groups.indexOf('bureaucrat') !== -1 || | |||
groups.indexOf('interface-admin') !== -1; | |||
var actionIds = [ | |||
'ca-view', | |||
'ca-edit', | |||
'ca-ve-edit', | |||
'ca-viewsource', | |||
'ca-history' | |||
]; | |||
actionIds.forEach(function (id) { | |||
var action = document.getElementById(id); | |||
if (!action) return; | |||
action.style.setProperty('display', canSeePageActions ? '' : 'none', 'important'); | |||
}); | |||
} | |||
document.addEventListener('DOMContentLoaded', hidePageActionButtonsForNonContributors); | |||
mw.hook('wikipage.content').add(hidePageActionButtonsForNonContributors); | |||
setTimeout(hidePageActionButtonsForNonContributors, 250); | |||
setTimeout(hidePageActionButtonsForNonContributors, 1000); | |||
function initSandstormChangesHeaderCollapse() { | |||
var page = document.querySelector('.eqoa-changes-page'); | |||
if (!page) return; | |||
Array.prototype.forEach.call( | |||
page.querySelectorAll('.eqoa-changes-section.mw-collapsible'), | |||
function (section) { | |||
var header = section.querySelector('.eqoa-header'); | |||
var defaultToggle = section.querySelector('.mw-collapsible-toggle'); | |||
var plus; | |||
if (!header) return; | |||
if (section.classList.contains('eqoa-change-header-collapse-ready')) return; | |||
plus = document.createElement('span'); | |||
plus.className = 'eqoa-change-header-plus'; | |||
plus.textContent = section.classList.contains('mw-collapsed') ? '+' : '-'; | |||
header.appendChild(plus); | |||
function updatePlus() { | |||
plus.textContent = section.classList.contains('mw-collapsed') ? '+' : '-'; | |||
} | |||
header.addEventListener('click', function (event) { | |||
if (event.target.closest('a')) return; | |||
defaultToggle = section.querySelector('.mw-collapsible-toggle'); | |||
if (defaultToggle) { | |||
defaultToggle.click(); | |||
setTimeout(updatePlus, 50); | |||
return; | |||
} | |||
section.classList.toggle('mw-collapsed'); | |||
updatePlus(); | |||
}); | |||
new MutationObserver(updatePlus).observe(section, { | |||
attributes: true, | |||
attributeFilter: ['class'] | |||
}); | |||
updatePlus(); | |||
section.classList.add('eqoa-change-header-collapse-ready'); | |||
} | |||
); | |||
} | |||
mw.hook('wikipage.content').add(initSandstormChangesHeaderCollapse); | |||
document.addEventListener('DOMContentLoaded', initSandstormChangesHeaderCollapse); | |||
setTimeout(initSandstormChangesHeaderCollapse, 500); | |||
setTimeout(initSandstormChangesHeaderCollapse, 1500); | |||
function initEqoaExpCampToggleIcons(root) { | |||
root = root && root.jquery ? root[0] : (root || document); | |||
root.querySelectorAll('table.exp-camp-table.mw-collapsible caption').forEach(function (caption) { | |||
var table = caption.closest('table.exp-camp-table'); | |||
var toggle = caption.querySelector('.mw-collapsible-toggle'); | |||
if (!table || !toggle) return; | |||
function removeDefaultBrackets() { | |||
[caption, toggle].forEach(function (scope) { | |||
if (!scope) return; | |||
scope.querySelectorAll('.mw-collapsible-bracket').forEach(function (bracket) { | |||
bracket.remove(); | |||
}); | |||
Array.from(scope.childNodes).forEach(function (node) { | |||
if (node.nodeType === 3) { | |||
node.nodeValue = node.nodeValue.replace(/\[|\]/g, ''); | |||
} | |||
if ( | |||
node.nodeType === 1 && | |||
( | |||
node.classList.contains('mw-collapsible-bracket') || | |||
node.textContent.trim() === '[' || | |||
node.textContent.trim() === ']' | |||
) | |||
) { | |||
node.remove(); | |||
} | |||
}); | |||
}); | |||
} | |||
function updateToggleIcon() { | |||
var isCollapsed = | |||
table.classList.contains('mw-collapsed') || | |||
toggle.classList.contains('mw-collapsible-toggle-collapsed') || | |||
toggle.getAttribute('aria-expanded') === 'false'; | |||
removeDefaultBrackets(); | |||
toggle.textContent = isCollapsed ? '+' : '−'; | |||
toggle.title = isCollapsed ? 'Expand' : 'Collapse'; | |||
toggle.setAttribute('aria-label', isCollapsed ? 'Expand' : 'Collapse'); | |||
removeDefaultBrackets(); | |||
} | |||
if (!caption.classList.contains('eqoa-exp-caption-ready')) { | |||
caption.classList.add('eqoa-exp-caption-ready'); | |||
caption.addEventListener('click', function (event) { | |||
if (event.target.closest('.mw-collapsible-toggle')) { | |||
setTimeout(updateToggleIcon, 0); | |||
setTimeout(updateToggleIcon, 100); | |||
return; | |||
} | |||
event.preventDefault(); | |||
toggle.click(); | |||
setTimeout(updateToggleIcon, 0); | |||
setTimeout(updateToggleIcon, 100); | |||
}); | |||
} | |||
updateToggleIcon(); | |||
}); | |||
} | |||
mw.hook('wikipage.content').add(initEqoaExpCampToggleIcons); | |||
document.addEventListener('DOMContentLoaded', function () { | |||
initEqoaExpCampToggleIcons(document); | |||
}); | |||
setTimeout(function () { | |||
initEqoaExpCampToggleIcons(document); | |||
}, 500); | |||
setTimeout(function () { | |||
initEqoaExpCampToggleIcons(document); | |||
}, 1500); | |||
function initEqoaTemplateCopyButtons(root) { | |||
root = root && root.jquery ? root[0] : (root || document); | |||
root.querySelectorAll('.eqoa-template-copy-button').forEach(function (button) { | |||
if (button.classList.contains('eqoa-template-copy-ready')) return; | |||
button.classList.add('eqoa-template-copy-ready'); | |||
button.addEventListener('click', function () { | |||
var card = button.closest('.eqoa-template-card'); | |||
var code = card ? card.querySelector('.eqoa-template-code') : null; | |||
var text = code ? code.textContent.trim() : ''; | |||
if (!text) return; | |||
navigator.clipboard.writeText(text).then(function () { | |||
button.textContent = 'Copied'; | |||
setTimeout(function () { | |||
button.textContent = 'Copy'; | |||
}, 1200); | |||
}); | |||
}); | |||
}); | |||
} | |||
mw.hook('wikipage.content').add(initEqoaTemplateCopyButtons); | |||
document.addEventListener('DOMContentLoaded', function () { | |||
initEqoaTemplateCopyButtons(document); | |||
}); | |||
function initEqoaZoneTabsLocked(root) { | |||
root = root && root.jquery ? root[0] : (root || document); | |||
root.querySelectorAll('.eqoa-zone-tabs').forEach(function (tabs) { | |||
if (tabs.classList.contains('eqoa-zone-tabs-locked-ready')) return; | |||
tabs.classList.add('eqoa-zone-tabs-locked-ready'); | |||
tabs.querySelectorAll('.eqoa-zone-tab-button[data-tab]').forEach(function (button) { | |||
button.addEventListener('click', function () { | |||
var tabName = button.getAttribute('data-tab'); | |||
tabs.querySelectorAll('.eqoa-zone-tab-button[data-tab]').forEach(function (btn) { | |||
btn.classList.toggle('is-active', btn === button); | |||
}); | |||
tabs.querySelectorAll('.eqoa-zone-tab-panel[data-tab-panel]').forEach(function (panel) { | |||
panel.classList.toggle( | |||
'is-active', | |||
panel.getAttribute('data-tab-panel') === tabName | |||
); | |||
}); | |||
}); | |||
}); | |||
}); | |||
} | |||
mw.hook('wikipage.content').add(initEqoaZoneTabsLocked); | |||
document.addEventListener('DOMContentLoaded', function () { | |||
initEqoaZoneTabsLocked(document); | |||
}); | |||
setTimeout(function () { | |||
initEqoaZoneTabsLocked(document); | |||
}, 500); | |||
/* ===== Zone NPC Tab Auto-Fill From Category ===== */ | |||
(function () { | |||
function normalizeEqoaText(value) { | |||
return String(value || '') | |||
.replace(/\s+/g, ' ') | |||
.trim() | |||
.toLowerCase(); | |||
} | |||
function getCurrentZoneName() { | |||
return mw.config.get('wgTitle') || mw.config.get('wgPageName') || ''; | |||
} | |||
function getNpcFamilyFromDoc(doc) { | |||
var rows = doc.querySelectorAll('.eqoa-npc-info-table tr'); | |||
for (var i = 0; i < rows.length; i++) { | |||
var cells = rows[i].querySelectorAll('td'); | |||
var label; | |||
if (cells.length < 2) continue; | |||
label = normalizeEqoaText(cells[0].textContent); | |||
if (label.indexOf('family') !== -1) { | |||
return cells[1].innerHTML.trim(); | |||
} | |||
} | |||
return ''; | |||
} | |||
function getNpcZoneRowsFromDoc(doc, zoneName) { | |||
var wantedZone = normalizeEqoaText(zoneName); | |||
var rows = doc.querySelectorAll('.eqoa-npc-zone-table tr'); | |||
var matches = []; | |||
for (var i = 0; i < rows.length; i++) { | |||
var cells = rows[i].querySelectorAll('td'); | |||
if (cells.length < 7) continue; | |||
if (normalizeEqoaText(cells[0].textContent) === wantedZone) { | |||
matches.push({ | |||
level: cells[1].innerHTML.trim(), | |||
drops: cells[3].innerHTML.trim(), | |||
quest: cells[5].innerHTML.trim(), | |||
notes: cells[6].innerHTML.trim() | |||
}); | |||
} | |||
} | |||
return matches; | |||
} | |||
function buildNpcLink(pageTitle) { | |||
var link = document.createElement('a'); | |||
link.href = mw.util.getUrl(pageTitle); | |||
link.textContent = pageTitle; | |||
return link; | |||
} | |||
function loadZoneNpcPage(api, pageTitle, zoneName) { | |||
return api.get({ | |||
action: 'parse', | |||
page: pageTitle, | |||
prop: 'text', | |||
format: 'json' | |||
}).then(function (data) { | |||
var html = data && data.parse && data.parse.text && data.parse.text['*']; | |||
var doc; | |||
var zoneRows; | |||
var family; | |||
if (!html) return []; | |||
doc = new DOMParser().parseFromString(html, 'text/html'); | |||
zoneRows = getNpcZoneRowsFromDoc(doc, zoneName); | |||
family = getNpcFamilyFromDoc(doc); | |||
if (!zoneRows.length) { | |||
return [{ | |||
name: pageTitle, | |||
level: '', | |||
drops: '', | |||
family: family || '', | |||
notes: '' | |||
}]; | |||
} | |||
return zoneRows.map(function (zoneRow) { | |||
return { | |||
name: pageTitle, | |||
level: zoneRow.level || '', | |||
drops: zoneRow.drops || '', | |||
family: family || '', | |||
notes: zoneRow.notes || '' | |||
}; | |||
}); | |||
}); | |||
} | |||
function renderZoneNpcRows(table, npcs) { | |||
var tbody = table.querySelector('tbody'); | |||
if (!tbody) { | |||
tbody = document.createElement('tbody'); | |||
table.appendChild(tbody); | |||
} | |||
tbody.innerHTML = ''; | |||
if (!npcs.length) { | |||
tbody.innerHTML = | |||
'<tr class="zone-npc-row">' + | |||
'<td colspan="5">No NPCs found.</td>' + | |||
'</tr>'; | |||
return; | |||
} | |||
npcs.forEach(function (npc) { | |||
var row = document.createElement('tr'); | |||
var nameCell = document.createElement('td'); | |||
row.className = 'zone-npc-row'; | |||
nameCell.className = 'zone-monster-name'; | |||
nameCell.appendChild(buildNpcLink(npc.name)); | |||
row.appendChild(nameCell); | |||
row.innerHTML += | |||
'<td class="zone-monster-level">' + (npc.level || '') + '</td>' + | |||
'<td class="zone-monster-drops">' + (npc.drops || '') + '</td>' + | |||
'<td class="zone-monster-family">' + (npc.family || '') + '</td>' + | |||
'<td class="zone-monster-notes">' + (npc.notes || '') + '</td>'; | |||
tbody.appendChild(row); | |||
}); | |||
} | |||
function initZoneNpcCategoryAutoFill(root) { | |||
var zoneName = getCurrentZoneName(); | |||
var categoryName = zoneName + ' NPCs'; | |||
var panel; | |||
var table; | |||
var api; | |||
root = root && root.jquery ? root[0] : (root || document); | |||
panel = root.querySelector('[data-tab-panel="npcs"]'); | |||
if (!panel || panel.classList.contains('eqoa-zone-npc-auto-ready')) return; | |||
table = panel.querySelector('.zone-monster-table'); | |||
if (!table) return; | |||
panel.classList.add('eqoa-zone-npc-auto-ready'); | |||
api = new mw.Api(); | |||
api.get({ | |||
action: 'query', | |||
list: 'categorymembers', | |||
cmtitle: 'Category:' + categoryName, | |||
cmnamespace: 0, | |||
cmlimit: 500, | |||
format: 'json' | |||
}).then(function (data) { | |||
var members = data && | |||
data.query && | |||
data.query.categorymembers | |||
? data.query.categorymembers | |||
: []; | |||
return Promise.all( | |||
members.map(function (member) { | |||
return loadZoneNpcPage(api, member.title, zoneName); | |||
}) | |||
); | |||
}).then(function (rows) { | |||
rows = rows.reduce(function (allRows, npcRows) { | |||
return allRows.concat(npcRows || []); | |||
}, []); | |||
rows = rows.filter(Boolean).sort(function (a, b) { | |||
var nameCompare = a.name.localeCompare(b.name); | |||
if (nameCompare !== 0) return nameCompare; | |||
return String(a.level || '').localeCompare(String(b.level || '')); | |||
}); | |||
renderZoneNpcRows(table, rows); | |||
if (typeof initGeneratedItemTooltipsForWiki === 'function') { | |||
initGeneratedItemTooltipsForWiki(panel); | |||
} | |||
if (typeof initZoneNpcSearch === 'function') { | |||
initZoneNpcSearch(panel); | |||
} | |||
if (window.jQuery && jQuery.fn && jQuery.fn.tablesorter) { | |||
jQuery(table).trigger('destroy'); | |||
jQuery(table).tablesorter(); | |||
} | |||
}).catch(function (error) { | |||
console.error('EQOA zone NPC auto-fill failed:', error); | |||
}); | |||
} | |||
mw.hook('wikipage.content').add(initZoneNpcCategoryAutoFill); | |||
document.addEventListener('DOMContentLoaded', function () { | |||
initZoneNpcCategoryAutoFill(document); | |||
}); | |||
setTimeout(function () { | |||
initZoneNpcCategoryAutoFill(document); | |||
}, 500); | |||
setTimeout(function () { | |||
initZoneNpcCategoryAutoFill(document); | |||
}, 1500); | |||
})(); | |||
Latest revision as of 02:50, 16 July 2026
mw.loader.load(
mw.config.get('wgScript') +
'?title=MediaWiki:AchievementTracker.js' +
'&action=raw' +
'&ctype=text/javascript' +
'&v=20260716-1'
);
/* 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 sortSelect;
var sortButton;
var clearButton;
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-left">' +
'<input type="search" id="filter-item-search" placeholder="Search item">' +
'</div>' +
'<div class="items-filter-right">' +
'<select id="items-found-here-sort-select">' +
'<option value="name">Sort: Name</option>' +
'<option value="level">Sort: Level</option>' +
'<option value="class">Sort: Class</option>' +
'<option value="dmg">Sort: DMG</option>' +
'<option value="ac">Sort: AC</option>' +
'<option value="str">Sort: STR</option>' +
'<option value="sta">Sort: STA</option>' +
'<option value="agi">Sort: AGI</option>' +
'<option value="wis">Sort: WIS</option>' +
'<option value="dex">Sort: DEX</option>' +
'<option value="cha">Sort: CHA</option>' +
'<option value="int">Sort: INT</option>' +
'<option value="hp">Sort: HP</option>' +
'<option value="pow">Sort: POW</option>' +
'<option value="pr">Sort: PR</option>' +
'<option value="dr">Sort: DR</option>' +
'<option value="fr">Sort: FR</option>' +
'<option value="cr">Sort: CR</option>' +
'<option value="lr">Sort: LR</option>' +
'<option value="ar">Sort: AR</option>' +
'<option value="dur">Sort: DUR</option>' +
'</select>' +
'<button id="items-found-here-sort-button" type="button">Sort</button>' +
'<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');
sortSelect = document.getElementById('items-found-here-sort-select');
sortButton = document.getElementById('items-found-here-sort-button');
clearButton = document.getElementById('clear-item-filters');
box.style.setProperty('display', 'grid', 'important');
box.style.setProperty('grid-template-columns', '1fr auto', 'important');
box.style.setProperty('align-items', 'center', 'important');
box.style.setProperty('gap', '12px', 'important');
box.querySelector('.items-filter-left').style.setProperty('display', 'flex', 'important');
box.querySelector('.items-filter-left').style.setProperty('justify-content', 'flex-start', 'important');
box.querySelector('.items-filter-right').style.setProperty('display', 'flex', 'important');
box.querySelector('.items-filter-right').style.setProperty('justify-content', 'flex-end', 'important');
box.querySelector('.items-filter-right').style.setProperty('gap', '8px', 'important');
[searchInput, classSelect, sortSelect, sortButton, clearButton].forEach(function (control) {
if (!control) return;
control.style.setProperty('height', '34px', 'important');
control.style.setProperty('box-sizing', 'border-box', 'important');
control.style.setProperty('margin', '0', 'important');
control.style.setProperty('vertical-align', 'middle', 'important');
});
searchInput.style.setProperty('width', '660px', 'important');
searchInput.style.setProperty('max-width', '100%', 'important');
sortSelect.style.setProperty('width', '128px', 'important');
classSelect.style.setProperty('width', '128px', 'important');
sortButton.style.setProperty('width', '58px', 'important');
clearButton.style.setProperty('width', '64px', 'important');
function getCellText(row, index) {
return row.cells[index] ? row.cells[index].textContent.trim() : '';
}
function getRowClassText(row) {
return (
row.getAttribute('data-class') ||
getCellText(row, 2) ||
''
).toUpperCase();
}
function classMatches(row, selectedClass) {
var classText = getRowClassText(row);
if (!selectedClass) return true;
if (classText === 'ALL') return true;
if (classText.indexOf('ALL') !== -1) return true;
return classText.split(/[\s,\/]+/).indexOf(selectedClass) !== -1;
}
function filterTable() {
var query = String(searchInput.value || '').toLowerCase();
var selectedClass = classSelect.value;
var rows = table.querySelectorAll('tr.items-found-here-row');
Array.prototype.forEach.call(rows, function (row) {
var rowText = [
row.textContent,
row.getAttribute('data-name'),
row.getAttribute('data-level'),
row.getAttribute('data-class'),
row.getAttribute('data-slot'),
row.getAttribute('data-rarity')
].join(' ').toLowerCase();
var show = true;
if (query && rowText.indexOf(query) === -1) show = false;
if (!classMatches(row, selectedClass)) show = false;
row.style.display = show ? '' : 'none';
});
}
function getSortValue(row, key) {
var value = row.getAttribute('data-' + key) || '';
if (!value) {
if (key === 'name') value = getCellText(row, 0);
if (key === 'level') value = getCellText(row, 1);
if (key === 'class') value = getCellText(row, 2);
}
return String(value || '');
}
function sortTable() {
var tbody = table.querySelector('tbody') || table;
var rows = Array.prototype.slice.call(table.querySelectorAll('tr.items-found-here-row'));
var key = sortSelect.value;
rows.sort(function (a, b) {
var aValue = getSortValue(a, key);
var bValue = getSortValue(b, key);
var aNumber = parseFloat(aValue.replace(/[^\d.-]/g, ''));
var bNumber = parseFloat(bValue.replace(/[^\d.-]/g, ''));
if (!isNaN(aNumber) || !isNaN(bNumber)) {
return (bNumber || 0) - (aNumber || 0);
}
return aValue.localeCompare(bValue);
});
rows.forEach(function (row) {
tbody.appendChild(row);
});
filterTable();
}
searchInput.addEventListener('input', filterTable);
classSelect.addEventListener('change', filterTable);
sortButton.addEventListener('click', sortTable);
clearButton.addEventListener('click', function () {
searchInput.value = '';
classSelect.value = '';
sortSelect.value = 'name';
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 initEqoaGeneratedItemLinks($content) {
var root = $content && $content[0] ? $content[0] : document;
var links = root.querySelectorAll(
'#items-found-here-table .items-found-here-name a:not([data-npc-item-tooltip-bound="1"]):not([data-quest-reward-tooltip-bound="1"]), ' +
'#items-found-here-table td:first-child a:not([data-npc-item-tooltip-bound="1"]):not([data-quest-reward-tooltip-bound="1"]), ' +
'.zone-monster-table .zone-monster-drops a:not([data-npc-item-tooltip-bound="1"]):not([data-quest-reward-tooltip-bound="1"]), ' +
'.zone-monster-table td:nth-child(3) a:not([data-npc-item-tooltip-bound="1"]):not([data-quest-reward-tooltip-bound="1"]), ' +
'.eqoa-related-items a:not([data-npc-item-tooltip-bound="1"]):not([data-quest-reward-tooltip-bound="1"])'
);
var items = window.EQOA_GENERATED_ITEMS || [];
var lookup = {};
if (!links.length) return;
if (!items.length) {
setTimeout(function () {
initEqoaGeneratedItemLinks($content);
}, 250);
return;
}
function normalizeName(value) {
return String(value || '')
.replace(/\u00a0/g, ' ')
.replace(/[’`]/g, "'")
.replace(/\s+/g, ' ')
.trim()
.toLowerCase();
}
function looseName(value) {
return normalizeName(value).replace(/[^a-z0-9]/g, '');
}
function stripExtraText(value) {
return String(value || '')
.replace(/\(\d+%\)/g, '')
.replace(/\([^)]*\)$/g, '')
.replace(/\s+/g, ' ')
.trim();
}
function rarityRank(item) {
var rarity = normalizeClassItemRarity(item && item.rarity);
if (rarity === 'ultra-rare') return 4;
if (rarity === 'rare') return 3;
if (rarity === 'uncommon') return 2;
return 1;
}
function storeItem(key, item) {
var current;
if (!key) return;
current = lookup[key];
if (!current || rarityRank(item) > rarityRank(current)) {
lookup[key] = item;
}
}
function getHrefTitle(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 getCandidates(link) {
var row = link.closest('tr');
var names = [
link.getAttribute('data-eqoa-item-name'),
row && row.getAttribute('data-eqoa-item-name'),
link.textContent,
stripExtraText(link.textContent),
getHrefTitle(link),
stripExtraText(getHrefTitle(link))
];
return names.filter(function (name) {
return name && String(name).trim();
});
}
function findItem(link) {
var candidates = getCandidates(link);
var i;
var name;
var item;
for (i = 0; i < candidates.length; i++) {
name = candidates[i];
item = lookup[normalizeName(name)] || lookup[looseName(name)];
if (item) return item;
}
return null;
}
function normalizeGeneratedItem(item) {
return {
name: item.name || '',
rarity: item.rarity || 'common',
slot: item.slot || '',
level: item.level || item.lvl || '',
race: item.race || '',
className: Array.isArray(item.class) ? item.class.join(', ') : (item.class || item.classes || item.className || ''),
notrade: item.notrade || item.noTrade || item.trade === 'NO TRADE',
lore: item.lore || '',
dmg: item.dmg || item.damage || '',
ac: item.ac || '',
str: item.str || '',
sta: item.sta || '',
agi: item.agi || '',
dex: item.dex || '',
wis: item.wis || '',
int: item.int || '',
cha: item.cha || '',
hp: item.hp || '',
hot: item.hot || '',
pow: item.pow || '',
pot: item.pot || '',
pr: item.pr || '',
dr: item.dr || '',
fr: item.fr || '',
cr: item.cr || '',
lr: item.lr || '',
ar: item.ar || '',
dur: item.dur || '',
gearhp: item.gearhp || item.gearHp || item.gear_hp || '',
effect: item.effect || '',
proc: item.proc || '',
source: item.source || ''
};
}
items.forEach(function (item) {
if (!item || !item.name) return;
storeItem(normalizeName(item.name), item);
storeItem(looseName(item.name), item);
});
Array.prototype.forEach.call(links, function (link) {
var item = findItem(link);
var tooltipItem;
var rarity;
if (!item) return;
tooltipItem = normalizeGeneratedItem(item);
rarity = normalizeClassItemRarity(tooltipItem.rarity);
link.classList.remove(
'class-item-rarity-common',
'class-item-rarity-uncommon',
'class-item-rarity-rare',
'class-item-rarity-ultra-rare',
'eqoa-related-tooltip-ready',
'items-found-tooltip-ready'
);
link.classList.add('class-item-tooltip-link');
link.classList.add('class-item-rarity-' + rarity);
link.classList.add('cb-rarity-' + rarity);
applyClassItemRarityStyle(link, rarity);
if (link._eqoaGeneratedTooltipBound) return;
link._eqoaGeneratedTooltipBound = true;
link.addEventListener('mouseenter', function (event) {
showClassItemTooltip(tooltipItem, event);
});
link.addEventListener('mousemove', moveClassItemTooltip);
link.addEventListener('mouseleave', function () {
hideClassItemTooltip();
});
});
}
function initItemsFoundHereRarityTooltips() {
initEqoaGeneratedItemLinks();
}
function initRelatedItemTooltips($content) {
initEqoaGeneratedItemLinks($content);
}
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.width = '100%';
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);
mapBox._leaflet_image_layer = imageLayer;
function sizeEqoaMobileMapBox() {
var parent;
var boxWidth;
var boxHeight;
if (!window.matchMedia || !window.matchMedia('(max-width: 700px)').matches) {
return;
}
parent = mapBox.closest('.zone-interactive-map-content');
if (parent) {
parent.style.setProperty('padding', '0', 'important');
parent.style.setProperty('height', 'auto', 'important');
parent.style.setProperty('min-height', '0', 'important');
parent.style.setProperty('background', 'transparent', 'important');
}
mapBox.style.setProperty('display', 'block', 'important');
mapBox.style.setProperty('width', '100%', 'important');
mapBox.style.setProperty('max-width', '650px', 'important');
mapBox.style.setProperty('margin', '0 auto', 'important');
mapBox.style.setProperty('padding', '0', 'important');
mapBox.style.setProperty('aspect-ratio', width + ' / ' + height, 'important');
mapBox.style.setProperty('overflow', 'hidden', 'important');
mapBox.style.setProperty('background', 'transparent', 'important');
boxWidth = mapBox.clientWidth || mapBox.getBoundingClientRect().width;
if (!boxWidth) {
return;
}
boxHeight = Math.round(boxWidth * height / width);
mapBox.style.setProperty('height', boxHeight + 'px', 'important');
mapBox.style.setProperty('min-height', boxHeight + 'px', 'important');
mapBox.style.setProperty('max-height', boxHeight + 'px', 'important');
}
function repairEqoaMobileMapImage() {
var imageElement;
var northWest;
var southEast;
var imageWidth;
var imageHeight;
if (!window.matchMedia || !window.matchMedia('(max-width: 700px)').matches) {
return;
}
imageLayer.setBounds(bounds);
imageElement = imageLayer.getElement && imageLayer.getElement();
if (!imageElement) {
return;
}
northWest = map.latLngToLayerPoint([height, 0]);
southEast = map.latLngToLayerPoint([0, width]);
imageWidth = Math.ceil(Math.abs(southEast.x - northWest.x));
imageHeight = Math.ceil(Math.abs(southEast.y - northWest.y));
imageElement.style.setProperty('display', 'block', 'important');
imageElement.style.setProperty('visibility', 'visible', 'important');
imageElement.style.setProperty('opacity', '1', 'important');
imageElement.style.setProperty('width', imageWidth + 'px', 'important');
imageElement.style.setProperty('height', imageHeight + 'px', 'important');
imageElement.style.setProperty('max-width', 'none', 'important');
imageElement.style.setProperty('max-height', 'none', 'important');
}
function getMapCenter() {
return [height / 2, width / 2];
}
function fitEqoaMapToBox() {
var fitZoom;
var isMobileMap =
window.matchMedia &&
window.matchMedia('(max-width: 700px)').matches;
sizeEqoaMobileMapBox();
if (isMobileMap) {
map.options.zoomSnap = 0;
}
map.invalidateSize();
fitZoom = map.getBoundsZoom(bounds, false);
map.setMinZoom(fitZoom);
map.setMaxZoom(fitZoom + 3);
map.setMaxBounds(bounds);
map.fitBounds(bounds, {
animate: false,
padding: [0, 0]
});
repairEqoaMobileMapImage();
updateMapDragging();
}
function updateMapDragging() {
var minZoom = map.getMinZoom();
if (map.getZoom() <= minZoom + 0.001) {
sizeEqoaMobileMapBox();
map.invalidateSize();
map.fitBounds(bounds, {
animate: false,
padding: [0, 0]
});
repairEqoaMobileMapImage();
map.dragging.disable();
} else {
repairEqoaMobileMapImage();
map.dragging.enable();
}
}
imageLayer.on('load', function () {
sizeEqoaMobileMapBox();
fitEqoaMapToBox();
});
setTimeout(function () {
fitEqoaMapToBox();
repairEqoaMobileMapImage();
}, 50);
setTimeout(function () {
fitEqoaMapToBox();
repairEqoaMobileMapImage();
}, 250);
setTimeout(function () {
fitEqoaMapToBox();
repairEqoaMobileMapImage();
}, 750);
map.on('zoomend', updateMapDragging);
window.addEventListener('resize', function () {
setTimeout(function () {
fitEqoaMapToBox();
repairEqoaMobileMapImage();
}, 100);
setTimeout(function () {
fitEqoaMapToBox();
repairEqoaMobileMapImage();
}, 300);
});
window.addEventListener('orientationchange', function () {
setTimeout(fitEqoaMapToBox, 250);
setTimeout(fitEqoaMapToBox, 600);
});
function getEqoaMapTooltipDirection(x, y) {
var edgePadding = 120;
if (y < edgePadding) return 'bottom';
if (y > height - edgePadding) return 'top';
if (x < edgePadding) return 'right';
if (x > width - edgePadding) return 'left';
return 'top';
}
function getEqoaMapTooltipOffset(direction) {
if (direction === 'bottom') return [0, 18];
if (direction === 'top') return [0, -18];
if (direction === 'left') return [-18, 0];
if (direction === 'right') return [18, 0];
return [0, -18];
}
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;
tooltipDirection = getEqoaMapTooltipDirection(x, y);
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,
autoPan: true,
keepInView: true,
autoPanPaddingTopLeft: [24, 80],
autoPanPaddingBottomRight: [24, 24],
offset: tooltipDirection === 'bottom' ? [0, 22] : [0, -8],
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: tooltipDirection,
sticky: false,
offset: getEqoaMapTooltipOffset(tooltipDirection),
opacity: 1
});
} else {
marker.bindTooltip(label + '<br><span style="color:#ffd45a;">Click to open page</span>', {
direction: tooltipDirection,
sticky: false,
offset: getEqoaMapTooltipOffset(tooltipDirection),
opacity: 1
});
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' },
{ id: 'items', label: 'Drops' },
{ id: 'achievements', label: 'Achievements' }
];
var headings = [];
var activators = {};
var initialHash = window.location.hash ? window.location.hash.replace('#', '') : '';
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);
panel.id = 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(pushHash) {
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');
if (pushHash && window.history && window.history.pushState) {
window.history.pushState(null, '', '#' + section.id);
}
wrapper.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
activators[section.id] = activateTab;
button.addEventListener('click', function () {
activateTab(true);
});
document.querySelectorAll('a[href="#' + section.id + '"]').forEach(function (link) {
link.addEventListener('click', function (event) {
event.preventDefault();
activateTab(true);
});
});
nav.appendChild(button);
body.appendChild(panel);
});
if (initialHash && activators[initialHash]) {
activators[initialHash](false);
}
}
mw.hook('wikipage.content').add(initEqoaZoneTabs);
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initEqoaZoneTabs);
} else {
initEqoaZoneTabs();
}
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 + ' cb-rarity-' + rarity + '" style="' + getClassItemRarityInlineStyle(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) {
rarity = String(rarity || 'common')
.toLowerCase()
.replace(/\s+/g, '-')
.trim();
if (rarity === 'ultrarare') return 'ultra-rare';
if (rarity === 'ultra-rare') return 'ultra-rare';
if (rarity === 'legendary') return 'legendary';
if (rarity === 'unique') return 'unique';
if (rarity === 'rare') return 'rare';
if (rarity === 'uncommon') return 'uncommon';
if (rarity === 'common') return 'common';
return 'common';
}
function getClassItemRarityInlineStyle(rarity) {
rarity = normalizeClassItemRarity(rarity);
if (rarity === 'unique') {
return 'color:#ffd700!important;text-shadow:0 0 8px rgba(255,215,0,0.85)!important;';
}
if (rarity === 'legendary') {
return 'color:#ff8000!important;text-shadow:0 0 8px rgba(255,128,0,0.85)!important;';
}
if (rarity === 'ultra-rare') {
return 'color:#ff33ff!important;text-shadow:0 0 8px rgba(255,51,255,0.85)!important;';
}
if (rarity === 'rare') {
return 'color:#1e90ff!important;text-shadow:0 0 8px rgba(30,144,255,0.85)!important;';
}
if (rarity === 'uncommon') {
return 'color:#00ff55!important;text-shadow:0 0 8px rgba(0,255,85,0.85)!important;';
}
return 'color:#ffffff!important;text-shadow:0 0 6px rgba(255,255,255,0.75)!important;';
}
function applyClassItemRarityStyle(element, rarity) {
rarity = normalizeClassItemRarity(rarity);
if (!element) return;
if (rarity === 'unique') {
element.style.setProperty('color', '#ffd700', 'important');
element.style.setProperty('text-shadow', '0 0 8px rgba(255,215,0,0.85)', 'important');
return;
}
if (rarity === 'legendary') {
element.style.setProperty('color', '#ff8000', 'important');
element.style.setProperty('text-shadow', '0 0 8px rgba(255,128,0,0.85)', 'important');
return;
}
if (rarity === 'ultra-rare') {
element.style.setProperty('color', '#ff33ff', 'important');
element.style.setProperty('text-shadow', '0 0 8px rgba(255,51,255,0.85)', 'important');
return;
}
if (rarity === 'rare') {
element.style.setProperty('color', '#1e90ff', 'important');
element.style.setProperty('text-shadow', '0 0 8px rgba(30,144,255,0.85)', 'important');
return;
}
if (rarity === 'uncommon') {
element.style.setProperty('color', '#00ff55', 'important');
element.style.setProperty('text-shadow', '0 0 8px rgba(0,255,85,0.85)', 'important');
return;
}
element.style.setProperty('color', '#ffffff', 'important');
element.style.setProperty('text-shadow', '0 0 6px rgba(255,255,255,0.75)', 'important');
}
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, ''');
}
function initGeneratedItemTooltipsForWiki($content) {
var root = $content && $content[0] ? $content[0] : document;
var items = window.EQOA_GENERATED_ITEMS || [];
var lookup = {};
var links;
if (!items.length) {
setTimeout(function () {
initGeneratedItemTooltipsForWiki($content);
}, 250);
return;
}
function normalizeItemName(value) {
return String(value || '')
.replace(/\u00a0/g, ' ')
.replace(/[’`]/g, "'")
.replace(/\s+/g, ' ')
.trim()
.toLowerCase();
}
function looseItemName(value) {
return normalizeItemName(value).replace(/[^a-z0-9]/g, '');
}
function stripDropText(value) {
return String(value || '')
.replace(/\(\d+%\)/g, '')
.replace(/\([^)]*\)$/g, '')
.replace(/\s+/g, ' ')
.trim();
}
function rarityRank(item) {
var rarity = normalizeClassItemRarity(item && item.rarity);
if (rarity === 'ultra-rare') return 4;
if (rarity === 'rare') return 3;
if (rarity === 'uncommon') return 2;
return 1;
}
function storeItem(key, item) {
var current;
if (!key) return;
current = lookup[key];
if (!current || rarityRank(item) > rarityRank(current)) {
lookup[key] = item;
}
}
function getItemFromLink(link) {
var text = stripDropText(link.textContent);
var href = link.getAttribute('href') || '';
var title = '';
if (href.indexOf('title=') !== -1) {
title = decodeURIComponent(href.split('title=')[1].split('&')[0]).replace(/_/g, ' ');
} else if (href.indexOf('/index.php?title=') !== -1) {
title = decodeURIComponent(href.split('/index.php?title=')[1].split('&')[0]).replace(/_/g, ' ');
}
return (
lookup[normalizeItemName(text)] ||
lookup[looseItemName(text)] ||
lookup[normalizeItemName(title)] ||
lookup[looseItemName(title)] ||
null
);
}
function normalizeGeneratedItem(item) {
return {
name: item.name || '',
rarity: item.rarity || 'common',
slot: item.slot || '',
level: item.level || item.lvl || '',
race: item.race || '',
className: Array.isArray(item.class) ? item.class.join(', ') : (item.class || item.classes || item.className || ''),
notrade: item.notrade || item.noTrade || item.trade === 'NO TRADE',
lore: item.lore || '',
dmg: item.dmg || item.damage || '',
ac: item.ac || '',
str: item.str || '',
sta: item.sta || '',
agi: item.agi || '',
dex: item.dex || '',
wis: item.wis || '',
int: item.int || '',
cha: item.cha || '',
hp: item.hp || '',
hot: item.hot || '',
pow: item.pow || '',
pot: item.pot || '',
pr: item.pr || '',
dr: item.dr || '',
fr: item.fr || '',
cr: item.cr || '',
lr: item.lr || '',
ar: item.ar || '',
dur: item.dur || '',
gearhp: item.gearhp || item.gearHp || item.gear_hp || '',
effect: item.effect || '',
proc: item.proc || '',
source: item.source || ''
};
}
items.forEach(function (item) {
if (!item || !item.name) return;
storeItem(normalizeItemName(item.name), item);
storeItem(looseItemName(item.name), item);
});
links = root.querySelectorAll(
'#items-found-here-table td:first-child a, ' +
'#items-found-here-table .items-found-here-name a, ' +
'.zone-monster-table .zone-monster-drops a, ' +
'.zone-monster-table td:nth-child(3) a, ' +
'.eqoa-related-items a'
);
Array.prototype.forEach.call(links, function (link) {
var item = getItemFromLink(link);
var tooltipItem;
var rarity;
if (!item) return;
tooltipItem = normalizeGeneratedItem(item);
rarity = normalizeClassItemRarity(tooltipItem.rarity);
link.classList.remove(
'class-item-rarity-common',
'class-item-rarity-uncommon',
'class-item-rarity-rare',
'class-item-rarity-ultra-rare',
'class-item-rarity-legendary',
'class-item-rarity-unique',
'cb-rarity-common',
'cb-rarity-uncommon',
'cb-rarity-rare',
'cb-rarity-ultra-rare',
'cb-rarity-legendary',
'cb-rarity-unique',
'eqoa-related-tooltip-ready',
'items-found-tooltip-ready'
);
link.classList.add('class-item-tooltip-link');
link.classList.add('class-item-rarity-' + rarity);
link.classList.add('cb-rarity-' + rarity);
applyClassItemRarityStyle(link, rarity);
link.onmouseenter = function (event) {
showClassItemTooltip(tooltipItem, event);
};
link.onmousemove = moveClassItemTooltip;
link.onmouseleave = function () {
hideClassItemTooltip();
};
});
}
mw.hook('wikipage.content').add(initGeneratedItemTooltipsForWiki);
document.addEventListener('DOMContentLoaded', initGeneratedItemTooltipsForWiki);
setTimeout(initGeneratedItemTooltipsForWiki, 500);
setTimeout(initGeneratedItemTooltipsForWiki, 1500);
mw.hook('wikipage.content').add(initClassItemLists);
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initClassItemLists);
} else {
initClassItemLists();
}
setTimeout(initClassItemLists, 500);
setTimeout(initClassItemLists, 1500);
function initItemsFoundHereSortControls(root) {
root = root && root.jquery ? root[0] : (root || document);
var table = root.querySelector('#items-found-here-table');
if (!table) return;
var wrapper = table.closest('.zone-drops-auto-wrapper') || table.parentElement;
if (!wrapper) return;
if (wrapper.querySelector('.zone-drops-controls')) return;
var controls = document.createElement('div');
controls.className = 'zone-drops-controls';
controls.innerHTML =
'<div class="zone-drops-controls-left">' +
'<input class="zone-drops-search" type="search" placeholder="Search item">' +
'</div>' +
'<div class="zone-drops-controls-right">' +
'<select class="zone-drops-sort-select">' +
'<option value="name">Sort: Name</option>' +
'<option value="level">Sort: Level</option>' +
'<option value="class">Sort: Class</option>' +
'<option value="dmg">Sort: DMG</option>' +
'<option value="ac">Sort: AC</option>' +
'<option value="slot">Sort: Slot</option>' +
'<option value="rarity">Sort: Rarity</option>' +
'</select>' +
'<button type="button" class="zone-drops-sort-button">Sort</button>' +
'<select class="zone-drops-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 type="button" class="zone-drops-clear">Clear</button>' +
'</div>';
wrapper.insertBefore(controls, table);
var searchInput = controls.querySelector('.zone-drops-search');
var sortSelect = controls.querySelector('.zone-drops-sort-select');
var sortButton = controls.querySelector('.zone-drops-sort-button');
var classSelect = controls.querySelector('.zone-drops-class-select');
var clearButton = controls.querySelector('.zone-drops-clear');
function getRows() {
return Array.from(table.querySelectorAll('tbody tr'));
}
function getCellText(row, index) {
var cell = row.children[index];
return cell ? cell.textContent.trim() : '';
}
function getSortValue(row, key) {
if (key === 'name') return row.getAttribute('data-name') || getCellText(row, 0);
if (key === 'level') return row.getAttribute('data-level') || getCellText(row, 1);
if (key === 'class') return row.getAttribute('data-class') || getCellText(row, 2);
return row.getAttribute('data-' + key) || '';
}
function numberOrText(value) {
var number = parseFloat(String(value).replace(/[^\d.-]/g, ''));
return isNaN(number) ? String(value).toLowerCase() : number;
}
function applyFilters() {
var query = searchInput.value.trim().toLowerCase();
var classFilter = classSelect.value.trim().toUpperCase();
getRows().forEach(function (row) {
var rowText = row.textContent.toLowerCase();
var rowClass = String(row.getAttribute('data-class') || getCellText(row, 2)).toUpperCase();
var matchesSearch = !query || rowText.indexOf(query) !== -1;
var matchesClass = !classFilter || rowClass.indexOf(classFilter) !== -1;
row.style.display = matchesSearch && matchesClass ? '' : 'none';
});
}
function sortRows() {
var tbody = table.querySelector('tbody');
var key = sortSelect.value;
var rows = getRows();
rows.sort(function (a, b) {
var aValue = numberOrText(getSortValue(a, key));
var bValue = numberOrText(getSortValue(b, key));
if (typeof aValue === 'number' && typeof bValue === 'number') {
return aValue - bValue;
}
return String(aValue).localeCompare(String(bValue));
});
rows.forEach(function (row) {
tbody.appendChild(row);
});
applyFilters();
}
searchInput.addEventListener('input', applyFilters);
classSelect.addEventListener('change', applyFilters);
sortButton.addEventListener('click', sortRows);
clearButton.addEventListener('click', function () {
searchInput.value = '';
classSelect.value = '';
sortSelect.value = 'name';
applyFilters();
});
}
function initEqoaCategoryLists() {
document.querySelectorAll('.eqoa-category-list').forEach(function (box) {
var category = box.getAttribute('data-category');
if (!category || box.classList.contains('eqoa-category-list-ready')) return;
box.classList.add('eqoa-category-list-ready');
box.innerHTML = 'Loading...';
new mw.Api().get({
action: 'query',
list: 'categorymembers',
cmtitle: 'Category:' + category,
cmlimit: 500,
format: 'json'
}).then(function (data) {
var pages = data.query.categorymembers || [];
if (!pages.length) {
box.innerHTML = 'No entries found.';
return;
}
pages.sort(function (a, b) {
return a.title.localeCompare(b.title);
});
box.innerHTML =
'<table class="zone-data-table zone-quest-table">' +
'<tr>' +
'<th class="zone-quest-name">Quest</th>' +
'<th class="zone-quest-type">Type</th>' +
'<th class="zone-quest-starter">Starter</th>' +
'<th class="zone-quest-location">Location</th>' +
'</tr>' +
pages.map(function (page) {
return '<tr>' +
'<td class="zone-quest-name"><a href="' + mw.util.getUrl(page.title) + '">' + page.title + '</a></td>' +
'<td class="zone-quest-type"></td>' +
'<td class="zone-quest-starter"></td>' +
'<td class="zone-quest-location"></td>' +
'</tr>';
}).join('') +
'</table>';
});
});
}
mw.hook('wikipage.content').add(initEqoaCategoryLists);
document.addEventListener('DOMContentLoaded', initEqoaCategoryLists);
function initEqoaClassIndex() {
document.querySelectorAll('.eqoa-class-index').forEach(function (root) {
if (root.classList.contains('eqoa-class-index-ready')) return;
var grid = root.querySelector('.eqoa-class-index-grid');
var cards = root.querySelectorAll('.eqoa-class-card');
var toolbar = document.createElement('div');
var filters = document.createElement('div');
var roles;
var activeRole = 'all';
if (!grid) return;
root.classList.add('eqoa-class-index-ready');
toolbar.className = 'eqoa-class-index-toolbar';
filters.className = 'eqoa-class-index-filters';
if (root.classList.contains('eqoa-race-index')) {
roles = [
['all', 'All'],
['good', 'Good'],
['evil', 'Evil'],
['neutral', 'Neutral']
];
} else {
roles = [
['all', 'All'],
['tank', 'Tank'],
['melee', 'Melee'],
['healer', 'Healer'],
['support', 'Support'],
['caster', 'Caster']
];
}
roles.forEach(function (role) {
var button = document.createElement('button');
button.type = 'button';
button.textContent = role[1];
button.setAttribute('data-role-filter', role[0]);
if (role[0] === 'all') {
button.classList.add('is-active');
}
button.addEventListener('click', function () {
activeRole = role[0];
filters.querySelectorAll('button').forEach(function (btn) {
btn.classList.remove('is-active');
});
button.classList.add('is-active');
applyFilters();
});
filters.appendChild(button);
});
toolbar.appendChild(filters);
root.insertBefore(toolbar, grid);
function cardHasRole(card, role) {
if (role === 'all') return true;
return card.classList.contains('eqoa-role-' + role);
}
function applyFilters() {
cards.forEach(function (card) {
card.style.display = cardHasRole(card, activeRole) ? '' : 'none';
});
}
applyFilters();
});
}
mw.loader.load(mw.config.get('wgScript') + '?title=MediaWiki:ClassSpellGeneratedSpells1.js&action=raw&ctype=text/javascript');
mw.loader.load(mw.config.get('wgScript') + '?title=MediaWiki:ClassSpellGeneratedSpells2.js&action=raw&ctype=text/javascript');
mw.loader.load(mw.config.get('wgScript') + '?title=MediaWiki:ClassSpellGeneratedSpells3.js&action=raw&ctype=text/javascript');
mw.loader.load(mw.config.get('wgScript') + '?title=MediaWiki:ClassSpellGeneratedSpells4.js&action=raw&ctype=text/javascript');
mw.loader.load(mw.config.get('wgScript') + '?title=MediaWiki:ClassSpellGeneratedSpells5.js&action=raw&ctype=text/javascript');
mw.loader.load(mw.config.get('wgScript') + '?title=MediaWiki:ClassSpellGroupOverrides.js&action=raw&ctype=text/javascript&v=20260604');
mw.loader.load(mw.config.get('wgScript') + '?title=MediaWiki:ClassSpellGroupOverrides.js&action=raw&ctype=text/javascript');
mw.hook('wikipage.content').add(initEqoaClassIndex);
document.addEventListener('DOMContentLoaded', initEqoaClassIndex);
setTimeout(initEqoaClassIndex, 500);
function initEqoaSpellLists() {
var lists = document.querySelectorAll('[data-eqoa-spell-list]');
var spells = window.EQOA_GENERATED_SPELLS || [];
if (!lists.length) return;
if (!spells.length) {
setTimeout(initEqoaSpellLists, 250);
return;
}
function escapeHtml(value) {
return String(value == null ? '' : value)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
function fileUrl(fileName) {
fileName = String(fileName || '').trim();
if (!fileName) return '';
if (/^https?:\/\//i.test(fileName)) return fileName;
return mw.config.get('wgScript') + '?title=Special:Redirect/file/' + encodeURIComponent(fileName);
}
function pageUrl(title) {
if (window.mw && mw.util && mw.util.getUrl) {
return mw.util.getUrl(title);
}
return '/index.php?title=' + encodeURIComponent(String(title || '').replace(/ /g, '_'));
}
function iconHtml(fileName, className, altText) {
var url = fileUrl(fileName);
if (!url) return '';
return '<img class="' + className + '" src="' + url + '" alt="' + escapeHtml(altText || '') + '">';
}
function classIconFile(classCode) {
return 'ClassIcon' + classCode + '.png';
}
function classIconsHtml(spell) {
return (spell.classes || []).map(function (classCode) {
return iconHtml(classIconFile(classCode), 'eqoa-spell-class-icon', classCode);
}).join('');
}
function spellIconFile(spell) {
var iconName = String(spell.icon || '').trim();
if (!iconName) return '';
iconName = iconName
.replace(/\.[a-z0-9]+$/i, '')
.replace(/[^a-z0-9]+/gi, '');
return 'SpellIcon' + iconName + '.png';
}
function classText(spell) {
return (spell.classNames || spell.classes || []).join(', ');
}
function sortSpells(rows, sortKey) {
rows.sort(function (a, b) {
if (sortKey === 'name') {
return String(a.name || '').localeCompare(String(b.name || ''));
}
if (sortKey === 'level') {
return (parseFloat(a.level) || 0) - (parseFloat(b.level) || 0);
}
return String(classText(a)).localeCompare(String(classText(b)));
});
}
function getSpellTooltipBox() {
var tooltip = document.getElementById('eqoa-spell-tooltip');
if (!tooltip) {
tooltip = document.createElement('div');
tooltip.id = 'eqoa-spell-tooltip';
tooltip.className = 'eqoa-spell-tooltip';
tooltip.style.display = 'none';
document.body.appendChild(tooltip);
}
return tooltip;
}
function formatSpellValue(value) {
if (value === undefined || value === null || value === '') {
return '0';
}
return escapeHtml(value);
}
function showSpellTooltip(spell, event) {
var tooltip = getSpellTooltipBox();
var name = escapeHtml(spell.name || '');
var scope = escapeHtml(spell.scope || '');
var cast = formatSpellValue(spell.castTime);
var recast = formatSpellValue(spell.recast);
var power = formatSpellValue(spell.powerCost);
var equipReq = escapeHtml((spell.classNames || spell.classes || ['ANY']).join(', ') || 'ANY');
var level = formatSpellValue(spell.level);
var range = formatSpellValue(spell.range);
var description = escapeHtml(spell.description || '');
tooltip.innerHTML =
'<div class="eqoa-spell-tooltip-main">' +
'<div class="eqoa-spell-tooltip-name">' + name + '</div>' +
'<div>Scope: ' + scope + ' <span>Cast: ' + cast + '</span></div>' +
'<div>Recast: ' + recast + ' <span>Power: ' + power + '</span></div>' +
'<div>EquipReq: ' + equipReq + '</div>' +
'<div>Level: ' + level + ' <span>Range: ' + range + '</span></div>' +
'</div>' +
'<div class="eqoa-spell-tooltip-desc">' +
description +
'</div>';
tooltip.style.display = 'block';
moveSpellTooltip(event);
}
function moveSpellTooltip(event) {
var tooltip = getSpellTooltipBox();
var left = event.pageX + 18;
var top = event.pageY + 18;
if (left + 430 > window.scrollX + window.innerWidth) {
left = event.pageX - 430;
}
tooltip.style.left = left + 'px';
tooltip.style.top = top + 'px';
}
function hideSpellTooltip() {
var tooltip = document.getElementById('eqoa-spell-tooltip');
if (tooltip) {
tooltip.style.display = 'none';
}
}
function bindSpellTooltip(row, spell) {
row.addEventListener('mouseenter', function (event) {
showSpellTooltip(spell, event);
});
row.addEventListener('mousemove', moveSpellTooltip);
row.addEventListener('mouseleave', hideSpellTooltip);
}
function renderRows(tbody, rows) {
tbody.innerHTML = '';
rows.forEach(function (spell) {
var row = document.createElement('tr');
var name = escapeHtml(spell.name || '');
var level = escapeHtml(spell.level || '');
var classes = classIconsHtml(spell);
var skill = escapeHtml(classText(spell));
var icon = iconHtml(spellIconFile(spell), 'eqoa-spell-icon', spell.name);
row.className = 'eqoa-spell-row';
row.innerHTML =
'<td class="eqoa-spell-name-cell">' + icon + '<a href="' + pageUrl(spell.name) + '">' + name + '</a></td>' +
'<td class="eqoa-spell-level-cell">' + level + '</td>' +
'<td class="eqoa-spell-classes-cell">' + classes + '</td>';
bindSpellTooltip(row, spell);
tbody.appendChild(row);
});
}
lists.forEach(function (box) {
var classCode = String(box.getAttribute('data-class') || '').toUpperCase().trim();
var rows;
var table;
var tbody;
var sortSelect;
var loading;
if (box.classList.contains('eqoa-spell-list-ready')) return;
box.classList.add('eqoa-spell-list-ready');
rows = spells.filter(function (spell) {
if (String(spell.spellGroup || 'Base') !== 'Base') return false;
if (!classCode) return true;
return (spell.classes || []).indexOf(classCode) !== -1;
});
sortSpells(rows, 'level');
if (!rows.length) {
box.innerHTML += '<div class="eqoa-spell-empty">No spells found.</div>';
return;
}
loading = box.querySelector('.eqoa-spell-list__loading');
if (loading) loading.remove();
box.insertAdjacentHTML('beforeend',
'<div class="eqoa-spell-toolbar">' +
'<div class="eqoa-spell-count">' + rows.length + ' spells</div>' +
'<div class="eqoa-spell-sort-wrap">' +
'<select class="eqoa-spell-sort">' +
'<option value="level">Sort: Level</option>' +
'<option value="name">Sort: Name</option>' +
'<option value="class">Sort: Class</option>' +
'</select>' +
'</div>' +
'</div>' +
'<table class="eqoa-spell-list-table">' +
'<tr>' +
'<th>Name</th>' +
'<th>Level</th>' +
'<th>Classes</th>' +
'</tr>' +
'</table>'
);
table = box.querySelector('.eqoa-spell-list-table');
tbody = document.createElement('tbody');
table.appendChild(tbody);
renderRows(tbody, rows);
sortSelect = box.querySelector('.eqoa-spell-sort');
sortSelect.addEventListener('change', function () {
sortSpells(rows, sortSelect.value);
renderRows(tbody, rows);
});
});
}
mw.hook('wikipage.content').add(initEqoaSpellLists);
document.addEventListener('DOMContentLoaded', initEqoaSpellLists);
setTimeout(initEqoaSpellLists, 500);
setTimeout(initEqoaSpellLists, 1500);
function initEqoaSpellPathLists() {
var spellData = window.EQOA_GENERATED_SPELLS || [];
document.querySelectorAll('[data-eqoa-spell-path-list]').forEach(function (box) {
var classCode;
var spellGroup;
var title;
var rows;
var path1;
var path2;
var loading;
classCode = String(box.getAttribute('data-class') || '').trim().toUpperCase();
spellGroup = normalizeEqoaSpellPathGroup(box.getAttribute('data-spell-group'));
title = box.querySelector('.eqoa-spell-list__title')
? box.querySelector('.eqoa-spell-list__title').textContent.trim()
: 'Spell Path';
if (!spellData.length) {
setTimeout(initEqoaSpellPathLists, 250);
return;
}
loading = box.querySelector('.eqoa-spell-list__loading');
if (loading) loading.remove();
rows = spellData.filter(function (spell) {
var currentGroup = normalizeEqoaSpellPathGroup(spell.spellGroup);
if (currentGroup !== spellGroup) return false;
if (!classCode) return true;
return (spell.classes || []).map(function (code) {
return String(code || '').toUpperCase();
}).indexOf(classCode) !== -1;
});
path1 = rows.filter(function (spell) {
var spellPath = String(spell.spellPath || '');
return spellPath === '1' || spellPath === 'both';
});
path2 = rows.filter(function (spell) {
var spellPath = String(spell.spellPath || '');
return spellPath === '2' || spellPath === 'both';
});
box.innerHTML =
'<div class="eqoa-spell-list__title">' + escapeEqoaSpellPathHtml(title) + '</div>' +
'<div class="eqoa-spell-path-grid">' +
buildEqoaSpellPathSection('Path 1', path1) +
buildEqoaSpellPathSection('Path 2', path2) +
'</div>';
bindEqoaSpellPathTooltipEvents(box, rows);
});
}
function normalizeEqoaSpellPathGroup(value) {
value = String(value || '').toLowerCase().replace(/\s+/g, '');
if (value === 'classpath' || value === 'path') return 'ClassPath';
if (value === 'exemplarpath' || value === 'exemplar') return 'ExemplarPath';
return value;
}
function buildEqoaSpellPathSection(title, spells) {
var html = '';
html += '<div class="eqoa-spell-path-section">';
html += '<div class="eqoa-spell-path-title">' + escapeEqoaSpellPathHtml(title) + '</div>';
html += '<table class="eqoa-spell-list-table">';
html += '<tr>';
html += '<th>Name</th>';
html += '<th>Level</th>';
html += '<th>Classes</th>';
html += '</tr>';
if (!spells.length) {
html += '<tr class="eqoa-spell-row"><td colspan="4">No spells found.</td></tr>';
} else {
spells.forEach(function (spell) {
html += '<tr class="eqoa-spell-row" data-eqoa-spell-name="' + escapeEqoaSpellPathHtml(spell.name) + '">';
html += '<td class="eqoa-spell-name-cell">';
html += buildEqoaSpellPathIcon(spell);
html += '<a class="eqoa-spell-link" href="' + eqoaSpellPathPageUrl(spell.name) + '" data-eqoa-spell-name="' + escapeEqoaSpellPathHtml(spell.name) + '">';
html += escapeEqoaSpellPathHtml(spell.name);
html += '</a>';
html += '</td>';
html += '<td class="eqoa-spell-level-cell">' + escapeEqoaSpellPathHtml(spell.level || '') + '</td>';
html += '<td class="eqoa-spell-classes-cell">' + buildEqoaSpellPathClassIcons(spell) + '</td>';
html += '</tr>';
});
}
html += '</table>';
html += '</div>';
return html;
}
function buildEqoaSpellPathIcon(spell) {
var fileName = spellIconNameToFile(spell.icon);
if (!fileName) return '';
return '<img class="eqoa-spell-icon" src="' + eqoaSpellPathFileUrl(fileName) + '" alt="' + escapeEqoaSpellPathHtml(spell.name || '') + '">';
}
function spellIconNameToFile(iconName) {
iconName = String(iconName || '').trim();
if (!iconName) return '';
iconName = iconName
.replace(/\.[a-z0-9]+$/i, '')
.replace(/[^a-z0-9]+/gi, '');
return 'SpellIcon' + iconName + '.png';
}
function buildEqoaSpellPathClassIcons(spell) {
var classes = spell.classes || [];
if (!classes.length) return '';
return classes.map(function (classCode) {
return '<img class="eqoa-spell-class-icon" src="' + eqoaSpellPathFileUrl('ClassIcon' + classCode + '.png') + '" alt="' + escapeEqoaSpellPathHtml(classCode) + '" title="' + escapeEqoaSpellPathHtml(classCode) + '">';
}).join('');
}
function bindEqoaSpellPathTooltipEvents(root, spells) {
var lookup = {};
spells.forEach(function (spell) {
lookup[String(spell.name || '').toLowerCase()] = spell;
});
root.querySelectorAll('.eqoa-spell-row, .eqoa-spell-link').forEach(function (el) {
var spellName = String(el.getAttribute('data-eqoa-spell-name') || el.textContent || '').trim();
var spell = lookup[spellName.toLowerCase()];
if (!spell) return;
el.onmouseenter = function (event) {
showEqoaSpellPathTooltip(spell, event);
};
el.onmousemove = function (event) {
moveEqoaSpellPathTooltip(event);
};
el.onmouseleave = function () {
hideEqoaSpellPathTooltip();
};
});
}
function getEqoaSpellPathTooltipBox() {
var tooltip = document.getElementById('eqoa-spell-tooltip');
if (!tooltip) {
tooltip = document.createElement('div');
tooltip.id = 'eqoa-spell-tooltip';
tooltip.className = 'eqoa-spell-tooltip';
tooltip.style.display = 'none';
document.body.appendChild(tooltip);
}
return tooltip;
}
function showEqoaSpellPathTooltip(spell, event) {
var tooltip = getEqoaSpellPathTooltipBox();
var name = escapeEqoaSpellPathHtml(spell.name || '');
var scope = escapeEqoaSpellPathHtml(spell.scope || '');
var cast = formatEqoaSpellPathValue(spell.castTime);
var recast = formatEqoaSpellPathValue(spell.recast);
var power = formatEqoaSpellPathValue(spell.powerCost);
var equipReq = escapeEqoaSpellPathHtml(spell.reqEquipment || 'ANY');
var level = formatEqoaSpellPathValue(spell.level);
var range = formatEqoaSpellPathValue(spell.range);
var description = escapeEqoaSpellPathHtml(spell.description || '');
tooltip.innerHTML =
'<div class="eqoa-spell-tooltip-main">' +
'<div class="eqoa-spell-tooltip-name">' + name + '</div>' +
'<div>Scope: ' + scope + ' <span>Cast: ' + cast + '</span></div>' +
'<div>Recast: ' + recast + ' <span>Power: ' + power + '</span></div>' +
'<div>EquipReq: ' + equipReq + '</div>' +
'<div>Level: ' + level + ' <span>Range: ' + range + '</span></div>' +
'</div>' +
'<div class="eqoa-spell-tooltip-desc">' +
description +
'</div>';
tooltip.style.display = 'block';
moveEqoaSpellPathTooltip(event);
}
function moveEqoaSpellPathTooltip(event) {
var tooltip = getEqoaSpellPathTooltipBox();
var left = event.pageX + 18;
var top = event.pageY + 18;
if (left + 430 > window.scrollX + window.innerWidth) {
left = event.pageX - 430;
}
tooltip.style.left = left + 'px';
tooltip.style.top = top + 'px';
}
function hideEqoaSpellPathTooltip() {
var tooltip = document.getElementById('eqoa-spell-tooltip');
if (tooltip) {
tooltip.style.display = 'none';
}
}
function formatEqoaSpellPathValue(value) {
if (value === undefined || value === null || value === '') {
return '0';
}
return escapeEqoaSpellPathHtml(value);
}
function eqoaSpellPathFileUrl(fileName) {
fileName = String(fileName || '').trim();
if (!fileName) return '';
if (/^https?:\/\//i.test(fileName)) return fileName;
return mw.config.get('wgScript') + '?title=Special:Redirect/file/' + encodeURIComponent(fileName);
}
function eqoaSpellPathPageUrl(title) {
if (window.mw && mw.util && mw.util.getUrl) {
return mw.util.getUrl(title);
}
return '/index.php?title=' + encodeURIComponent(String(title || '').replace(/ /g, '_'));
}
function escapeEqoaSpellPathHtml(value) {
return String(value || '')
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
function initZoneNpcListFilters() {
document.querySelectorAll('.zone-npc-list-wrapper').forEach(function (wrapper) {
var table = wrapper.querySelector('.zone-monster-table');
var bar;
var input;
var clear;
if (!table) return;
if (wrapper.classList.contains('zone-npc-filter-ready')) return;
wrapper.classList.add('zone-npc-filter-ready');
bar = document.createElement('div');
bar.className = 'zone-npc-filter-bar';
input = document.createElement('input');
input.className = 'zone-npc-search-input';
input.type = 'search';
input.placeholder = 'Search NPCs';
clear = document.createElement('button');
clear.className = 'zone-npc-search-clear';
clear.type = 'button';
clear.textContent = 'Clear';
bar.appendChild(input);
bar.appendChild(clear);
wrapper.insertBefore(bar, table);
function filterRows() {
var query = String(input.value || '').toLowerCase();
var rows = table.querySelectorAll('tr');
Array.prototype.forEach.call(rows, function (row, index) {
var text;
if (index === 0) return;
text = String(row.textContent || '').toLowerCase();
row.style.display = !query || text.indexOf(query) !== -1 ? '' : 'none';
});
}
input.addEventListener('input', filterRows);
clear.addEventListener('click', function () {
input.value = '';
filterRows();
input.focus();
});
});
}
function initEqoaCategoryPageLists() {
var boxes = document.querySelectorAll('.eqoa-category-page-list');
if (!boxes.length) return;
boxes.forEach(function (box) {
var category = box.getAttribute('data-category');
var api;
if (!category) return;
if (box.classList.contains('eqoa-category-page-list-ready')) return;
box.classList.add('eqoa-category-page-list-ready');
box.textContent = 'Loading...';
api = new mw.Api();
api.get({
action: 'query',
list: 'categorymembers',
cmtitle: 'Category:' + category,
cmnamespace: 0,
cmlimit: 500,
cmsort: 'sortkey',
cmdir: 'asc',
format: 'json'
}).then(function (data) {
var pages = data.query && data.query.categorymembers
? data.query.categorymembers
: [];
var list;
box.innerHTML = '';
if (!pages.length) {
box.textContent = 'No pages found.';
return;
}
list = document.createElement('ul');
list.className = 'eqoa-category-page-list-items';
pages.forEach(function (page) {
var item = document.createElement('li');
var link = document.createElement('a');
link.href = mw.util.getUrl(page.title);
link.textContent = page.title;
item.appendChild(link);
list.appendChild(item);
});
box.appendChild(list);
}).catch(function () {
box.textContent = 'Unable to load category.';
});
});
}
function initQuestRewardItemTooltips() {
var links = document.querySelectorAll('.quest-reward-items a');
var items = window.EQOA_GENERATED_ITEMS || [];
if (!links.length) return;
if (!items.length) {
setTimeout(initQuestRewardItemTooltips, 500);
return;
}
function normalizeQuestRewardRarity(rarity) {
return String(rarity || 'common')
.toLowerCase()
.replace(/\s+/g, '-');
}
function getQuestRewardRarityStyle(rarity) {
rarity = normalizeQuestRewardRarity(rarity);
if (rarity === 'uncommon') {
return {
color: '#20ff45',
shadow: '0 0 6px rgba(32, 255, 69, 0.55)'
};
}
if (rarity === 'rare') {
return {
color: '#1e88ff',
shadow: '0 0 6px rgba(30, 136, 255, 0.65)'
};
}
if (rarity === 'ultra-rare') {
return {
color: '#ff3cff',
shadow: '0 0 8px rgba(255, 60, 255, 0.75)'
};
}
if (rarity === 'legendary') {
return {
color: '#ff8000',
shadow: '0 0 8px rgba(255, 128, 0, 0.85)'
};
}
if (rarity === 'unique') {
return {
color: '#ffd700',
shadow: '0 0 8px rgba(255, 215, 0, 0.85)'
};
}
return {
color: '#ffffff',
shadow: '0 0 6px rgba(255, 255, 255, 0.45)'
};
}
function applyQuestRewardRarityStyle(link, item) {
var rarity = normalizeQuestRewardRarity(item.rarity);
var style = getQuestRewardRarityStyle(rarity);
[
'class-item-rarity-common',
'class-item-rarity-uncommon',
'class-item-rarity-rare',
'class-item-rarity-ultra-rare',
'class-item-rarity-legendary',
'class-item-rarity-unique'
].forEach(function (className) {
link.classList.remove(className);
});
link.classList.add('class-item-rarity-' + rarity);
link.style.setProperty('color', style.color, 'important');
link.style.setProperty('font-weight', '800', 'important');
link.style.setProperty('text-shadow', style.shadow, 'important');
link.style.setProperty('text-decoration-color', style.color, 'important');
}
links.forEach(function (link) {
var itemName;
var item;
itemName = (link.getAttribute('data-class-item-name') || link.textContent || '')
.replace(/_/g, ' ')
.trim();
try {
itemName = decodeURIComponent(itemName);
} catch (e) {}
if (!itemName) return;
item = items.find(function (entry) {
return String(entry.name || '').toLowerCase() === itemName.toLowerCase();
});
if (!item) return;
link.classList.add('quest-reward-tooltip-ready');
link.classList.add('class-item-tooltip-link');
link.setAttribute('data-class-item-name', encodeURIComponent(item.name));
applyQuestRewardRarityStyle(link, item);
if (link.getAttribute('data-quest-reward-tooltip-bound') === '1') return;
link.setAttribute('data-quest-reward-tooltip-bound', '1');
link.addEventListener('mouseenter', function (event) {
applyQuestRewardRarityStyle(link, item);
if (typeof showClassItemTooltip === 'function') {
showClassItemTooltip(item, event);
}
});
link.addEventListener('mousemove', function (event) {
if (typeof moveClassItemTooltip === 'function') {
moveClassItemTooltip(event);
}
});
link.addEventListener('mouseleave', function () {
applyQuestRewardRarityStyle(link, item);
if (typeof hideClassItemTooltip === 'function') {
hideClassItemTooltip();
}
});
});
}
function initEqoaQuestIndexFilters() {
document.querySelectorAll('.eqoa-quest-index-section').forEach(function (section) {
var filterRow = section.querySelector('.eqoa-quest-filter-row');
var filters = section.querySelectorAll('.eqoa-quest-filter');
var panels = section.querySelectorAll('.eqoa-quest-panel');
var search;
if (!filterRow || !filters.length || !panels.length) return;
search = section.querySelector('.eqoa-quest-search');
if (!search) {
search = document.createElement('input');
search.type = 'search';
search.className = 'eqoa-quest-search';
search.placeholder = 'Search quests';
filterRow.insertAdjacentElement('afterend', search);
}
function setActive(target) {
filters.forEach(function (filter) {
filter.classList.toggle(
'is-active',
filter.getAttribute('data-quest-filter') === target
);
});
panels.forEach(function (panel) {
var active = panel.getAttribute('data-quest-panel') === target;
panel.classList.toggle('is-active', active);
panel.style.display = active ? 'block' : 'none';
});
filterQuestList();
}
function filterQuestList() {
var activePanel = section.querySelector('.eqoa-quest-panel.is-active');
var query = String(search.value || '').toLowerCase();
if (!activePanel) return;
activePanel.querySelectorAll('li').forEach(function (li) {
li.style.display = li.textContent.toLowerCase().indexOf(query) !== -1
? ''
: 'none';
});
}
if (!section.classList.contains('eqoa-quest-index-ready')) {
section.classList.add('eqoa-quest-index-ready');
filters.forEach(function (filter) {
filter.addEventListener('click', function () {
setActive(filter.getAttribute('data-quest-filter'));
});
});
search.addEventListener('input', filterQuestList);
}
var activeFilter = section.querySelector('.eqoa-quest-filter.is-active') || filters[0];
setActive(activeFilter.getAttribute('data-quest-filter'));
});
}
mw.hook('wikipage.content').add(initEqoaQuestIndexFilters);
document.addEventListener('DOMContentLoaded', initEqoaQuestIndexFilters);
setTimeout(initEqoaQuestIndexFilters, 500);
setTimeout(initEqoaQuestIndexFilters, 1500);
function initEqoaQuestIndexExpandableSections() {
document.querySelectorAll('.eqoa-quest-index-section').forEach(function (section) {
var title = section.querySelector(':scope > .eqoa-section-title');
if (!title) return;
if (section.classList.contains('eqoa-quest-expand-ready')) return;
section.classList.add('eqoa-quest-expand-ready');
title.addEventListener('click', function () {
section.classList.toggle('is-open');
});
});
}
mw.hook('wikipage.content').add(initEqoaQuestIndexExpandableSections);
document.addEventListener('DOMContentLoaded', initEqoaQuestIndexExpandableSections);
setTimeout(initEqoaQuestIndexExpandableSections, 500);
setTimeout(initEqoaQuestIndexExpandableSections, 1500);
function centerSoaHomeBanner() {
document.querySelectorAll('.soa-home-banner-wrap').forEach(function (wrap) {
var image = wrap.querySelector('img');
if (!image) return;
wrap.style.display = 'flex';
wrap.style.justifyContent = 'center';
wrap.style.alignItems = 'center';
wrap.style.width = '100%';
wrap.style.overflow = 'hidden';
wrap.style.background = 'transparent';
image.style.display = 'block';
image.style.width = '100%';
image.style.maxWidth = '1200px';
image.style.height = 'auto';
image.style.margin = '0 auto';
image.parentElement.style.display = 'block';
image.parentElement.style.margin = '0 auto';
});
}
document.addEventListener('DOMContentLoaded', centerSoaHomeBanner);
mw.hook('wikipage.content').add(centerSoaHomeBanner);
setTimeout(centerSoaHomeBanner, 250);
setTimeout(centerSoaHomeBanner, 1000);
function moveMobilePageActionsAfterNavigation() {
var isMobile = window.matchMedia && window.matchMedia('(max-width: 700px)').matches;
var actions = document.getElementById('p-cactions');
var sidebar = document.getElementById('sidebar');
var navigation = document.getElementById('p-navigation');
var column = document.getElementById('column-one');
var logo = document.getElementById('p-logo');
if (!actions || !column) return;
if (isMobile) {
if (navigation && navigation.parentNode) {
navigation.insertAdjacentElement('afterend', actions);
} else if (sidebar) {
sidebar.appendChild(actions);
} else {
column.appendChild(actions);
}
actions.classList.add('eqoa-mobile-page-actions-bottom');
return;
}
actions.classList.remove('eqoa-mobile-page-actions-bottom');
if (logo && logo.parentNode === column) {
column.insertBefore(actions, logo);
} else {
column.insertBefore(actions, column.firstChild);
}
}
document.addEventListener('DOMContentLoaded', moveMobilePageActionsAfterNavigation);
mw.hook('wikipage.content').add(moveMobilePageActionsAfterNavigation);
window.addEventListener('resize', moveMobilePageActionsAfterNavigation);
window.addEventListener('orientationchange', function () {
setTimeout(moveMobilePageActionsAfterNavigation, 150);
});
setTimeout(moveMobilePageActionsAfterNavigation, 250);
setTimeout(moveMobilePageActionsAfterNavigation, 1000);
function initNpcPageItemTooltips() {
var links = document.querySelectorAll('.eqoa-npc-page .eqoa-npc-zone-table td:nth-child(4) a');
var items = window.EQOA_GENERATED_ITEMS || [];
if (!links.length) return;
if (!items.length) {
setTimeout(initNpcPageItemTooltips, 500);
return;
}
function normalizeNpcDropName(text) {
return String(text || '')
.replace(/\u2019/g, "'")
.replace(/\s*\(\s*\d+%\s*\)\s*$/g, '')
.replace(/\s+/g, ' ')
.trim()
.toLowerCase();
}
function normalizeNpcDropRarity(rarity) {
return String(rarity || 'common')
.toLowerCase()
.replace(/\s+/g, '-');
}
function getNpcDropRarityStyle(rarity) {
rarity = normalizeNpcDropRarity(rarity);
if (rarity === 'uncommon') {
return { color: '#20ff45', shadow: '0 0 6px rgba(32, 255, 69, 0.55)' };
}
if (rarity === 'rare') {
return { color: '#1e88ff', shadow: '0 0 6px rgba(30, 136, 255, 0.65)' };
}
if (rarity === 'ultra-rare') {
return { color: '#ff3cff', shadow: '0 0 8px rgba(255, 60, 255, 0.75)' };
}
if (rarity === 'legendary') {
return { color: '#ff8000', shadow: '0 0 8px rgba(255, 128, 0, 0.85)' };
}
if (rarity === 'unique') {
return { color: '#ffd700', shadow: '0 0 8px rgba(255, 215, 0, 0.85)' };
}
return { color: '#ffffff', shadow: '0 0 6px rgba(255, 255, 255, 0.45)' };
}
function getNpcDropItemFromLink(link) {
var names = [];
var href = link.getAttribute('href') || '';
names.push(link.textContent || '');
names.push(link.getAttribute('title') || '');
names.push(link.getAttribute('data-class-item-name') || '');
try {
if (href.indexOf('title=') !== -1) {
names.push(decodeURIComponent(href.split('title=')[1].split('&')[0]).replace(/_/g, ' '));
} else {
names.push(decodeURIComponent(href.substring(href.lastIndexOf('/') + 1)).replace(/_/g, ' '));
}
} catch (e) {}
names = names.map(normalizeNpcDropName).filter(Boolean);
return items.find(function (item) {
var itemName = normalizeNpcDropName(item.name);
return names.indexOf(itemName) !== -1;
});
}
function applyNpcDropRarityStyle(link, item) {
var rarity = normalizeNpcDropRarity(item.rarity);
var style = getNpcDropRarityStyle(rarity);
var textSpan;
[
'class-item-rarity-common',
'class-item-rarity-uncommon',
'class-item-rarity-rare',
'class-item-rarity-ultra-rare',
'class-item-rarity-legendary',
'class-item-rarity-unique'
].forEach(function (className) {
link.classList.remove(className);
});
link.classList.add('class-item-tooltip-link');
link.classList.add('class-item-rarity-' + rarity);
link.setAttribute('data-class-item-name', encodeURIComponent(item.name));
/*
* Remove browser-native hover tooltip.
* The CB-style tooltip handles item display instead.
*/
link.removeAttribute('title');
textSpan = link.querySelector('.npc-drop-rarity-text');
if (!textSpan) {
textSpan = document.createElement('span');
textSpan.className = 'npc-drop-rarity-text';
textSpan.textContent = link.textContent;
link.textContent = '';
link.appendChild(textSpan);
}
textSpan.textContent = item.name;
link.style.setProperty('font-weight', '800', 'important');
link.style.setProperty('text-decoration-color', style.color, 'important');
textSpan.style.setProperty('color', style.color, 'important');
textSpan.style.setProperty('-webkit-text-fill-color', style.color, 'important');
textSpan.style.setProperty('font-weight', '800', 'important');
textSpan.style.setProperty('text-shadow', style.shadow, 'important');
textSpan.style.setProperty('text-decoration-color', style.color, 'important');
}
links.forEach(function (link) {
var item = getNpcDropItemFromLink(link);
if (!item) return;
applyNpcDropRarityStyle(link, item);
if (link.getAttribute('data-npc-item-tooltip-bound') === '1') return;
link.setAttribute('data-npc-item-tooltip-bound', '1');
link.addEventListener('mouseenter', function (event) {
applyNpcDropRarityStyle(link, item);
if (typeof showClassItemTooltip === 'function') {
showClassItemTooltip(item, event);
}
});
link.addEventListener('mousemove', function (event) {
applyNpcDropRarityStyle(link, item);
if (typeof moveClassItemTooltip === 'function') {
moveClassItemTooltip(event);
}
});
link.addEventListener('mouseleave', function () {
applyNpcDropRarityStyle(link, item);
if (typeof hideClassItemTooltip === 'function') {
hideClassItemTooltip();
}
});
});
}
mw.hook('wikipage.content').add(initNpcPageItemTooltips);
document.addEventListener('DOMContentLoaded', initNpcPageItemTooltips);
setTimeout(initNpcPageItemTooltips, 500);
setTimeout(initNpcPageItemTooltips, 1500);
setTimeout(initNpcPageItemTooltips, 3000);
setTimeout(initNpcPageItemTooltips, 5000);
mw.hook('wikipage.content').add(initQuestRewardItemTooltips);
document.addEventListener('DOMContentLoaded', initQuestRewardItemTooltips);
setTimeout(initQuestRewardItemTooltips, 500);
setTimeout(initQuestRewardItemTooltips, 1500);
setTimeout(initQuestRewardItemTooltips, 3000);
mw.hook('wikipage.content').add(initQuestRewardItemTooltips);
document.addEventListener('DOMContentLoaded', initQuestRewardItemTooltips);
setTimeout(initQuestRewardItemTooltips, 500);
setTimeout(initQuestRewardItemTooltips, 1500);
mw.hook('wikipage.content').add(initEqoaCategoryPageLists);
document.addEventListener('DOMContentLoaded', initEqoaCategoryPageLists);
setTimeout(initEqoaCategoryPageLists, 500);
mw.hook('wikipage.content').add(initZoneNpcListFilters);
document.addEventListener('DOMContentLoaded', initZoneNpcListFilters);
setTimeout(initZoneNpcListFilters, 500);
mw.hook('wikipage.content').add(initEqoaSpellPathLists);
document.addEventListener('DOMContentLoaded', initEqoaSpellPathLists);
setTimeout(initEqoaSpellPathLists, 500);
setTimeout(initEqoaSpellPathLists, 1500);
setTimeout(initEqoaSpellPathLists, 3000);
function hidePageActionButtonsForNonContributors() {
var username = mw.config.get('wgUserName') || '';
var groups = mw.config.get('wgUserGroups') || [];
var canSeePageActions =
username === 'DevRenodor' ||
groups.indexOf('contributor') !== -1 ||
groups.indexOf('administrator') !== -1 ||
groups.indexOf('bureaucrat') !== -1 ||
groups.indexOf('interface-admin') !== -1;
var actionIds = [
'ca-view',
'ca-edit',
'ca-ve-edit',
'ca-viewsource',
'ca-history'
];
actionIds.forEach(function (id) {
var action = document.getElementById(id);
if (!action) return;
action.style.setProperty('display', canSeePageActions ? '' : 'none', 'important');
});
}
document.addEventListener('DOMContentLoaded', hidePageActionButtonsForNonContributors);
mw.hook('wikipage.content').add(hidePageActionButtonsForNonContributors);
setTimeout(hidePageActionButtonsForNonContributors, 250);
setTimeout(hidePageActionButtonsForNonContributors, 1000);
function initSandstormChangesHeaderCollapse() {
var page = document.querySelector('.eqoa-changes-page');
if (!page) return;
Array.prototype.forEach.call(
page.querySelectorAll('.eqoa-changes-section.mw-collapsible'),
function (section) {
var header = section.querySelector('.eqoa-header');
var defaultToggle = section.querySelector('.mw-collapsible-toggle');
var plus;
if (!header) return;
if (section.classList.contains('eqoa-change-header-collapse-ready')) return;
plus = document.createElement('span');
plus.className = 'eqoa-change-header-plus';
plus.textContent = section.classList.contains('mw-collapsed') ? '+' : '-';
header.appendChild(plus);
function updatePlus() {
plus.textContent = section.classList.contains('mw-collapsed') ? '+' : '-';
}
header.addEventListener('click', function (event) {
if (event.target.closest('a')) return;
defaultToggle = section.querySelector('.mw-collapsible-toggle');
if (defaultToggle) {
defaultToggle.click();
setTimeout(updatePlus, 50);
return;
}
section.classList.toggle('mw-collapsed');
updatePlus();
});
new MutationObserver(updatePlus).observe(section, {
attributes: true,
attributeFilter: ['class']
});
updatePlus();
section.classList.add('eqoa-change-header-collapse-ready');
}
);
}
mw.hook('wikipage.content').add(initSandstormChangesHeaderCollapse);
document.addEventListener('DOMContentLoaded', initSandstormChangesHeaderCollapse);
setTimeout(initSandstormChangesHeaderCollapse, 500);
setTimeout(initSandstormChangesHeaderCollapse, 1500);
function initEqoaExpCampToggleIcons(root) {
root = root && root.jquery ? root[0] : (root || document);
root.querySelectorAll('table.exp-camp-table.mw-collapsible caption').forEach(function (caption) {
var table = caption.closest('table.exp-camp-table');
var toggle = caption.querySelector('.mw-collapsible-toggle');
if (!table || !toggle) return;
function removeDefaultBrackets() {
[caption, toggle].forEach(function (scope) {
if (!scope) return;
scope.querySelectorAll('.mw-collapsible-bracket').forEach(function (bracket) {
bracket.remove();
});
Array.from(scope.childNodes).forEach(function (node) {
if (node.nodeType === 3) {
node.nodeValue = node.nodeValue.replace(/\[|\]/g, '');
}
if (
node.nodeType === 1 &&
(
node.classList.contains('mw-collapsible-bracket') ||
node.textContent.trim() === '[' ||
node.textContent.trim() === ']'
)
) {
node.remove();
}
});
});
}
function updateToggleIcon() {
var isCollapsed =
table.classList.contains('mw-collapsed') ||
toggle.classList.contains('mw-collapsible-toggle-collapsed') ||
toggle.getAttribute('aria-expanded') === 'false';
removeDefaultBrackets();
toggle.textContent = isCollapsed ? '+' : '−';
toggle.title = isCollapsed ? 'Expand' : 'Collapse';
toggle.setAttribute('aria-label', isCollapsed ? 'Expand' : 'Collapse');
removeDefaultBrackets();
}
if (!caption.classList.contains('eqoa-exp-caption-ready')) {
caption.classList.add('eqoa-exp-caption-ready');
caption.addEventListener('click', function (event) {
if (event.target.closest('.mw-collapsible-toggle')) {
setTimeout(updateToggleIcon, 0);
setTimeout(updateToggleIcon, 100);
return;
}
event.preventDefault();
toggle.click();
setTimeout(updateToggleIcon, 0);
setTimeout(updateToggleIcon, 100);
});
}
updateToggleIcon();
});
}
mw.hook('wikipage.content').add(initEqoaExpCampToggleIcons);
document.addEventListener('DOMContentLoaded', function () {
initEqoaExpCampToggleIcons(document);
});
setTimeout(function () {
initEqoaExpCampToggleIcons(document);
}, 500);
setTimeout(function () {
initEqoaExpCampToggleIcons(document);
}, 1500);
function initEqoaTemplateCopyButtons(root) {
root = root && root.jquery ? root[0] : (root || document);
root.querySelectorAll('.eqoa-template-copy-button').forEach(function (button) {
if (button.classList.contains('eqoa-template-copy-ready')) return;
button.classList.add('eqoa-template-copy-ready');
button.addEventListener('click', function () {
var card = button.closest('.eqoa-template-card');
var code = card ? card.querySelector('.eqoa-template-code') : null;
var text = code ? code.textContent.trim() : '';
if (!text) return;
navigator.clipboard.writeText(text).then(function () {
button.textContent = 'Copied';
setTimeout(function () {
button.textContent = 'Copy';
}, 1200);
});
});
});
}
mw.hook('wikipage.content').add(initEqoaTemplateCopyButtons);
document.addEventListener('DOMContentLoaded', function () {
initEqoaTemplateCopyButtons(document);
});
function initEqoaZoneTabsLocked(root) {
root = root && root.jquery ? root[0] : (root || document);
root.querySelectorAll('.eqoa-zone-tabs').forEach(function (tabs) {
if (tabs.classList.contains('eqoa-zone-tabs-locked-ready')) return;
tabs.classList.add('eqoa-zone-tabs-locked-ready');
tabs.querySelectorAll('.eqoa-zone-tab-button[data-tab]').forEach(function (button) {
button.addEventListener('click', function () {
var tabName = button.getAttribute('data-tab');
tabs.querySelectorAll('.eqoa-zone-tab-button[data-tab]').forEach(function (btn) {
btn.classList.toggle('is-active', btn === button);
});
tabs.querySelectorAll('.eqoa-zone-tab-panel[data-tab-panel]').forEach(function (panel) {
panel.classList.toggle(
'is-active',
panel.getAttribute('data-tab-panel') === tabName
);
});
});
});
});
}
mw.hook('wikipage.content').add(initEqoaZoneTabsLocked);
document.addEventListener('DOMContentLoaded', function () {
initEqoaZoneTabsLocked(document);
});
setTimeout(function () {
initEqoaZoneTabsLocked(document);
}, 500);
/* ===== Zone NPC Tab Auto-Fill From Category ===== */
(function () {
function normalizeEqoaText(value) {
return String(value || '')
.replace(/\s+/g, ' ')
.trim()
.toLowerCase();
}
function getCurrentZoneName() {
return mw.config.get('wgTitle') || mw.config.get('wgPageName') || '';
}
function getNpcFamilyFromDoc(doc) {
var rows = doc.querySelectorAll('.eqoa-npc-info-table tr');
for (var i = 0; i < rows.length; i++) {
var cells = rows[i].querySelectorAll('td');
var label;
if (cells.length < 2) continue;
label = normalizeEqoaText(cells[0].textContent);
if (label.indexOf('family') !== -1) {
return cells[1].innerHTML.trim();
}
}
return '';
}
function getNpcZoneRowsFromDoc(doc, zoneName) {
var wantedZone = normalizeEqoaText(zoneName);
var rows = doc.querySelectorAll('.eqoa-npc-zone-table tr');
var matches = [];
for (var i = 0; i < rows.length; i++) {
var cells = rows[i].querySelectorAll('td');
if (cells.length < 7) continue;
if (normalizeEqoaText(cells[0].textContent) === wantedZone) {
matches.push({
level: cells[1].innerHTML.trim(),
drops: cells[3].innerHTML.trim(),
quest: cells[5].innerHTML.trim(),
notes: cells[6].innerHTML.trim()
});
}
}
return matches;
}
function buildNpcLink(pageTitle) {
var link = document.createElement('a');
link.href = mw.util.getUrl(pageTitle);
link.textContent = pageTitle;
return link;
}
function loadZoneNpcPage(api, pageTitle, zoneName) {
return api.get({
action: 'parse',
page: pageTitle,
prop: 'text',
format: 'json'
}).then(function (data) {
var html = data && data.parse && data.parse.text && data.parse.text['*'];
var doc;
var zoneRows;
var family;
if (!html) return [];
doc = new DOMParser().parseFromString(html, 'text/html');
zoneRows = getNpcZoneRowsFromDoc(doc, zoneName);
family = getNpcFamilyFromDoc(doc);
if (!zoneRows.length) {
return [{
name: pageTitle,
level: '',
drops: '',
family: family || '',
notes: ''
}];
}
return zoneRows.map(function (zoneRow) {
return {
name: pageTitle,
level: zoneRow.level || '',
drops: zoneRow.drops || '',
family: family || '',
notes: zoneRow.notes || ''
};
});
});
}
function renderZoneNpcRows(table, npcs) {
var tbody = table.querySelector('tbody');
if (!tbody) {
tbody = document.createElement('tbody');
table.appendChild(tbody);
}
tbody.innerHTML = '';
if (!npcs.length) {
tbody.innerHTML =
'<tr class="zone-npc-row">' +
'<td colspan="5">No NPCs found.</td>' +
'</tr>';
return;
}
npcs.forEach(function (npc) {
var row = document.createElement('tr');
var nameCell = document.createElement('td');
row.className = 'zone-npc-row';
nameCell.className = 'zone-monster-name';
nameCell.appendChild(buildNpcLink(npc.name));
row.appendChild(nameCell);
row.innerHTML +=
'<td class="zone-monster-level">' + (npc.level || '') + '</td>' +
'<td class="zone-monster-drops">' + (npc.drops || '') + '</td>' +
'<td class="zone-monster-family">' + (npc.family || '') + '</td>' +
'<td class="zone-monster-notes">' + (npc.notes || '') + '</td>';
tbody.appendChild(row);
});
}
function initZoneNpcCategoryAutoFill(root) {
var zoneName = getCurrentZoneName();
var categoryName = zoneName + ' NPCs';
var panel;
var table;
var api;
root = root && root.jquery ? root[0] : (root || document);
panel = root.querySelector('[data-tab-panel="npcs"]');
if (!panel || panel.classList.contains('eqoa-zone-npc-auto-ready')) return;
table = panel.querySelector('.zone-monster-table');
if (!table) return;
panel.classList.add('eqoa-zone-npc-auto-ready');
api = new mw.Api();
api.get({
action: 'query',
list: 'categorymembers',
cmtitle: 'Category:' + categoryName,
cmnamespace: 0,
cmlimit: 500,
format: 'json'
}).then(function (data) {
var members = data &&
data.query &&
data.query.categorymembers
? data.query.categorymembers
: [];
return Promise.all(
members.map(function (member) {
return loadZoneNpcPage(api, member.title, zoneName);
})
);
}).then(function (rows) {
rows = rows.reduce(function (allRows, npcRows) {
return allRows.concat(npcRows || []);
}, []);
rows = rows.filter(Boolean).sort(function (a, b) {
var nameCompare = a.name.localeCompare(b.name);
if (nameCompare !== 0) return nameCompare;
return String(a.level || '').localeCompare(String(b.level || ''));
});
renderZoneNpcRows(table, rows);
if (typeof initGeneratedItemTooltipsForWiki === 'function') {
initGeneratedItemTooltipsForWiki(panel);
}
if (typeof initZoneNpcSearch === 'function') {
initZoneNpcSearch(panel);
}
if (window.jQuery && jQuery.fn && jQuery.fn.tablesorter) {
jQuery(table).trigger('destroy');
jQuery(table).tablesorter();
}
}).catch(function (error) {
console.error('EQOA zone NPC auto-fill failed:', error);
});
}
mw.hook('wikipage.content').add(initZoneNpcCategoryAutoFill);
document.addEventListener('DOMContentLoaded', function () {
initZoneNpcCategoryAutoFill(document);
});
setTimeout(function () {
initZoneNpcCategoryAutoFill(document);
}, 500);
setTimeout(function () {
initZoneNpcCategoryAutoFill(document);
}, 1500);
})();