Diferencia entre revisiones de «MediaWiki:Common.js»
Ir a la navegación
Ir a la búsqueda
Página creada con «function googleTranslateElementInit() { new google.translate.TranslateElement({ pageLanguage: 'es', includedLanguages: 'en,pt', layout: google.translate.TranslateElement.InlineLayout.SIMPLE }, 'google_translate_element'); } var gtScript = document.createElement('script'); gtScript.type = 'text/javascript'; gtScript.src = '//translate.google.com/translate_a/element.js?cb=googleTranslateElementInit'; document.head.appendChild(gtScript); // Cambiar idio…» |
Sin resumen de edición |
||
| (No se muestran 8 ediciones intermedias del mismo usuario) | |||
| Línea 1: | Línea 1: | ||
function | (function () { | ||
function safeNormalize(text) { | |||
text = String(text || '').toLowerCase().trim(); | |||
try { | |||
}, ' | return text.normalize('NFD').replace(/[\u0300-\u036f]/g, ''); | ||
} catch (e) { | |||
return text; | |||
} | |||
} | |||
function hasMark(cellText) { | |||
cellText = String(cellText || '').replace(/\s+/g, '').trim(); | |||
return ( | |||
cellText !== '' && | |||
( | |||
cellText.indexOf('✔') !== -1 || | |||
cellText.indexOf('✅') !== -1 || | |||
cellText.indexOf('☑') !== -1 | |||
) | |||
); | |||
} | |||
function getActiveCategoryCol(tableId) { | |||
var activeBtn = document.querySelector('.oroza-cat-btn.active[data-table="' + tableId + '"]'); | |||
if (!activeBtn) { | |||
var firstBtn = document.querySelector('.oroza-cat-btn[data-table="' + tableId + '"]'); | |||
if (!firstBtn) return null; | |||
firstBtn.classList.add('active'); | |||
return firstBtn.getAttribute('data-col'); | |||
} | |||
return activeBtn.getAttribute('data-col'); // Puede retornar "all" en string | |||
} | |||
function getSearchTextForRow(row, tableId) { | |||
var cells = row.getElementsByTagName('td'); | |||
var text = row.getAttribute('data-search') || ''; | |||
// Como añadimos "Prob.", ahora las columnas de información base son 3 en Slot 1 y 4 en Slot 2 | |||
var visibleInfoCols = tableId === 'tableSlot1' ? 3 : 4; | |||
for (var i = 0; i < visibleInfoCols; i++) { | |||
if (cells[i]) { | |||
text += ' ' + (cells[i].textContent || cells[i].innerText || ''); | |||
} | |||
} | |||
return safeNormalize(text); | |||
} | |||
function updateCategoryView(tableId, inputId) { | |||
var table = document.getElementById(tableId); | |||
var input = document.getElementById(inputId); | |||
if (!table || !table.tBodies.length || !table.tHead || !table.tHead.rows.length) return; | |||
var activeColRaw = getActiveCategoryCol(tableId); | |||
if (activeColRaw === null) return; | |||
var showAll = (activeColRaw === 'all'); | |||
var activeCol = showAll ? -1 : parseInt(activeColRaw, 10); | |||
var searchText = input ? safeNormalize(input.value) : ''; | |||
var rows = table.tBodies[0].getElementsByTagName('tr'); | |||
var headerRow = table.tHead.rows[0]; | |||
// Columnas que SIEMPRE se ven (Atributo, Prob, Rango, etc.) | |||
var staticCols = tableId === 'tableSlot1' ? 3 : 4; | |||
// Actualizar Header | |||
for (var h = 0; h < headerRow.cells.length; h++) { | |||
headerRow.cells[h].style.display = (showAll || h < staticCols || h === activeCol) ? '' : 'none'; | |||
} | |||
// Actualizar Body | |||
for (var i = 0; i < rows.length; i++) { | |||
var row = rows[i]; | |||
var cells = row.getElementsByTagName('td'); | |||
// Si estamos en "Todos", empezamos asumiendo que sí tiene categoría válida | |||
var matchCategory = showAll; | |||
var matchText = true; | |||
// Si se escogió una categoría específica, verificamos si tiene "✔" | |||
if (!showAll && cells[activeCol]) { | |||
matchCategory = hasMark(cells[activeCol].textContent || cells[activeCol].innerText); | |||
} | |||
// Verificación de búsqueda por texto | |||
if (searchText !== '') { | |||
var searchPool = getSearchTextForRow(row, tableId); | |||
if (searchPool.indexOf(searchText) === -1) { | |||
matchText = false; | |||
} | |||
} | |||
row.style.display = (matchCategory && matchText) ? '' : 'none'; | |||
for (var c = 0; c < cells.length; c++) { | |||
cells[c].style.display = (showAll || c < staticCols || c === activeCol) ? '' : 'none'; | |||
} | |||
} | |||
} | |||
function bindCategoryButtons(tableId, inputId) { | |||
var buttons = document.querySelectorAll('.oroza-cat-btn[data-table="' + tableId + '"]'); | |||
for (var i = 0; i < buttons.length; i++) { | |||
if (buttons[i].dataset.orozaBound === '1') continue; | |||
buttons[i].dataset.orozaBound = '1'; | |||
buttons[i].addEventListener('click', function () { | |||
var sameTableButtons = document.querySelectorAll('.oroza-cat-btn[data-table="' + tableId + '"]'); | |||
for (var j = 0; j < sameTableButtons.length; j++) { | |||
sameTableButtons[j].classList.remove('active'); | |||
} | |||
this.classList.add('active'); | |||
updateCategoryView(tableId, inputId); | |||
}); | |||
} | |||
} | |||
function bindSearchInput(tableId, inputId) { | |||
var input = document.getElementById(inputId); | |||
if (!input || input.dataset.orozaBound === '1') return; | |||
input.dataset.orozaBound = '1'; | |||
input.addEventListener('input', function () { | |||
updateCategoryView(tableId, inputId); | |||
}); | |||
} | |||
function initOrozaDropFilters() { | |||
if (document.getElementById('tableSlot1')) { | |||
bindCategoryButtons('tableSlot1', 'textSlot1'); | |||
bindSearchInput('tableSlot1', 'textSlot1'); | |||
updateCategoryView('tableSlot1', 'textSlot1'); | |||
} | |||
if (document.getElementById('tableSlot2')) { | |||
bindCategoryButtons('tableSlot2', 'textSlot2'); | |||
bindSearchInput('tableSlot2', 'textSlot2'); | |||
updateCategoryView('tableSlot2', 'textSlot2'); | |||
} | |||
} | |||
if (window.mw && mw.hook) { | |||
mw.hook('wikipage.content').add(function () { | |||
initOrozaDropFilters(); | |||
}); | |||
} else if (document.readyState === 'loading') { | |||
document.addEventListener('DOMContentLoaded', initOrozaDropFilters); | |||
} else { | |||
initOrozaDropFilters(); | |||
} | |||
})(); | |||
/* ========================================================= | |||
RESALTAR MENÚ LATERAL ACTIVO (VERSIÓN NATIVA MEDIAWIKI) | |||
========================================================= */ | |||
function highlightActiveSidebarLink() { | |||
// 1. Obtenemos el nombre exacto de la página actual desde el motor de MediaWiki | |||
var currentPage = mw.config.get('wgPageName'); | |||
if (!currentPage) return; | |||
// Limpiamos el nombre de la página actual (todo a minúsculas y espacios a guiones bajos) | |||
currentPage = currentPage.replace(/ /g, '_').toLowerCase(); | |||
var sidebarLinks = document.querySelectorAll('.oroza-sidebar li a'); | |||
for (var i = 0; i < sidebarLinks.length; i++) { | |||
var linkHref = sidebarLinks[i].getAttribute('href'); | |||
if (!linkHref) continue; | |||
// 2. Extraemos el nombre de la página del enlace del menú | |||
var linkPage = ""; | |||
if (linkHref.indexOf('title=') !== -1) { | |||
linkPage = linkHref.split('title=')[1].split('&')[0]; | |||
} else { | |||
linkPage = linkHref.split('/').pop(); // Toma lo último después del slash (ej. "Como-instalarlo") | |||
} | |||
if (!linkPage) continue; | |||
// 3. Decodificamos caracteres especiales (como acentos o espacios %20) | |||
try { | |||
linkPage = decodeURIComponent(linkPage); | |||
} catch(e) {} | |||
// Limpiamos el enlace de la misma forma para que la comparación sea exacta | |||
linkPage = linkPage.replace(/ /g, '_').toLowerCase(); | |||
// 4. Comparamos | |||
if (currentPage === linkPage) { | |||
sidebarLinks[i].classList.add('active'); | |||
} else { | |||
sidebarLinks[i].classList.remove('active'); // Limpia los demás por si acaso | |||
} | |||
} | |||
} | } | ||
var | // Integración con MediaWiki | ||
if (window.mw && mw.hook) { | |||
mw.hook('wikipage.content').add(highlightActiveSidebarLink); | |||
document. | } else if (document.readyState === 'loading') { | ||
document.addEventListener('DOMContentLoaded', highlightActiveSidebarLink); | |||
} else { | |||
highlightActiveSidebarLink(); | |||
} | |||
/* ========================================================= | |||
OROZA PET ATTACK REBALANCE V13.3 - SIMPLE SEARCH | |||
Paste into MediaWiki:Common.js | |||
========================================================= */ | |||
(function () { | |||
function normalize(text) { | |||
return (text || '') | |||
.toString() | |||
.toLowerCase() | |||
.normalize('NFD') | |||
.replace(/[\u0300-\u036f]/g, '') | |||
.replace(/_/g, ' ') | |||
.replace(/\.{2,}/g, '…') | |||
.replace(/\s+/g, ' ') | |||
.trim(); | |||
} | |||
function initOrozaPetSearchV9(root) { | |||
root = root || document; | |||
var input = root.querySelector ? root.querySelector('#orozaPetSearchV6') : document.getElementById('orozaPetSearchV6'); | |||
if (!input || input.dataset.ready === 'v9') return; | |||
input.dataset.ready = 'v9'; | |||
var container = input.closest('.oroza-pet-v6') || document; | |||
var cards = Array.prototype.slice.call(container.querySelectorAll('.oroza-pet-v6-card')); | |||
var sections = Array.prototype.slice.call(container.querySelectorAll('[data-pet-section]')); | |||
var result = container.querySelector('#orozaPetSearchV6Result'); | |||
var clearBtn = container.querySelector('[data-pet-clear]'); | |||
function applySearch() { | |||
var q = normalize(input.value); | |||
var shown = 0; | |||
cards.forEach(function (card) { | |||
var haystack = normalize((card.getAttribute('data-pet-search') || '') + ' ' + card.textContent); | |||
var match = !q || haystack.indexOf(q) !== -1; | |||
card.classList.toggle('is-hidden', !match); | |||
if (match) shown++; | |||
}); | |||
sections.forEach(function (section) { | |||
var visibleCards = section.querySelectorAll('.oroza-pet-v6-card:not(.is-hidden)').length; | |||
var shouldHide = visibleCards === 0 && q; | |||
section.classList.toggle('is-hidden', shouldHide); | |||
if (!shouldHide && q) section.open = true; | |||
}); | |||
if (result) { | |||
if (shown === 0) { | |||
result.textContent = 'No pets were found with that text.'; | |||
} else if (q) { | |||
result.textContent = 'Visible results: ' + shown + '. Open the card to review each Pokémon skill and level.'; | |||
} else { | |||
result.textContent = 'Showing all pets.'; | |||
} | |||
} | |||
} | |||
input.addEventListener('input', applySearch); | |||
if (clearBtn) { | |||
clearBtn.addEventListener('click', function () { | |||
input.value = ''; | |||
applySearch(); | |||
input.focus(); | |||
}); | |||
} | |||
applySearch(); | |||
} | |||
function boot() { | |||
initOrozaPetSearchV9(document); | |||
} | |||
if (document.readyState === 'loading') { | |||
document.addEventListener('DOMContentLoaded', boot); | |||
} else { | |||
boot(); | |||
} | |||
if (window.mw && window.mw.hook) { | |||
window.mw.hook('wikipage.content').add(function ($content) { | |||
var node = $content && $content[0] ? $content[0] : document; | |||
initOrozaPetSearchV9(node); | |||
}); | |||
} | |||
})(); | |||
/* ========================================================= | |||
OROZA RO SMART WIKI SEARCH V2.2 | |||
--------------------------------------------------------- | |||
- Searches inside the complete text of every main Wiki page | |||
- Detects headings/sections and shows the matching fragment | |||
- Combines a local smart index with MediaWiki native search | |||
- Adds aliases/synonyms for common Ragnarok Online terms | |||
- Uses cache to avoid downloading the Wiki on every search | |||
- Keyboard navigation, mobile support and native fallback | |||
========================================================= */ | |||
(function () { | |||
'use strict'; | |||
var SELECTOR = '[data-oroza-global-search]'; | |||
var MIN_QUERY_LENGTH = 2; | |||
var SMART_LIMIT = 10; | |||
var API_LIMIT = 8; | |||
var MENU_LIMIT = 3; | |||
var DEBOUNCE_MS = 240; | |||
var INDEX_TTL_MS = 2 * 60 * 60 * 1000; | |||
var MAX_INDEX_PAGES = 400; | |||
var MAX_PAGE_TEXT = 60000; | |||
var CACHE_VERSION = 4; | |||
var indexPromise = null; | |||
var memoryIndex = null; | |||
function normalizeText(value) { | |||
var text = String(value || '') | |||
.toLowerCase() | |||
.replace(/_/g, ' ') | |||
.replace(/ /gi, ' ') | |||
.replace(/[\u2010-\u2015]/g, '-') | |||
.replace(/[^a-z0-9@+#áéíóúüñçàèìòùâêîôûäëïöü\s-]/gi, ' ') | |||
.replace(/\s+/g, ' ') | |||
.trim(); | |||
try { | |||
text = text.normalize('NFD').replace(/[\u0300-\u036f]/g, ''); | |||
} catch (e) {} | |||
return text; | |||
} | |||
function decodeHtmlEntities(value) { | |||
var text = String(value || ''); | |||
/* MediaWiki search snippets can arrive HTML-escaped more than once. | |||
Decode repeatedly so <h2> becomes a real tag before removing it. */ | |||
for (var i = 0; i < 3; i++) { | |||
var decoder = document.createElement('textarea'); | |||
decoder.innerHTML = text; | |||
var decoded = decoder.value; | |||
if (decoded === text) break; | |||
text = decoded; | |||
} | |||
return text; | |||
} | |||
function cleanSearchCodeResidue(value) { | |||
var text = String(value || ''); | |||
/* Native MediaWiki snippets can begin or end in the middle of an HTML | |||
attribute. In that case there is no complete tag for the browser to | |||
remove, so fragments such as object-fit, drop-shadow or rgba survive. | |||
This pass removes only presentation/code residue, preserving guide text. */ | |||
var cssProperties = [ | |||
'object-fit', 'bject-fit', 'ject-fit', 'object-position', | |||
'filter', 'box-shadow', 'text-shadow', 'background', 'background-color', | |||
'background-image', 'border', 'border-radius', 'border-color', | |||
'border-width', 'border-style', 'color', 'display', 'position', | |||
'top', 'right', 'bottom', 'left', 'width', 'height', 'max-width', | |||
'min-width', 'max-height', 'min-height', 'margin', 'margin-top', | |||
'margin-right', 'margin-bottom', 'margin-left', 'padding', 'padding-top', | |||
'padding-right', 'padding-bottom', 'padding-left', 'font-size', | |||
'font-weight', 'font-family', 'font-style', 'line-height', | |||
'letter-spacing', 'text-align', 'text-decoration', 'white-space', | |||
'vertical-align', 'overflow', 'overflow-x', 'overflow-y', 'opacity', | |||
'transform', 'transform-origin', 'transition', 'animation', 'cursor', | |||
'z-index', 'float', 'clear', 'content', 'visibility', 'flex', | |||
'flex-grow', 'flex-shrink', 'flex-basis', 'flex-direction', 'flex-wrap', | |||
'align-items', 'align-content', 'align-self', 'justify-content', | |||
'justify-items', 'justify-self', 'gap', 'row-gap', 'column-gap', | |||
'grid', 'grid-template-columns', 'grid-template-rows', 'grid-column', | |||
'grid-row', 'list-style', 'list-style-type', 'object-fit' | |||
]; | |||
var propertyPattern = new RegExp( | |||
'(?:^|[\\s"\\\';])(?:' + cssProperties.join('|') + ')\\s*:\\s*[^;{}<>]{0,600};', | |||
'gi' | |||
); | |||
for (var i = 0; i < 4; i++) { | |||
var before = text; | |||
text = text.replace(propertyPattern, ' '); | |||
if (text === before) break; | |||
} | } | ||
}, | |||
}; | /* Remove truncated generic declarations only when they clearly contain a | ||
CSS value/function. This avoids deleting normal phrases such as | |||
"Reward: 10 Zeny". */ | |||
var cssValuePattern = /(?:^|[\s"';,.…])[-a-z]{2,}\s*:\s*[^;{}<>]{0,500}(?:rgba?\s*\(|hsla?\s*\(|drop-shadow\s*\(|linear-gradient\s*\(|radial-gradient\s*\(|url\s*\(|calc\s*\(|var\s*\(|\b(?:contain|cover|flex|grid|block|inline-block|absolute|relative|fixed|sticky|hidden|visible|solid|dashed|none|auto|center)\b|\d+(?:\.\d+)?(?:px|rem|em|vh|vw|%))[^;{}<>]*;?/gi; | |||
for (var j = 0; j < 3; j++) { | |||
var previous = text; | |||
text = text.replace(cssValuePattern, ' '); | |||
if (text === previous) break; | |||
} | |||
text = text | |||
.replace(/\b(?:class|style|id|src|srcset|alt|title|width|height|href|target|rel|loading|decoding|data-[a-z0-9_-]+|aria-[a-z0-9_-]+)\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)/gi, ' ') | |||
.replace(/\b(?:drop-shadow|rgba?|hsla?|linear-gradient|radial-gradient|translate(?:x|y|3d)?|scale(?:x|y|3d)?|rotate(?:x|y|z|3d)?|calc|var|url)\s*\([^;{}<>]{0,500}\)/gi, ' ') | |||
.replace(/(?:^|\s)(?:px|rem|em|vh|vw)\b/gi, ' ') | |||
.replace(/\s*(?:\/?>|<\/?)\s*/g, ' ') | |||
.replace(/["'`{}]+/g, ' ') | |||
.replace(/\.{2,}/g, '…') | |||
.replace(/\s+/g, ' ') | |||
.trim(); | |||
return text; | |||
} | |||
function stripHtml(value) { | |||
var text = decodeHtmlEntities(value) | |||
.replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, ' ') | |||
.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, ' ') | |||
.replace(/<!--([\s\S]*?)-->/g, ' '); | |||
/* Parse the decoded HTML in a detached element. This removes tags such | |||
as h2, img, span, table, code and their attributes without executing it. */ | |||
for (var i = 0; i < 2; i++) { | |||
var node = document.createElement('div'); | |||
node.innerHTML = text; | |||
var plain = node.textContent || node.innerText || ''; | |||
if (plain === text) break; | |||
text = plain; | |||
} | |||
/* Final defensive cleanup for malformed or partially escaped markup. */ | |||
text = decodeHtmlEntities(text) | |||
.replace(/<[^>]*>/g, ' ') | |||
.replace(/\b(?:class|style|id|src|alt|title|width|height|href|target|rel|loading)\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)/gi, ' ') | |||
.replace(/(?:^|\s)(?:div|span|table|tbody|thead|tr|td|th|img|h[1-6]|p|br|code|strong|em)(?:\s|$)/gi, ' '); | |||
return cleanSearchCodeResidue(cleanWikiText(text)) | |||
.replace(/\s+/g, ' ') | |||
.trim(); | |||
} | |||
function cleanWikiText(value) { | |||
var text = decodeHtmlEntities(value); | |||
text = text | |||
.replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, ' ') | |||
.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, ' ') | |||
.replace(/<ref\b[^>]*>[\s\S]*?<\/ref>/gi, ' ') | |||
.replace(/<ref\b[^>]*\/\s*>/gi, ' ') | |||
.replace(/<!--([\s\S]*?)-->/g, ' ') | |||
.replace(/\{\|[\s\S]*?\|\}/g, function (table) { | |||
return table.replace(/[|!{}]/g, ' '); | |||
}) | |||
.replace(/\[\[Category:[^\]]+\]\]/gi, ' ') | |||
.replace(/\[\[File:[^\]]+\]\]/gi, ' ') | |||
.replace(/\[\[Image:[^\]]+\]\]/gi, ' ') | |||
.replace(/\[\[[^\]|]+\|([^\]]+)\]\]/g, '$1') | |||
.replace(/\[\[([^\]]+)\]\]/g, '$1') | |||
.replace(/\[(?:https?:)?\/\/[^\s\]]+\s+([^\]]+)\]/g, '$1') | |||
.replace(/\[(?:https?:)?\/\/[^\]]+\]/g, ' ') | |||
.replace(/\{\{[^{}]*\}\}/g, ' ') | |||
.replace(/\{\{[^{}]*\}\}/g, ' ') | |||
.replace(/<[^>]+>/g, ' ') | |||
.replace(/'{2,5}/g, '') | |||
.replace(/={2,6}/g, ' ') | |||
.replace(/__[^_]+__/g, ' ') | |||
.replace(/[|{}]/g, ' ') | |||
.replace(/\s+/g, ' ') | |||
.trim(); | |||
return cleanSearchCodeResidue(text); | |||
} | |||
function extractKeywords(wikitext) { | |||
var keywords = []; | |||
var pattern = /<!--\s*OROZA-KEYWORDS\s*:\s*([\s\S]*?)-->/gi; | |||
var match; | |||
while ((match = pattern.exec(String(wikitext || '')))) { | |||
match[1].split(/[,;|\n]/).forEach(function (item) { | |||
item = item.trim(); | |||
if (item) keywords.push(item); | |||
}); | |||
} | |||
return keywords; | |||
} | |||
function extractCategories(wikitext) { | |||
var categories = []; | |||
var pattern = /\[\[Category\s*:\s*([^\]|]+)(?:\|[^\]]*)?\]\]/gi; | |||
var match; | |||
while ((match = pattern.exec(String(wikitext || '')))) { | |||
var category = cleanWikiText(match[1]); | |||
if (category && categories.indexOf(category) === -1) categories.push(category); | |||
} | |||
return categories; | |||
} | |||
function splitIntoSections(wikitext) { | |||
var source = decodeHtmlEntities(wikitext); | |||
/* Many Oroza Wiki guides are built with literal HTML instead of == Wiki | |||
headings ==. Convert HTML headings and accordion summaries into virtual | |||
Wiki headings so the search can identify the exact section. */ | |||
source = source.replace(/<h([1-6])\b[^>]*>([\s\S]*?)<\/h\1>/gi, function (match, level, title) { | |||
var headingLevel = Math.max(2, Math.min(6, parseInt(level, 10) || 2)); | |||
var marks = '======'.slice(0, headingLevel); | |||
var cleanTitle = stripHtml(title); | |||
return cleanTitle ? '\n' + marks + ' ' + cleanTitle + ' ' + marks + '\n' : '\n'; | |||
}); | |||
source = source.replace(/<summary\b[^>]*>([\s\S]*?)<\/summary>/gi, function (match, title) { | |||
var cleanTitle = stripHtml(title); | |||
return cleanTitle ? '\n=== ' + cleanTitle + ' ===\n' : '\n'; | |||
}); | |||
var lines = source.split(/\r?\n/); | |||
var sections = []; | |||
var currentTitle = ''; | |||
var currentLines = []; | |||
function flush() { | |||
var plain = cleanWikiText(currentLines.join('\n')); | |||
if (plain) { | |||
sections.push({ | |||
title: cleanWikiText(currentTitle), | |||
text: plain.slice(0, MAX_PAGE_TEXT) | |||
}); | |||
} | |||
currentLines = []; | |||
} | |||
lines.forEach(function (line) { | |||
var heading = line.match(/^\s*(={2,6})\s*(.*?)\s*\1\s*$/); | |||
if (heading) { | |||
flush(); | |||
currentTitle = stripHtml(heading[2]); | |||
} else { | |||
currentLines.push(line); | |||
} | |||
}); | |||
flush(); | |||
if (!sections.length) { | |||
var plain = cleanWikiText(source); | |||
if (plain) sections.push({ title: '', text: plain.slice(0, MAX_PAGE_TEXT) }); | |||
} | |||
return sections; | |||
} | |||
function decodePageName(href) { | |||
try { | |||
var url = new URL(href, window.location.href); | |||
var title = url.searchParams.get('title'); | |||
if (!title) { | |||
var marker = '/wiki/index.php/'; | |||
var markerIndex = url.pathname.indexOf(marker); | |||
if (markerIndex !== -1) title = url.pathname.slice(markerIndex + marker.length); | |||
} | |||
if (!title) return ''; | |||
return decodeURIComponent(title).replace(/_/g, ' ').trim(); | |||
} catch (e) { | |||
return ''; | |||
} | |||
} | |||
function getLocale() { | |||
var isEnglish = window.location.hostname === 'en.orozaro.com'; | |||
return isEnglish ? { | |||
placeholder: 'Search guides, commands, items, systems and content...', | |||
ariaLabel: 'Search inside every Oroza RO Wiki guide', | |||
searchButton: 'Search', | |||
loading: 'Searching titles, sections and guide content...', | |||
smartLoading: 'Building the smart guide index for the first search...', | |||
smartGroup: 'Matches found inside guides', | |||
apiGroup: 'Other Wiki results', | |||
menuGroup: 'Quick links', | |||
noResultsTitle: 'No results found', | |||
noResultsText: 'Try another spelling, a shorter phrase, or a related Ragnarok term.', | |||
categoryFallback: 'Wiki guide', | |||
sectionPrefix: 'Section', | |||
insideGuide: 'Inside guide', | |||
titleMatch: 'Guide title', | |||
aliasMatch: 'Related term', | |||
resultSingular: 'result', | |||
resultPlural: 'results', | |||
viewAll: 'View native search', | |||
suggestion: 'Did you mean', | |||
indexedGuides: 'guides indexed', | |||
indexUnavailable: 'Smart index unavailable; showing native results.', | |||
apiErrorTitle: 'Native search is temporarily unavailable', | |||
apiErrorText: 'Smart guide matches are still available. Press Enter to open MediaWiki search.', | |||
introduction: 'Introduction' | |||
} : { | |||
placeholder: 'Buscar guías, comandos, objetos, sistemas y contenido...', | |||
ariaLabel: 'Buscar dentro de todas las guías de la Wiki de Oroza RO', | |||
searchButton: 'Buscar', | |||
loading: 'Buscando en títulos, secciones y contenido de las guías...', | |||
smartLoading: 'Creando el índice inteligente de las guías por primera vez...', | |||
smartGroup: 'Coincidencias encontradas dentro de las guías', | |||
apiGroup: 'Otros resultados de la Wiki', | |||
menuGroup: 'Accesos rápidos', | |||
noResultsTitle: 'No encontramos resultados', | |||
noResultsText: 'Prueba otra escritura, una frase más corta o un término relacionado de Ragnarok.', | |||
categoryFallback: 'Guía de la Wiki', | |||
sectionPrefix: 'Sección', | |||
insideGuide: 'Dentro de la guía', | |||
titleMatch: 'Título de la guía', | |||
aliasMatch: 'Término relacionado', | |||
resultSingular: 'resultado', | |||
resultPlural: 'resultados', | |||
viewAll: 'Ver búsqueda nativa', | |||
suggestion: 'Quizá quisiste decir', | |||
indexedGuides: 'guías indexadas', | |||
indexUnavailable: 'Índice inteligente no disponible; se muestran resultados nativos.', | |||
apiErrorTitle: 'La búsqueda nativa no está disponible temporalmente', | |||
apiErrorText: 'Las coincidencias del índice inteligente siguen disponibles. Presiona Enter para abrir la búsqueda de MediaWiki.', | |||
introduction: 'Introducción' | |||
}; | |||
} | |||
function getSynonymMap(isEnglish) { | |||
var shared = { | |||
'@aa': ['auto attack', 'auto ataque', 'autoatk', 'automatic attack'], | |||
'aa': ['auto attack', 'auto ataque', 'autoatk'], | |||
'bg': ['battleground', 'battlegrounds', 'campo de batalla'], | |||
'woe': ['war of emperium', 'guild war', 'guerra de emperium'], | |||
'mvp': ['boss', 'jefe', 'monster boss'], | |||
'npc': ['non player character', 'personaje no jugador'], | |||
'exp': ['experience', 'experiencia'], | |||
'zeny': ['money', 'currency', 'dinero', 'moneda'] | |||
}; | |||
var english = { | |||
'beginner': ['starter', 'new player', 'newbie', 'first steps', 'getting started'], | |||
'newbie': ['beginner', 'starter', 'new player', 'first steps'], | |||
'starter': ['beginner', 'new player', 'newbie', 'first steps', '@starter'], | |||
'job': ['class', 'profession', 'job change'], | |||
'class': ['job', 'profession', 'job change'], | |||
'card': ['cards', 'monster card'], | |||
'cards': ['card', 'monster cards'], | |||
'hat': ['headgear', 'head gear', 'costume'], | |||
'headgear': ['hat', 'head gear', 'costume'], | |||
'refine': ['refinement', 'upgrade', 'safe refine'], | |||
'refinement': ['refine', 'upgrade', 'safe refine'], | |||
'enchant': ['enchantment', 'bonus', 'random option'], | |||
'quest': ['mission', 'task'], | |||
'mission': ['quest', 'task'], | |||
'party': ['group', 'even share'], | |||
'group': ['party', 'even share'], | |||
'drop': ['loot', 'reward', 'item drop'], | |||
'loot': ['drop', 'reward'], | |||
'pet': ['pokemon', 'companion', 'homunculus'], | |||
'pokemon': ['pet', 'companion'], | |||
'command': ['commands', '@command', 'chat command'], | |||
'donation': ['donate', 'cash points', 'support server'], | |||
'event': ['events', 'activity'], | |||
'skill': ['skills', 'ability'], | |||
'equipment': ['gear', 'weapon', 'armor'], | |||
'gear': ['equipment', 'weapon', 'armor'] | |||
}; | |||
var spanish = { | |||
'principiante': ['starter', 'jugador nuevo', 'novato', 'primeros pasos', 'inicio'], | |||
'novato': ['principiante', 'starter', 'jugador nuevo', 'primeros pasos'], | |||
'nuevo': ['principiante', 'starter', 'jugador nuevo', 'primeros pasos'], | |||
'starter': ['principiante', 'jugador nuevo', 'novato', 'primeros pasos', '@starter'], | |||
'job': ['clase', 'profesión', 'cambio de job'], | |||
'clase': ['job', 'profesión', 'cambio de clase'], | |||
'carta': ['cartas', 'card', 'monster card'], | |||
'cartas': ['carta', 'cards', 'monster cards'], | |||
'hat': ['sombrero', 'headgear', 'costume'], | |||
'sombrero': ['hat', 'headgear', 'costume'], | |||
'refinar': ['refinamiento', 'mejorar equipo', 'refine'], | |||
'refinamiento': ['refinar', 'mejorar equipo', 'safe refine'], | |||
'encantar': ['encantamiento', 'bonus', 'opción aleatoria'], | |||
'encantamiento': ['encantar', 'bonus', 'random option'], | |||
'quest': ['misión', 'mision', 'tarea'], | |||
'misión': ['quest', 'mision', 'tarea'], | |||
'mision': ['quest', 'misión', 'tarea'], | |||
'party': ['grupo', 'even share', 'compartir experiencia'], | |||
'grupo': ['party', 'even share', 'compartir experiencia'], | |||
'drop': ['loot', 'recompensa', 'objeto'], | |||
'loot': ['drop', 'recompensa'], | |||
'mascota': ['pokemon', 'pet', 'homúnculo', 'homunculo'], | |||
'pokemon': ['mascota', 'pet', 'compañero'], | |||
'comando': ['comandos', '@comando', 'chat command'], | |||
'donación': ['donar', 'cash points', 'apoyar servidor'], | |||
'donacion': ['donar', 'cash points', 'apoyar servidor'], | |||
'evento': ['eventos', 'actividad'], | |||
'skill': ['skills', 'habilidad'], | |||
'equipo': ['gear', 'arma', 'armadura'] | |||
}; | |||
var result = {}; | |||
Object.keys(shared).forEach(function (key) { result[normalizeText(key)] = shared[key]; }); | |||
Object.keys(isEnglish ? english : spanish).forEach(function (key) { | |||
result[normalizeText(key)] = (isEnglish ? english : spanish)[key]; | |||
}); | |||
return result; | |||
} | |||
function tokenize(value) { | |||
var stopWords = { | |||
'the': 1, 'a': 1, 'an': 1, 'and': 1, 'or': 1, 'of': 1, 'to': 1, 'in': 1, 'for': 1, | |||
'el': 1, 'la': 1, 'los': 1, 'las': 1, 'un': 1, 'una': 1, 'y': 1, 'o': 1, 'de': 1, | |||
'del': 1, 'en': 1, 'para': 1, 'como': 1, 'how': 1, 'what': 1, 'que': 1 | |||
}; | |||
return normalizeText(value).split(' ').filter(function (token) { | |||
return token && (token.length >= 2 || token.charAt(0) === '@') && !stopWords[token]; | |||
}); | |||
} | |||
function unique(values) { | |||
var seen = Object.create(null); | |||
return values.filter(function (value) { | |||
var key = normalizeText(value); | |||
if (!key || seen[key]) return false; | |||
seen[key] = true; | |||
return true; | |||
}); | |||
} | |||
function expandQuery(query, synonymMap) { | |||
var originalTokens = tokenize(query); | |||
var expandedPhrases = []; | |||
originalTokens.forEach(function (token) { | |||
var synonyms = synonymMap[token] || []; | |||
synonyms.forEach(function (synonym) { | |||
expandedPhrases.push(synonym); | |||
}); | |||
}); | |||
return { | |||
phrase: normalizeText(query), | |||
originalTokens: unique(originalTokens), | |||
expandedTokens: unique(expandedPhrases.reduce(function (all, phrase) { | |||
return all.concat(tokenize(phrase)); | |||
}, [])), | |||
expandedPhrases: unique(expandedPhrases.map(normalizeText)) | |||
}; | |||
} | |||
function collectMenuEntries() { | |||
var entries = []; | |||
var titles = document.querySelectorAll('.oroza-sidebar .menu-title'); | |||
Array.prototype.forEach.call(titles, function (titleNode) { | |||
var category = (titleNode.textContent || '').replace(/\s+/g, ' ').trim(); | |||
var list = titleNode.nextElementSibling; | |||
if (!list || String(list.tagName).toLowerCase() !== 'ul') return; | |||
Array.prototype.forEach.call(list.querySelectorAll('a[href]'), function (link) { | |||
var title = decodePageName(link.getAttribute('href')); | |||
var labelNode = link.querySelector('.menu-text'); | |||
var label = labelNode ? labelNode.textContent : link.textContent; | |||
label = String(label || '').replace(/›/g, '').replace(/\s+/g, ' ').trim(); | |||
title = title || label; | |||
if (!title || !link.href) return; | |||
entries.push({ | |||
title: title, | |||
label: label || title, | |||
category: category, | |||
href: link.href, | |||
normalized: normalizeText(title + ' ' + label + ' ' + category) | |||
}); | |||
}); | |||
}); | |||
return entries; | |||
} | |||
function findMenuMatches(entries, query) { | |||
var normalizedQuery = normalizeText(query); | |||
if (!normalizedQuery) return []; | |||
return entries.map(function (entry) { | |||
var normalizedTitle = normalizeText(entry.title + ' ' + entry.label); | |||
var score = 0; | |||
if (normalizedTitle === normalizedQuery) score = 100; | |||
else if (normalizedTitle.indexOf(normalizedQuery) === 0) score = 80; | |||
else if (normalizedTitle.indexOf(normalizedQuery) !== -1) score = 60; | |||
else if (entry.normalized.indexOf(normalizedQuery) !== -1) score = 40; | |||
return { entry: entry, score: score }; | |||
}).filter(function (item) { | |||
return item.score > 0; | |||
}).sort(function (a, b) { | |||
return b.score - a.score || a.entry.label.localeCompare(b.entry.label); | |||
}).slice(0, MENU_LIMIT).map(function (item) { | |||
return item.entry; | |||
}); | |||
} | |||
function buildCategoryMap(entries) { | |||
var map = Object.create(null); | |||
entries.forEach(function (entry) { | |||
map[normalizeText(entry.title)] = entry.category; | |||
map[normalizeText(entry.label)] = entry.category; | |||
}); | |||
return map; | |||
} | |||
function getCacheKey() { | |||
return 'oroza-smart-wiki-index-v' + CACHE_VERSION + ':' + window.location.hostname; | |||
} | |||
function readIndexCache() { | |||
try { | |||
var raw = window.localStorage.getItem(getCacheKey()); | |||
if (!raw) return null; | |||
var payload = JSON.parse(raw); | |||
if (!payload || !Array.isArray(payload.pages) || !payload.createdAt) return null; | |||
if (Date.now() - payload.createdAt > INDEX_TTL_MS) return null; | |||
return payload.pages; | |||
} catch (e) { | |||
return null; | |||
} | |||
} | |||
function writeIndexCache(pages) { | |||
try { | |||
window.localStorage.setItem(getCacheKey(), JSON.stringify({ | |||
createdAt: Date.now(), | |||
pages: pages | |||
})); | |||
} catch (e) {} | |||
} | |||
function clearIndexCache() { | |||
try { window.localStorage.removeItem(getCacheKey()); } catch (e) {} | |||
memoryIndex = null; | |||
indexPromise = null; | |||
} | |||
function getRevisionContent(page) { | |||
var revision = page && page.revisions && page.revisions[0]; | |||
if (!revision) return ''; | |||
if (revision.slots && revision.slots.main) { | |||
return revision.slots.main.content || revision.slots.main['*'] || ''; | |||
} | |||
return revision.content || revision['*'] || ''; | |||
} | |||
function createIndexedPage(page, categoryMap) { | |||
var wikitext = getRevisionContent(page); | |||
var title = page.title || ''; | |||
var sections = splitIntoSections(wikitext); | |||
var keywords = extractKeywords(wikitext); | |||
var categories = extractCategories(wikitext); | |||
var category = categoryMap[normalizeText(title)] || categories[0] || ''; | |||
return { | |||
title: title, | |||
normalizedTitle: normalizeText(title), | |||
url: (window.mw && mw.util) ? mw.util.getUrl(title) : '/wiki/index.php?title=' + encodeURIComponent(title.replace(/ /g, '_')), | |||
category: category, | |||
keywords: keywords, | |||
normalizedKeywords: normalizeText(keywords.join(' ')), | |||
sections: sections.map(function (section) { | |||
return { | |||
title: section.title, | |||
normalizedTitle: normalizeText(section.title), | |||
text: section.text, | |||
normalizedText: normalizeText(section.text) | |||
}; | |||
}) | |||
}; | |||
} | |||
function buildSmartIndex(api, categoryMap) { | |||
var pages = []; | |||
var continueParams = {}; | |||
function fetchBatch() { | |||
var params = { | |||
action: 'query', | |||
generator: 'allpages', | |||
gapnamespace: 0, | |||
gapfilterredir: 'nonredirects', | |||
gaplimit: 'max', | |||
prop: 'revisions', | |||
rvprop: 'content', | |||
rvslots: 'main', | |||
rvlimit: 1, | |||
formatversion: 2, | |||
utf8: 1 | |||
}; | |||
Object.keys(continueParams).forEach(function (key) { | |||
params[key] = continueParams[key]; | |||
}); | |||
return api.get(params).then(function (data) { | |||
var batch = data && data.query && Array.isArray(data.query.pages) ? data.query.pages : []; | |||
batch.forEach(function (page) { | |||
if (pages.length >= MAX_INDEX_PAGES || page.missing) return; | |||
pages.push(createIndexedPage(page, categoryMap)); | |||
}); | |||
if (data && data.continue && pages.length < MAX_INDEX_PAGES) { | |||
continueParams = data.continue; | |||
return fetchBatch(); | |||
} | |||
writeIndexCache(pages); | |||
memoryIndex = pages; | |||
return pages; | |||
}); | |||
} | |||
return fetchBatch(); | |||
} | |||
function ensureSmartIndex(api, categoryMap) { | |||
if (memoryIndex) return Promise.resolve(memoryIndex); | |||
var cached = readIndexCache(); | |||
if (cached) { | |||
memoryIndex = cached; | |||
return Promise.resolve(cached); | |||
} | |||
if (!api) return Promise.reject(new Error('MediaWiki API unavailable')); | |||
if (!indexPromise) { | |||
indexPromise = buildSmartIndex(api, categoryMap).catch(function (error) { | |||
indexPromise = null; | |||
throw error; | |||
}); | |||
} | |||
return indexPromise; | |||
} | |||
function countOccurrences(text, token) { | |||
if (!token) return 0; | |||
var count = 0; | |||
var position = 0; | |||
while ((position = text.indexOf(token, position)) !== -1) { | |||
count++; | |||
position += Math.max(token.length, 1); | |||
if (count >= 8) break; | |||
} | |||
return count; | |||
} | |||
function makeSnippet(text, queryData, maxLength) { | |||
var plain = cleanSearchCodeResidue(stripHtml(text)).replace(/\s+/g, ' ').trim(); | |||
if (!plain) return ''; | |||
var lower = plain.toLowerCase(); | |||
var candidates = [String(queryData.raw || '').toLowerCase()] | |||
.concat(queryData.originalTokens || []) | |||
.concat(queryData.expandedTokens || []); | |||
var position = -1; | |||
candidates.some(function (candidate) { | |||
if (!candidate) return false; | |||
position = lower.indexOf(candidate.toLowerCase()); | |||
return position !== -1; | |||
}); | |||
if (position < 0) position = 0; | |||
var radius = Math.floor((maxLength || 220) / 2); | |||
var start = Math.max(0, position - radius); | |||
var end = Math.min(plain.length, start + (maxLength || 220)); | |||
if (end - start < (maxLength || 220) && start > 0) { | |||
start = Math.max(0, end - (maxLength || 220)); | |||
} | |||
var snippet = cleanSearchCodeResidue(plain.slice(start, end)).trim(); | |||
if (!snippet) return ''; | |||
if (start > 0) snippet = '…' + snippet; | |||
if (end < plain.length) snippet += '…'; | |||
return snippet; | |||
} | |||
function scoreSection(page, section, queryData) { | |||
var title = page.normalizedTitle; | |||
var sectionTitle = section.normalizedTitle; | |||
var text = section.normalizedText; | |||
var keywords = page.normalizedKeywords; | |||
var phrase = queryData.phrase; | |||
var originalTokens = queryData.originalTokens; | |||
var expandedTokens = queryData.expandedTokens; | |||
var score = 0; | |||
var matchType = ''; | |||
if (title === phrase) { | |||
score += 320; | |||
matchType = 'title'; | |||
} else if (title.indexOf(phrase) === 0) { | |||
score += 260; | |||
matchType = 'title'; | |||
} else if (title.indexOf(phrase) !== -1) { | |||
score += 220; | |||
matchType = 'title'; | |||
} | |||
if (keywords && phrase && keywords.indexOf(phrase) !== -1) { | |||
score += 190; | |||
if (!matchType) matchType = 'alias'; | |||
} | |||
if (sectionTitle === phrase) { | |||
score += 185; | |||
if (!matchType) matchType = 'section'; | |||
} else if (sectionTitle && sectionTitle.indexOf(phrase) !== -1) { | |||
score += 160; | |||
if (!matchType) matchType = 'section'; | |||
} | |||
if (phrase && text.indexOf(phrase) !== -1) { | |||
score += 135; | |||
if (!matchType) matchType = 'content'; | |||
} | |||
var originalMatches = 0; | |||
var expandedMatches = 0; | |||
var frequency = 0; | |||
originalTokens.forEach(function (token) { | |||
var inTitle = title.indexOf(token) !== -1; | |||
var inSection = sectionTitle.indexOf(token) !== -1; | |||
var inKeywords = keywords.indexOf(token) !== -1; | |||
var occurrences = countOccurrences(text, token); | |||
if (inTitle || inSection || inKeywords || occurrences) originalMatches++; | |||
if (inTitle) score += 24; | |||
if (inSection) score += 19; | |||
if (inKeywords) score += 17; | |||
if (occurrences) score += Math.min(occurrences, 5) * 7; | |||
frequency += occurrences; | |||
}); | |||
expandedTokens.forEach(function (token) { | |||
if (title.indexOf(token) !== -1 || sectionTitle.indexOf(token) !== -1 || keywords.indexOf(token) !== -1 || text.indexOf(token) !== -1) { | |||
expandedMatches++; | |||
} | |||
}); | |||
var coverage = originalTokens.length ? originalMatches / originalTokens.length : 0; | |||
if (originalTokens.length > 1 && originalMatches === originalTokens.length) score += 65; | |||
score += Math.round(coverage * 50); | |||
score += Math.min(expandedMatches, 4) * 8; | |||
score += Math.min(frequency, 8) * 2; | |||
var qualifies = false; | |||
if (phrase && (title.indexOf(phrase) !== -1 || sectionTitle.indexOf(phrase) !== -1 || keywords.indexOf(phrase) !== -1 || text.indexOf(phrase) !== -1)) { | |||
qualifies = true; | |||
} else if (originalTokens.length === 1) { | |||
qualifies = originalMatches === 1 || expandedMatches > 0; | |||
} else if (originalTokens.length > 1) { | |||
qualifies = coverage >= 0.67 || (coverage >= 0.5 && expandedMatches > 0); | |||
} | |||
return { | |||
qualifies: qualifies, | |||
score: score, | |||
matchType: matchType || (expandedMatches ? 'alias' : 'content') | |||
}; | |||
} | |||
function searchSmartIndex(index, rawQuery, labels, synonymMap) { | |||
var queryData = expandQuery(rawQuery, synonymMap); | |||
queryData.raw = rawQuery; | |||
var results = []; | |||
(index || []).forEach(function (page) { | |||
var best = null; | |||
(page.sections || []).forEach(function (section) { | |||
var scored = scoreSection(page, section, queryData); | |||
if (!scored.qualifies) return; | |||
if (!best || scored.score > best.score) { | |||
best = { | |||
score: scored.score, | |||
matchType: scored.matchType, | |||
section: section | |||
}; | |||
} | |||
}); | |||
if (!best) return; | |||
var location = labels.insideGuide; | |||
if (best.matchType === 'title') location = labels.titleMatch; | |||
else if (best.matchType === 'alias') location = labels.aliasMatch; | |||
if (best.section.title) { | |||
location += ' · ' + labels.sectionPrefix + ': ' + best.section.title; | |||
} | |||
results.push({ | |||
title: page.title, | |||
label: page.title, | |||
href: page.url, | |||
category: page.category || labels.categoryFallback, | |||
location: location, | |||
snippet: makeSnippet(best.section.text, queryData, 240), | |||
score: best.score | |||
}); | |||
}); | |||
return results.sort(function (a, b) { | |||
return b.score - a.score || a.title.localeCompare(b.title); | |||
}).slice(0, SMART_LIMIT); | |||
} | |||
function ensureSearchHighlightStyles() { | |||
if (document.getElementById('oroza-search-highlight-styles')) return; | |||
var style = document.createElement('style'); | |||
style.id = 'oroza-search-highlight-styles'; | |||
style.textContent = | |||
'.oroza-search-highlight{' + | |||
'display:inline;' + | |||
'padding:1px 3px;' + | |||
'border-radius:4px;' + | |||
'background:linear-gradient(180deg,#fff0a6 0%,#ffd84d 100%);' + | |||
'color:#17324f!important;' + | |||
'font-weight:800;' + | |||
'box-shadow:inset 0 -1px 0 rgba(185,132,0,.22);' + | |||
'text-decoration:none;' + | |||
'}' + | |||
'.oroza-search-result-title .oroza-search-highlight{' + | |||
'padding:1px 4px;' + | |||
'background:linear-gradient(180deg,#ffe982 0%,#ffc928 100%);' + | |||
'}' + | |||
'.oroza-search-result-link[aria-selected="true"] .oroza-search-highlight{' + | |||
'background:#fff4bd;' + | |||
'color:#132a43!important;' + | |||
'}' + | |||
'@media (prefers-reduced-motion:reduce){' + | |||
'.oroza-search-highlight{transition:none;}' + | |||
'}'; | |||
(document.head || document.documentElement).appendChild(style); | |||
} | |||
function foldTextWithMap(value) { | |||
var source = String(value || ''); | |||
var folded = ''; | |||
var map = []; | |||
for (var i = 0; i < source.length; i++) { | |||
var character = source.charAt(i); | |||
var normalized = character; | |||
try { | |||
normalized = character.normalize('NFD').replace(/[\u0300-\u036f]/g, ''); | |||
} catch (e) {} | |||
normalized = normalized | |||
.toLowerCase() | |||
.replace(/[\u2010-\u2015]/g, '-'); | |||
for (var j = 0; j < normalized.length; j++) { | |||
folded += normalized.charAt(j); | |||
map.push(i); | |||
} | |||
} | |||
return { text: folded, map: map }; | |||
} | |||
function getHighlightTerms(query) { | |||
var raw = String(query || '').replace(/\s+/g, ' ').trim(); | |||
if (!raw) return []; | |||
var terms = []; | |||
var seen = Object.create(null); | |||
function addTerm(term) { | |||
term = String(term || '').trim(); | |||
if (!term) return; | |||
var key = normalizeText(term); | |||
if (!key || seen[key]) return; | |||
seen[key] = true; | |||
terms.push(term); | |||
} | |||
addTerm(raw); | |||
var tokens = raw.split(/\s+/); | |||
tokens.forEach(function (token) { | |||
var clean = token.replace(/^[^\w@+#áéíóúüñçàèìòùâêîôûäëïöü]+|[^\w@+#áéíóúüñçàèìòùâêîôûäëïöü]+$/gi, ''); | |||
if (!clean) return; | |||
/* Avoid painting common two-letter words in long questions, but keep | |||
useful short game terms such as IA, BG, HP or SP when searched alone. */ | |||
if (clean.length >= 3 || tokens.length === 1) addTerm(clean); | |||
}); | |||
return terms.sort(function (a, b) { return b.length - a.length; }); | |||
} | |||
function findHighlightRanges(text, query) { | |||
var source = String(text || ''); | |||
var terms = getHighlightTerms(query); | |||
if (!source || !terms.length) return []; | |||
var foldedSource = foldTextWithMap(source); | |||
var ranges = []; | |||
terms.forEach(function (term) { | |||
var foldedTerm = foldTextWithMap(term).text; | |||
if (!foldedTerm) return; | |||
var position = 0; | |||
while ((position = foldedSource.text.indexOf(foldedTerm, position)) !== -1) { | |||
var start = foldedSource.map[position]; | |||
var lastMapIndex = position + foldedTerm.length - 1; | |||
var end = typeof foldedSource.map[lastMapIndex] === 'number' | |||
? foldedSource.map[lastMapIndex] + 1 | |||
: start + term.length; | |||
ranges.push({ start: start, end: end }); | |||
position += Math.max(foldedTerm.length, 1); | |||
if (ranges.length >= 60) return; | |||
} | |||
}); | |||
ranges.sort(function (a, b) { | |||
return a.start - b.start || b.end - a.end; | |||
}); | |||
var merged = []; | |||
ranges.forEach(function (range) { | |||
var previous = merged.length ? merged[merged.length - 1] : null; | |||
if (!previous || range.start > previous.end) { | |||
merged.push({ start: range.start, end: range.end }); | |||
} else if (range.end > previous.end) { | |||
previous.end = range.end; | |||
} | |||
}); | |||
return merged; | |||
} | |||
function appendHighlightedText(parent, text, query) { | |||
var source = String(text || ''); | |||
var ranges = findHighlightRanges(source, query); | |||
if (!ranges.length) { | |||
parent.appendChild(document.createTextNode(source)); | |||
return parent; | |||
} | |||
var cursor = 0; | |||
ranges.forEach(function (range) { | |||
if (range.start > cursor) { | |||
parent.appendChild(document.createTextNode(source.slice(cursor, range.start))); | |||
} | |||
var mark = document.createElement('mark'); | |||
mark.className = 'oroza-search-highlight'; | |||
mark.textContent = source.slice(range.start, range.end); | |||
parent.appendChild(mark); | |||
cursor = range.end; | |||
}); | |||
if (cursor < source.length) { | |||
parent.appendChild(document.createTextNode(source.slice(cursor))); | |||
} | |||
return parent; | |||
} | |||
function createElement(tag, className, text) { | |||
var element = document.createElement(tag); | |||
if (className) element.className = className; | |||
if (typeof text !== 'undefined') element.textContent = text; | |||
return element; | |||
} | |||
function initSearch(root) { | |||
var widget = root.querySelector ? root.querySelector(SELECTOR) : null; | |||
if (!widget || widget.dataset.orozaSmartReady === '23') return; | |||
widget.dataset.orozaSmartReady = '23'; | |||
var form = widget.querySelector('.oroza-global-search-form'); | |||
var input = widget.querySelector('.oroza-global-search-input'); | |||
var results = widget.querySelector('.oroza-global-search-results'); | |||
var clearButton = widget.querySelector('[data-oroza-search-clear]'); | |||
var submitText = widget.querySelector('.oroza-global-search-submit span:last-child'); | |||
var helper = widget.querySelector('.oroza-global-search-helper'); | |||
if (!form || !input || !results || !clearButton) return; | |||
ensureSearchHighlightStyles(); | |||
var labels = getLocale(); | |||
var isEnglish = window.location.hostname === 'en.orozaro.com'; | |||
var synonymMap = getSynonymMap(isEnglish); | |||
var menuEntries = collectMenuEntries(); | |||
var categoryMap = buildCategoryMap(menuEntries); | |||
var apiCache = Object.create(null); | |||
var debounceTimer = null; | |||
var requestSequence = 0; | |||
var activeIndex = -1; | |||
var api = (window.mw && mw.Api) ? new mw.Api() : null; | |||
input.placeholder = labels.placeholder; | |||
input.setAttribute('aria-label', labels.ariaLabel); | |||
if (submitText) submitText.textContent = labels.searchButton; | |||
if (helper) { | |||
helper.innerHTML = isEnglish | |||
? 'Searches inside every guide, section and paragraph. Use <kbd>↑</kbd> <kbd>↓</kbd> and <kbd>Enter</kbd>.' | |||
: 'Busca dentro de cada guía, sección y párrafo. Usa <kbd>↑</kbd> <kbd>↓</kbd> y <kbd>Enter</kbd>.'; | |||
} | |||
function getNativeSearchUrl(query) { | |||
try { | |||
var url = new URL(form.action, window.location.href); | |||
url.searchParams.set('title', 'Special:Search'); | |||
url.searchParams.set('search', query); | |||
url.searchParams.set('fulltext', '1'); | |||
return url.toString(); | |||
} catch (e) { | |||
return '/wiki/index.php?title=Special:Search&fulltext=1&search=' + encodeURIComponent(query); | |||
} | |||
} | |||
function setOpen(isOpen) { | |||
results.hidden = !isOpen; | |||
input.setAttribute('aria-expanded', isOpen ? 'true' : 'false'); | |||
if (!isOpen) activeIndex = -1; | |||
} | |||
function clearResults() { | |||
while (results.firstChild) results.removeChild(results.firstChild); | |||
setOpen(false); | |||
} | |||
function addGroupTitle(text) { | |||
results.appendChild(createElement('div', 'oroza-search-group-title', text)); | |||
} | |||
function addResultItem(item, query) { | |||
var link = createElement('a', 'oroza-search-result-link'); | |||
link.href = item.href; | |||
link.setAttribute('role', 'option'); | |||
link.setAttribute('aria-selected', 'false'); | |||
var main = createElement('div', 'oroza-search-result-main'); | |||
var title = createElement('span', 'oroza-search-result-title'); | |||
appendHighlightedText(title, item.label || item.title, query); | |||
main.appendChild(title); | |||
if (item.location) { | |||
var location = createElement('span', 'oroza-search-result-location'); | |||
appendHighlightedText(location, item.location, query); | |||
main.appendChild(location); | |||
} | |||
if (item.snippet) { | |||
var safeSnippet = cleanSearchCodeResidue(stripHtml(item.snippet)); | |||
if (safeSnippet) { | |||
var snippet = createElement('p', 'oroza-search-result-snippet'); | |||
appendHighlightedText(snippet, safeSnippet, query); | |||
main.appendChild(snippet); | |||
} | |||
} | |||
link.appendChild(main); | |||
link.appendChild(createElement('span', 'oroza-search-result-category', item.category || labels.categoryFallback)); | |||
results.appendChild(link); | |||
} | |||
function addSuggestion(suggestion) { | |||
if (!suggestion) return; | |||
var box = createElement('div', 'oroza-search-suggestion'); | |||
box.appendChild(document.createTextNode(labels.suggestion + ': ')); | |||
var button = createElement('button', '', suggestion); | |||
button.type = 'button'; | |||
button.addEventListener('click', function () { | |||
input.value = suggestion; | |||
input.dispatchEvent(new Event('input', { bubbles: true })); | |||
input.focus(); | |||
}); | |||
box.appendChild(button); | |||
results.appendChild(box); | |||
} | |||
function addFooter(query, totalHits, indexState) { | |||
var footer = createElement('div', 'oroza-search-footer'); | |||
var left = createElement('span', 'oroza-search-footer-count'); | |||
var countText = ''; | |||
if (typeof totalHits === 'number') { | |||
countText = totalHits + ' ' + (totalHits === 1 ? labels.resultSingular : labels.resultPlural); | |||
} | |||
left.textContent = countText; | |||
footer.appendChild(left); | |||
if (indexState) { | |||
footer.appendChild(createElement('span', 'oroza-search-index-state', indexState)); | |||
} | |||
var link = createElement('a', '', labels.viewAll + ' →'); | |||
link.href = getNativeSearchUrl(query); | |||
footer.appendChild(link); | |||
results.appendChild(footer); | |||
} | |||
function renderLoading(menuMatches, query) { | |||
while (results.firstChild) results.removeChild(results.firstChild); | |||
if (menuMatches.length) { | |||
addGroupTitle(labels.menuGroup); | |||
menuMatches.forEach(function (item) { | |||
addResultItem({ | |||
title: item.title, | |||
label: item.label, | |||
href: item.href, | |||
category: item.category, | |||
location: labels.titleMatch, | |||
snippet: '' | |||
}, query); | |||
}); | |||
} | |||
var status = createElement('div', 'oroza-search-status'); | |||
status.appendChild(createElement('span', 'oroza-search-spinner')); | |||
status.appendChild(createElement('span', '', labels.loading)); | |||
results.appendChild(status); | |||
setOpen(true); | |||
} | |||
function renderState(query, state) { | |||
while (results.firstChild) results.removeChild(results.firstChild); | |||
var seen = Object.create(null); | |||
var smartResults = []; | |||
var apiResults = []; | |||
var menuMatches = []; | |||
(state.smart || []).forEach(function (item) { | |||
var key = normalizeText(item.title); | |||
if (!key || seen[key]) return; | |||
seen[key] = true; | |||
smartResults.push(item); | |||
}); | |||
(state.api || []).forEach(function (item) { | |||
var key = normalizeText(item.title); | |||
if (!key || seen[key]) return; | |||
seen[key] = true; | |||
apiResults.push(item); | |||
}); | |||
(state.menu || []).forEach(function (item) { | |||
var key = normalizeText(item.title); | |||
if (!key || seen[key]) return; | |||
seen[key] = true; | |||
menuMatches.push(item); | |||
}); | |||
if (smartResults.length) { | |||
addGroupTitle(labels.smartGroup); | |||
smartResults.forEach(function (item) { addResultItem(item, query); }); | |||
} | |||
if (apiResults.length) { | |||
addGroupTitle(labels.apiGroup); | |||
apiResults.forEach(function (item) { addResultItem(item, query); }); | |||
} | |||
if (menuMatches.length) { | |||
addGroupTitle(labels.menuGroup); | |||
menuMatches.forEach(function (item) { | |||
addResultItem({ | |||
title: item.title, | |||
label: item.label, | |||
href: item.href, | |||
category: item.category, | |||
location: labels.titleMatch, | |||
snippet: '' | |||
}, query); | |||
}); | |||
} | |||
if (!smartResults.length && !apiResults.length && !menuMatches.length) { | |||
var empty = createElement('div', 'oroza-search-empty'); | |||
empty.appendChild(createElement('span', 'oroza-search-empty-icon', '🔍')); | |||
empty.appendChild(createElement('p', 'oroza-search-empty-title', labels.noResultsTitle)); | |||
empty.appendChild(createElement('p', 'oroza-search-empty-text', labels.noResultsText)); | |||
results.appendChild(empty); | |||
} | |||
if (state.smartLoading) { | |||
var loading = createElement('div', 'oroza-search-smart-loading'); | |||
loading.appendChild(createElement('span', 'oroza-search-spinner')); | |||
loading.appendChild(createElement('span', '', labels.smartLoading)); | |||
results.appendChild(loading); | |||
} | |||
if (state.apiFailed && !apiResults.length) { | |||
var warning = createElement('div', 'oroza-search-inline-warning'); | |||
warning.textContent = labels.apiErrorTitle + '. ' + labels.apiErrorText; | |||
results.appendChild(warning); | |||
} | |||
addSuggestion(state.suggestion); | |||
var total = typeof state.totalHits === 'number' | |||
? Math.max(state.totalHits, smartResults.length) | |||
: smartResults.length + apiResults.length + menuMatches.length; | |||
var indexState = ''; | |||
if (typeof state.indexCount === 'number') indexState = state.indexCount + ' ' + labels.indexedGuides; | |||
else if (state.smartFailed) indexState = labels.indexUnavailable; | |||
addFooter(query, total, indexState); | |||
setOpen(true); | |||
} | |||
function transformApiData(data) { | |||
var query = data && data.query ? data.query : {}; | |||
var searchInfo = query.searchinfo || {}; | |||
var items = Array.isArray(query.search) ? query.search : []; | |||
return { | |||
totalHits: typeof searchInfo.totalhits === 'number' ? searchInfo.totalhits : items.length, | |||
suggestion: searchInfo.suggestion || '', | |||
items: items.map(function (item) { | |||
var category = categoryMap[normalizeText(item.title)] || labels.categoryFallback; | |||
var section = cleanSearchCodeResidue(stripHtml(item.sectiontitle || '')); | |||
var snippet = cleanSearchCodeResidue(stripHtml(item.snippet || '')); | |||
var location = labels.insideGuide; | |||
if (section) location += ' · ' + labels.sectionPrefix + ': ' + section; | |||
return { | |||
title: item.title, | |||
label: item.title, | |||
href: (window.mw && mw.util) ? mw.util.getUrl(item.title) : '/wiki/index.php?title=' + encodeURIComponent(item.title.replace(/ /g, '_')), | |||
category: category, | |||
location: location, | |||
snippet: snippet | |||
}; | |||
}) | |||
}; | |||
} | |||
function searchNative(query) { | |||
var key = normalizeText(query); | |||
if (apiCache[key]) return Promise.resolve(apiCache[key]); | |||
if (!api) return Promise.reject(new Error('MediaWiki API unavailable')); | |||
return api.get({ | |||
action: 'query', | |||
list: 'search', | |||
srsearch: query, | |||
srwhat: 'text', | |||
srnamespace: 0, | |||
srlimit: API_LIMIT, | |||
srinfo: 'totalhits|suggestion|rewrittenquery', | |||
srprop: 'snippet|titlesnippet|sectiontitle|redirecttitle', | |||
srenablerewrites: 1, | |||
formatversion: 2, | |||
utf8: 1 | |||
}).then(function (data) { | |||
var payload = transformApiData(data); | |||
apiCache[key] = payload; | |||
return payload; | |||
}); | |||
} | |||
function runSearch(rawQuery) { | |||
var query = String(rawQuery || '').trim(); | |||
if (query.length < MIN_QUERY_LENGTH) { | |||
clearResults(); | |||
return; | |||
} | |||
var currentRequest = ++requestSequence; | |||
var state = { | |||
menu: findMenuMatches(menuEntries, query), | |||
smart: [], | |||
api: [], | |||
smartLoading: true, | |||
smartFailed: false, | |||
apiFailed: false, | |||
suggestion: '', | |||
totalHits: null, | |||
indexCount: null | |||
}; | |||
renderLoading(state.menu, query); | |||
searchNative(query).then(function (payload) { | |||
if (currentRequest !== requestSequence) return; | |||
state.api = payload.items; | |||
state.suggestion = payload.suggestion; | |||
state.totalHits = payload.totalHits; | |||
renderState(query, state); | |||
}).catch(function () { | |||
if (currentRequest !== requestSequence) return; | |||
state.apiFailed = true; | |||
renderState(query, state); | |||
}); | |||
ensureSmartIndex(api, categoryMap).then(function (index) { | |||
if (currentRequest !== requestSequence) return; | |||
state.smart = searchSmartIndex(index, query, labels, synonymMap); | |||
state.smartLoading = false; | |||
state.indexCount = index.length; | |||
renderState(query, state); | |||
}).catch(function () { | |||
if (currentRequest !== requestSequence) return; | |||
state.smartLoading = false; | |||
state.smartFailed = true; | |||
renderState(query, state); | |||
}); | |||
} | |||
function getResultLinks() { | |||
return Array.prototype.slice.call(results.querySelectorAll('.oroza-search-result-link')); | |||
} | |||
function setActiveResult(index) { | |||
var links = getResultLinks(); | |||
if (!links.length) return; | |||
if (index < 0) index = links.length - 1; | |||
if (index >= links.length) index = 0; | |||
activeIndex = index; | |||
links.forEach(function (link, itemIndex) { | |||
var active = itemIndex === activeIndex; | |||
link.classList.toggle('is-active', active); | |||
link.setAttribute('aria-selected', active ? 'true' : 'false'); | |||
if (active) link.scrollIntoView({ block: 'nearest' }); | |||
}); | |||
} | |||
input.addEventListener('input', function () { | |||
var query = input.value.trim(); | |||
clearButton.hidden = !query; | |||
activeIndex = -1; | |||
window.clearTimeout(debounceTimer); | |||
if (query.length < MIN_QUERY_LENGTH) { | |||
requestSequence++; | |||
clearResults(); | |||
return; | |||
} | |||
debounceTimer = window.setTimeout(function () { | |||
runSearch(query); | |||
}, DEBOUNCE_MS); | |||
}); | |||
input.addEventListener('focus', function () { | |||
if (input.value.trim().length >= MIN_QUERY_LENGTH && results.hidden) { | |||
runSearch(input.value); | |||
} | |||
}); | |||
input.addEventListener('keydown', function (event) { | |||
if (event.key === 'ArrowDown') { | |||
event.preventDefault(); | |||
if (results.hidden) runSearch(input.value); | |||
setActiveResult(activeIndex + 1); | |||
} else if (event.key === 'ArrowUp') { | |||
event.preventDefault(); | |||
setActiveResult(activeIndex - 1); | |||
} else if (event.key === 'Enter' && activeIndex >= 0) { | |||
var links = getResultLinks(); | |||
if (links[activeIndex]) { | |||
event.preventDefault(); | |||
window.location.href = links[activeIndex].href; | |||
} | |||
} else if (event.key === 'Escape') { | |||
setOpen(false); | |||
input.blur(); | |||
} | |||
}); | |||
clearButton.addEventListener('click', function () { | |||
input.value = ''; | |||
clearButton.hidden = true; | |||
requestSequence++; | |||
window.clearTimeout(debounceTimer); | |||
clearResults(); | |||
input.focus(); | |||
}); | |||
document.addEventListener('click', function (event) { | |||
if (!widget.contains(event.target)) setOpen(false); | |||
}); | |||
form.addEventListener('submit', function (event) { | |||
if (!input.value.trim()) { | |||
event.preventDefault(); | |||
input.focus(); | |||
} | |||
}); | |||
} | |||
function boot(root) { | |||
root = root || document; | |||
var widgets = root.querySelectorAll ? root.querySelectorAll(SELECTOR) : []; | |||
Array.prototype.forEach.call(widgets, function (widget) { | |||
initSearch(widget.parentNode || document); | |||
}); | |||
} | |||
function bootWithModules(root) { | |||
if (window.mw && mw.loader && mw.loader.using) { | |||
mw.loader.using(['mediawiki.api', 'mediawiki.util']).then(function () { | |||
boot(root); | |||
}, function () { | |||
boot(root); | |||
}); | |||
} else { | |||
boot(root); | |||
} | |||
} | |||
window.OrozaWikiSearch = { | |||
rebuild: function () { | |||
clearIndexCache(); | |||
return true; | |||
}, | |||
clearCache: clearIndexCache | |||
}; | |||
if (document.readyState === 'loading') { | |||
document.addEventListener('DOMContentLoaded', function () { | |||
bootWithModules(document); | |||
}); | |||
} else { | |||
bootWithModules(document); | |||
} | |||
if (window.mw && mw.hook) { | |||
mw.hook('wikipage.content').add(function ($content) { | |||
var node = $content && $content[0] ? $content[0] : document; | |||
bootWithModules(node); | |||
bootWithModules(document); | |||
}); | |||
} | |||
})(); | |||
Revisión actual - 11:30 10 jul 2026
(function () {
function safeNormalize(text) {
text = String(text || '').toLowerCase().trim();
try {
return text.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
} catch (e) {
return text;
}
}
function hasMark(cellText) {
cellText = String(cellText || '').replace(/\s+/g, '').trim();
return (
cellText !== '' &&
(
cellText.indexOf('✔') !== -1 ||
cellText.indexOf('✅') !== -1 ||
cellText.indexOf('☑') !== -1
)
);
}
function getActiveCategoryCol(tableId) {
var activeBtn = document.querySelector('.oroza-cat-btn.active[data-table="' + tableId + '"]');
if (!activeBtn) {
var firstBtn = document.querySelector('.oroza-cat-btn[data-table="' + tableId + '"]');
if (!firstBtn) return null;
firstBtn.classList.add('active');
return firstBtn.getAttribute('data-col');
}
return activeBtn.getAttribute('data-col'); // Puede retornar "all" en string
}
function getSearchTextForRow(row, tableId) {
var cells = row.getElementsByTagName('td');
var text = row.getAttribute('data-search') || '';
// Como añadimos "Prob.", ahora las columnas de información base son 3 en Slot 1 y 4 en Slot 2
var visibleInfoCols = tableId === 'tableSlot1' ? 3 : 4;
for (var i = 0; i < visibleInfoCols; i++) {
if (cells[i]) {
text += ' ' + (cells[i].textContent || cells[i].innerText || '');
}
}
return safeNormalize(text);
}
function updateCategoryView(tableId, inputId) {
var table = document.getElementById(tableId);
var input = document.getElementById(inputId);
if (!table || !table.tBodies.length || !table.tHead || !table.tHead.rows.length) return;
var activeColRaw = getActiveCategoryCol(tableId);
if (activeColRaw === null) return;
var showAll = (activeColRaw === 'all');
var activeCol = showAll ? -1 : parseInt(activeColRaw, 10);
var searchText = input ? safeNormalize(input.value) : '';
var rows = table.tBodies[0].getElementsByTagName('tr');
var headerRow = table.tHead.rows[0];
// Columnas que SIEMPRE se ven (Atributo, Prob, Rango, etc.)
var staticCols = tableId === 'tableSlot1' ? 3 : 4;
// Actualizar Header
for (var h = 0; h < headerRow.cells.length; h++) {
headerRow.cells[h].style.display = (showAll || h < staticCols || h === activeCol) ? '' : 'none';
}
// Actualizar Body
for (var i = 0; i < rows.length; i++) {
var row = rows[i];
var cells = row.getElementsByTagName('td');
// Si estamos en "Todos", empezamos asumiendo que sí tiene categoría válida
var matchCategory = showAll;
var matchText = true;
// Si se escogió una categoría específica, verificamos si tiene "✔"
if (!showAll && cells[activeCol]) {
matchCategory = hasMark(cells[activeCol].textContent || cells[activeCol].innerText);
}
// Verificación de búsqueda por texto
if (searchText !== '') {
var searchPool = getSearchTextForRow(row, tableId);
if (searchPool.indexOf(searchText) === -1) {
matchText = false;
}
}
row.style.display = (matchCategory && matchText) ? '' : 'none';
for (var c = 0; c < cells.length; c++) {
cells[c].style.display = (showAll || c < staticCols || c === activeCol) ? '' : 'none';
}
}
}
function bindCategoryButtons(tableId, inputId) {
var buttons = document.querySelectorAll('.oroza-cat-btn[data-table="' + tableId + '"]');
for (var i = 0; i < buttons.length; i++) {
if (buttons[i].dataset.orozaBound === '1') continue;
buttons[i].dataset.orozaBound = '1';
buttons[i].addEventListener('click', function () {
var sameTableButtons = document.querySelectorAll('.oroza-cat-btn[data-table="' + tableId + '"]');
for (var j = 0; j < sameTableButtons.length; j++) {
sameTableButtons[j].classList.remove('active');
}
this.classList.add('active');
updateCategoryView(tableId, inputId);
});
}
}
function bindSearchInput(tableId, inputId) {
var input = document.getElementById(inputId);
if (!input || input.dataset.orozaBound === '1') return;
input.dataset.orozaBound = '1';
input.addEventListener('input', function () {
updateCategoryView(tableId, inputId);
});
}
function initOrozaDropFilters() {
if (document.getElementById('tableSlot1')) {
bindCategoryButtons('tableSlot1', 'textSlot1');
bindSearchInput('tableSlot1', 'textSlot1');
updateCategoryView('tableSlot1', 'textSlot1');
}
if (document.getElementById('tableSlot2')) {
bindCategoryButtons('tableSlot2', 'textSlot2');
bindSearchInput('tableSlot2', 'textSlot2');
updateCategoryView('tableSlot2', 'textSlot2');
}
}
if (window.mw && mw.hook) {
mw.hook('wikipage.content').add(function () {
initOrozaDropFilters();
});
} else if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initOrozaDropFilters);
} else {
initOrozaDropFilters();
}
})();
/* =========================================================
RESALTAR MENÚ LATERAL ACTIVO (VERSIÓN NATIVA MEDIAWIKI)
========================================================= */
function highlightActiveSidebarLink() {
// 1. Obtenemos el nombre exacto de la página actual desde el motor de MediaWiki
var currentPage = mw.config.get('wgPageName');
if (!currentPage) return;
// Limpiamos el nombre de la página actual (todo a minúsculas y espacios a guiones bajos)
currentPage = currentPage.replace(/ /g, '_').toLowerCase();
var sidebarLinks = document.querySelectorAll('.oroza-sidebar li a');
for (var i = 0; i < sidebarLinks.length; i++) {
var linkHref = sidebarLinks[i].getAttribute('href');
if (!linkHref) continue;
// 2. Extraemos el nombre de la página del enlace del menú
var linkPage = "";
if (linkHref.indexOf('title=') !== -1) {
linkPage = linkHref.split('title=')[1].split('&')[0];
} else {
linkPage = linkHref.split('/').pop(); // Toma lo último después del slash (ej. "Como-instalarlo")
}
if (!linkPage) continue;
// 3. Decodificamos caracteres especiales (como acentos o espacios %20)
try {
linkPage = decodeURIComponent(linkPage);
} catch(e) {}
// Limpiamos el enlace de la misma forma para que la comparación sea exacta
linkPage = linkPage.replace(/ /g, '_').toLowerCase();
// 4. Comparamos
if (currentPage === linkPage) {
sidebarLinks[i].classList.add('active');
} else {
sidebarLinks[i].classList.remove('active'); // Limpia los demás por si acaso
}
}
}
// Integración con MediaWiki
if (window.mw && mw.hook) {
mw.hook('wikipage.content').add(highlightActiveSidebarLink);
} else if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', highlightActiveSidebarLink);
} else {
highlightActiveSidebarLink();
}
/* =========================================================
OROZA PET ATTACK REBALANCE V13.3 - SIMPLE SEARCH
Paste into MediaWiki:Common.js
========================================================= */
(function () {
function normalize(text) {
return (text || '')
.toString()
.toLowerCase()
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.replace(/_/g, ' ')
.replace(/\.{2,}/g, '…')
.replace(/\s+/g, ' ')
.trim();
}
function initOrozaPetSearchV9(root) {
root = root || document;
var input = root.querySelector ? root.querySelector('#orozaPetSearchV6') : document.getElementById('orozaPetSearchV6');
if (!input || input.dataset.ready === 'v9') return;
input.dataset.ready = 'v9';
var container = input.closest('.oroza-pet-v6') || document;
var cards = Array.prototype.slice.call(container.querySelectorAll('.oroza-pet-v6-card'));
var sections = Array.prototype.slice.call(container.querySelectorAll('[data-pet-section]'));
var result = container.querySelector('#orozaPetSearchV6Result');
var clearBtn = container.querySelector('[data-pet-clear]');
function applySearch() {
var q = normalize(input.value);
var shown = 0;
cards.forEach(function (card) {
var haystack = normalize((card.getAttribute('data-pet-search') || '') + ' ' + card.textContent);
var match = !q || haystack.indexOf(q) !== -1;
card.classList.toggle('is-hidden', !match);
if (match) shown++;
});
sections.forEach(function (section) {
var visibleCards = section.querySelectorAll('.oroza-pet-v6-card:not(.is-hidden)').length;
var shouldHide = visibleCards === 0 && q;
section.classList.toggle('is-hidden', shouldHide);
if (!shouldHide && q) section.open = true;
});
if (result) {
if (shown === 0) {
result.textContent = 'No pets were found with that text.';
} else if (q) {
result.textContent = 'Visible results: ' + shown + '. Open the card to review each Pokémon skill and level.';
} else {
result.textContent = 'Showing all pets.';
}
}
}
input.addEventListener('input', applySearch);
if (clearBtn) {
clearBtn.addEventListener('click', function () {
input.value = '';
applySearch();
input.focus();
});
}
applySearch();
}
function boot() {
initOrozaPetSearchV9(document);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', boot);
} else {
boot();
}
if (window.mw && window.mw.hook) {
window.mw.hook('wikipage.content').add(function ($content) {
var node = $content && $content[0] ? $content[0] : document;
initOrozaPetSearchV9(node);
});
}
})();
/* =========================================================
OROZA RO SMART WIKI SEARCH V2.2
---------------------------------------------------------
- Searches inside the complete text of every main Wiki page
- Detects headings/sections and shows the matching fragment
- Combines a local smart index with MediaWiki native search
- Adds aliases/synonyms for common Ragnarok Online terms
- Uses cache to avoid downloading the Wiki on every search
- Keyboard navigation, mobile support and native fallback
========================================================= */
(function () {
'use strict';
var SELECTOR = '[data-oroza-global-search]';
var MIN_QUERY_LENGTH = 2;
var SMART_LIMIT = 10;
var API_LIMIT = 8;
var MENU_LIMIT = 3;
var DEBOUNCE_MS = 240;
var INDEX_TTL_MS = 2 * 60 * 60 * 1000;
var MAX_INDEX_PAGES = 400;
var MAX_PAGE_TEXT = 60000;
var CACHE_VERSION = 4;
var indexPromise = null;
var memoryIndex = null;
function normalizeText(value) {
var text = String(value || '')
.toLowerCase()
.replace(/_/g, ' ')
.replace(/ /gi, ' ')
.replace(/[\u2010-\u2015]/g, '-')
.replace(/[^a-z0-9@+#áéíóúüñçàèìòùâêîôûäëïöü\s-]/gi, ' ')
.replace(/\s+/g, ' ')
.trim();
try {
text = text.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
} catch (e) {}
return text;
}
function decodeHtmlEntities(value) {
var text = String(value || '');
/* MediaWiki search snippets can arrive HTML-escaped more than once.
Decode repeatedly so <h2> becomes a real tag before removing it. */
for (var i = 0; i < 3; i++) {
var decoder = document.createElement('textarea');
decoder.innerHTML = text;
var decoded = decoder.value;
if (decoded === text) break;
text = decoded;
}
return text;
}
function cleanSearchCodeResidue(value) {
var text = String(value || '');
/* Native MediaWiki snippets can begin or end in the middle of an HTML
attribute. In that case there is no complete tag for the browser to
remove, so fragments such as object-fit, drop-shadow or rgba survive.
This pass removes only presentation/code residue, preserving guide text. */
var cssProperties = [
'object-fit', 'bject-fit', 'ject-fit', 'object-position',
'filter', 'box-shadow', 'text-shadow', 'background', 'background-color',
'background-image', 'border', 'border-radius', 'border-color',
'border-width', 'border-style', 'color', 'display', 'position',
'top', 'right', 'bottom', 'left', 'width', 'height', 'max-width',
'min-width', 'max-height', 'min-height', 'margin', 'margin-top',
'margin-right', 'margin-bottom', 'margin-left', 'padding', 'padding-top',
'padding-right', 'padding-bottom', 'padding-left', 'font-size',
'font-weight', 'font-family', 'font-style', 'line-height',
'letter-spacing', 'text-align', 'text-decoration', 'white-space',
'vertical-align', 'overflow', 'overflow-x', 'overflow-y', 'opacity',
'transform', 'transform-origin', 'transition', 'animation', 'cursor',
'z-index', 'float', 'clear', 'content', 'visibility', 'flex',
'flex-grow', 'flex-shrink', 'flex-basis', 'flex-direction', 'flex-wrap',
'align-items', 'align-content', 'align-self', 'justify-content',
'justify-items', 'justify-self', 'gap', 'row-gap', 'column-gap',
'grid', 'grid-template-columns', 'grid-template-rows', 'grid-column',
'grid-row', 'list-style', 'list-style-type', 'object-fit'
];
var propertyPattern = new RegExp(
'(?:^|[\\s"\\\';])(?:' + cssProperties.join('|') + ')\\s*:\\s*[^;{}<>]{0,600};',
'gi'
);
for (var i = 0; i < 4; i++) {
var before = text;
text = text.replace(propertyPattern, ' ');
if (text === before) break;
}
/* Remove truncated generic declarations only when they clearly contain a
CSS value/function. This avoids deleting normal phrases such as
"Reward: 10 Zeny". */
var cssValuePattern = /(?:^|[\s"';,.…])[-a-z]{2,}\s*:\s*[^;{}<>]{0,500}(?:rgba?\s*\(|hsla?\s*\(|drop-shadow\s*\(|linear-gradient\s*\(|radial-gradient\s*\(|url\s*\(|calc\s*\(|var\s*\(|\b(?:contain|cover|flex|grid|block|inline-block|absolute|relative|fixed|sticky|hidden|visible|solid|dashed|none|auto|center)\b|\d+(?:\.\d+)?(?:px|rem|em|vh|vw|%))[^;{}<>]*;?/gi;
for (var j = 0; j < 3; j++) {
var previous = text;
text = text.replace(cssValuePattern, ' ');
if (text === previous) break;
}
text = text
.replace(/\b(?:class|style|id|src|srcset|alt|title|width|height|href|target|rel|loading|decoding|data-[a-z0-9_-]+|aria-[a-z0-9_-]+)\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)/gi, ' ')
.replace(/\b(?:drop-shadow|rgba?|hsla?|linear-gradient|radial-gradient|translate(?:x|y|3d)?|scale(?:x|y|3d)?|rotate(?:x|y|z|3d)?|calc|var|url)\s*\([^;{}<>]{0,500}\)/gi, ' ')
.replace(/(?:^|\s)(?:px|rem|em|vh|vw)\b/gi, ' ')
.replace(/\s*(?:\/?>|<\/?)\s*/g, ' ')
.replace(/["'`{}]+/g, ' ')
.replace(/\.{2,}/g, '…')
.replace(/\s+/g, ' ')
.trim();
return text;
}
function stripHtml(value) {
var text = decodeHtmlEntities(value)
.replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, ' ')
.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, ' ')
.replace(/<!--([\s\S]*?)-->/g, ' ');
/* Parse the decoded HTML in a detached element. This removes tags such
as h2, img, span, table, code and their attributes without executing it. */
for (var i = 0; i < 2; i++) {
var node = document.createElement('div');
node.innerHTML = text;
var plain = node.textContent || node.innerText || '';
if (plain === text) break;
text = plain;
}
/* Final defensive cleanup for malformed or partially escaped markup. */
text = decodeHtmlEntities(text)
.replace(/<[^>]*>/g, ' ')
.replace(/\b(?:class|style|id|src|alt|title|width|height|href|target|rel|loading)\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)/gi, ' ')
.replace(/(?:^|\s)(?:div|span|table|tbody|thead|tr|td|th|img|h[1-6]|p|br|code|strong|em)(?:\s|$)/gi, ' ');
return cleanSearchCodeResidue(cleanWikiText(text))
.replace(/\s+/g, ' ')
.trim();
}
function cleanWikiText(value) {
var text = decodeHtmlEntities(value);
text = text
.replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, ' ')
.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, ' ')
.replace(/<ref\b[^>]*>[\s\S]*?<\/ref>/gi, ' ')
.replace(/<ref\b[^>]*\/\s*>/gi, ' ')
.replace(/<!--([\s\S]*?)-->/g, ' ')
.replace(/\{\|[\s\S]*?\|\}/g, function (table) {
return table.replace(/[|!{}]/g, ' ');
})
.replace(/\[\[Category:[^\]]+\]\]/gi, ' ')
.replace(/\[\[File:[^\]]+\]\]/gi, ' ')
.replace(/\[\[Image:[^\]]+\]\]/gi, ' ')
.replace(/\[\[[^\]|]+\|([^\]]+)\]\]/g, '$1')
.replace(/\[\[([^\]]+)\]\]/g, '$1')
.replace(/\[(?:https?:)?\/\/[^\s\]]+\s+([^\]]+)\]/g, '$1')
.replace(/\[(?:https?:)?\/\/[^\]]+\]/g, ' ')
.replace(/\{\{[^{}]*\}\}/g, ' ')
.replace(/\{\{[^{}]*\}\}/g, ' ')
.replace(/<[^>]+>/g, ' ')
.replace(/'{2,5}/g, '')
.replace(/={2,6}/g, ' ')
.replace(/__[^_]+__/g, ' ')
.replace(/[|{}]/g, ' ')
.replace(/\s+/g, ' ')
.trim();
return cleanSearchCodeResidue(text);
}
function extractKeywords(wikitext) {
var keywords = [];
var pattern = /<!--\s*OROZA-KEYWORDS\s*:\s*([\s\S]*?)-->/gi;
var match;
while ((match = pattern.exec(String(wikitext || '')))) {
match[1].split(/[,;|\n]/).forEach(function (item) {
item = item.trim();
if (item) keywords.push(item);
});
}
return keywords;
}
function extractCategories(wikitext) {
var categories = [];
var pattern = /\[\[Category\s*:\s*([^\]|]+)(?:\|[^\]]*)?\]\]/gi;
var match;
while ((match = pattern.exec(String(wikitext || '')))) {
var category = cleanWikiText(match[1]);
if (category && categories.indexOf(category) === -1) categories.push(category);
}
return categories;
}
function splitIntoSections(wikitext) {
var source = decodeHtmlEntities(wikitext);
/* Many Oroza Wiki guides are built with literal HTML instead of == Wiki
headings ==. Convert HTML headings and accordion summaries into virtual
Wiki headings so the search can identify the exact section. */
source = source.replace(/<h([1-6])\b[^>]*>([\s\S]*?)<\/h\1>/gi, function (match, level, title) {
var headingLevel = Math.max(2, Math.min(6, parseInt(level, 10) || 2));
var marks = '======'.slice(0, headingLevel);
var cleanTitle = stripHtml(title);
return cleanTitle ? '\n' + marks + ' ' + cleanTitle + ' ' + marks + '\n' : '\n';
});
source = source.replace(/<summary\b[^>]*>([\s\S]*?)<\/summary>/gi, function (match, title) {
var cleanTitle = stripHtml(title);
return cleanTitle ? '\n=== ' + cleanTitle + ' ===\n' : '\n';
});
var lines = source.split(/\r?\n/);
var sections = [];
var currentTitle = '';
var currentLines = [];
function flush() {
var plain = cleanWikiText(currentLines.join('\n'));
if (plain) {
sections.push({
title: cleanWikiText(currentTitle),
text: plain.slice(0, MAX_PAGE_TEXT)
});
}
currentLines = [];
}
lines.forEach(function (line) {
var heading = line.match(/^\s*(={2,6})\s*(.*?)\s*\1\s*$/);
if (heading) {
flush();
currentTitle = stripHtml(heading[2]);
} else {
currentLines.push(line);
}
});
flush();
if (!sections.length) {
var plain = cleanWikiText(source);
if (plain) sections.push({ title: '', text: plain.slice(0, MAX_PAGE_TEXT) });
}
return sections;
}
function decodePageName(href) {
try {
var url = new URL(href, window.location.href);
var title = url.searchParams.get('title');
if (!title) {
var marker = '/wiki/index.php/';
var markerIndex = url.pathname.indexOf(marker);
if (markerIndex !== -1) title = url.pathname.slice(markerIndex + marker.length);
}
if (!title) return '';
return decodeURIComponent(title).replace(/_/g, ' ').trim();
} catch (e) {
return '';
}
}
function getLocale() {
var isEnglish = window.location.hostname === 'en.orozaro.com';
return isEnglish ? {
placeholder: 'Search guides, commands, items, systems and content...',
ariaLabel: 'Search inside every Oroza RO Wiki guide',
searchButton: 'Search',
loading: 'Searching titles, sections and guide content...',
smartLoading: 'Building the smart guide index for the first search...',
smartGroup: 'Matches found inside guides',
apiGroup: 'Other Wiki results',
menuGroup: 'Quick links',
noResultsTitle: 'No results found',
noResultsText: 'Try another spelling, a shorter phrase, or a related Ragnarok term.',
categoryFallback: 'Wiki guide',
sectionPrefix: 'Section',
insideGuide: 'Inside guide',
titleMatch: 'Guide title',
aliasMatch: 'Related term',
resultSingular: 'result',
resultPlural: 'results',
viewAll: 'View native search',
suggestion: 'Did you mean',
indexedGuides: 'guides indexed',
indexUnavailable: 'Smart index unavailable; showing native results.',
apiErrorTitle: 'Native search is temporarily unavailable',
apiErrorText: 'Smart guide matches are still available. Press Enter to open MediaWiki search.',
introduction: 'Introduction'
} : {
placeholder: 'Buscar guías, comandos, objetos, sistemas y contenido...',
ariaLabel: 'Buscar dentro de todas las guías de la Wiki de Oroza RO',
searchButton: 'Buscar',
loading: 'Buscando en títulos, secciones y contenido de las guías...',
smartLoading: 'Creando el índice inteligente de las guías por primera vez...',
smartGroup: 'Coincidencias encontradas dentro de las guías',
apiGroup: 'Otros resultados de la Wiki',
menuGroup: 'Accesos rápidos',
noResultsTitle: 'No encontramos resultados',
noResultsText: 'Prueba otra escritura, una frase más corta o un término relacionado de Ragnarok.',
categoryFallback: 'Guía de la Wiki',
sectionPrefix: 'Sección',
insideGuide: 'Dentro de la guía',
titleMatch: 'Título de la guía',
aliasMatch: 'Término relacionado',
resultSingular: 'resultado',
resultPlural: 'resultados',
viewAll: 'Ver búsqueda nativa',
suggestion: 'Quizá quisiste decir',
indexedGuides: 'guías indexadas',
indexUnavailable: 'Índice inteligente no disponible; se muestran resultados nativos.',
apiErrorTitle: 'La búsqueda nativa no está disponible temporalmente',
apiErrorText: 'Las coincidencias del índice inteligente siguen disponibles. Presiona Enter para abrir la búsqueda de MediaWiki.',
introduction: 'Introducción'
};
}
function getSynonymMap(isEnglish) {
var shared = {
'@aa': ['auto attack', 'auto ataque', 'autoatk', 'automatic attack'],
'aa': ['auto attack', 'auto ataque', 'autoatk'],
'bg': ['battleground', 'battlegrounds', 'campo de batalla'],
'woe': ['war of emperium', 'guild war', 'guerra de emperium'],
'mvp': ['boss', 'jefe', 'monster boss'],
'npc': ['non player character', 'personaje no jugador'],
'exp': ['experience', 'experiencia'],
'zeny': ['money', 'currency', 'dinero', 'moneda']
};
var english = {
'beginner': ['starter', 'new player', 'newbie', 'first steps', 'getting started'],
'newbie': ['beginner', 'starter', 'new player', 'first steps'],
'starter': ['beginner', 'new player', 'newbie', 'first steps', '@starter'],
'job': ['class', 'profession', 'job change'],
'class': ['job', 'profession', 'job change'],
'card': ['cards', 'monster card'],
'cards': ['card', 'monster cards'],
'hat': ['headgear', 'head gear', 'costume'],
'headgear': ['hat', 'head gear', 'costume'],
'refine': ['refinement', 'upgrade', 'safe refine'],
'refinement': ['refine', 'upgrade', 'safe refine'],
'enchant': ['enchantment', 'bonus', 'random option'],
'quest': ['mission', 'task'],
'mission': ['quest', 'task'],
'party': ['group', 'even share'],
'group': ['party', 'even share'],
'drop': ['loot', 'reward', 'item drop'],
'loot': ['drop', 'reward'],
'pet': ['pokemon', 'companion', 'homunculus'],
'pokemon': ['pet', 'companion'],
'command': ['commands', '@command', 'chat command'],
'donation': ['donate', 'cash points', 'support server'],
'event': ['events', 'activity'],
'skill': ['skills', 'ability'],
'equipment': ['gear', 'weapon', 'armor'],
'gear': ['equipment', 'weapon', 'armor']
};
var spanish = {
'principiante': ['starter', 'jugador nuevo', 'novato', 'primeros pasos', 'inicio'],
'novato': ['principiante', 'starter', 'jugador nuevo', 'primeros pasos'],
'nuevo': ['principiante', 'starter', 'jugador nuevo', 'primeros pasos'],
'starter': ['principiante', 'jugador nuevo', 'novato', 'primeros pasos', '@starter'],
'job': ['clase', 'profesión', 'cambio de job'],
'clase': ['job', 'profesión', 'cambio de clase'],
'carta': ['cartas', 'card', 'monster card'],
'cartas': ['carta', 'cards', 'monster cards'],
'hat': ['sombrero', 'headgear', 'costume'],
'sombrero': ['hat', 'headgear', 'costume'],
'refinar': ['refinamiento', 'mejorar equipo', 'refine'],
'refinamiento': ['refinar', 'mejorar equipo', 'safe refine'],
'encantar': ['encantamiento', 'bonus', 'opción aleatoria'],
'encantamiento': ['encantar', 'bonus', 'random option'],
'quest': ['misión', 'mision', 'tarea'],
'misión': ['quest', 'mision', 'tarea'],
'mision': ['quest', 'misión', 'tarea'],
'party': ['grupo', 'even share', 'compartir experiencia'],
'grupo': ['party', 'even share', 'compartir experiencia'],
'drop': ['loot', 'recompensa', 'objeto'],
'loot': ['drop', 'recompensa'],
'mascota': ['pokemon', 'pet', 'homúnculo', 'homunculo'],
'pokemon': ['mascota', 'pet', 'compañero'],
'comando': ['comandos', '@comando', 'chat command'],
'donación': ['donar', 'cash points', 'apoyar servidor'],
'donacion': ['donar', 'cash points', 'apoyar servidor'],
'evento': ['eventos', 'actividad'],
'skill': ['skills', 'habilidad'],
'equipo': ['gear', 'arma', 'armadura']
};
var result = {};
Object.keys(shared).forEach(function (key) { result[normalizeText(key)] = shared[key]; });
Object.keys(isEnglish ? english : spanish).forEach(function (key) {
result[normalizeText(key)] = (isEnglish ? english : spanish)[key];
});
return result;
}
function tokenize(value) {
var stopWords = {
'the': 1, 'a': 1, 'an': 1, 'and': 1, 'or': 1, 'of': 1, 'to': 1, 'in': 1, 'for': 1,
'el': 1, 'la': 1, 'los': 1, 'las': 1, 'un': 1, 'una': 1, 'y': 1, 'o': 1, 'de': 1,
'del': 1, 'en': 1, 'para': 1, 'como': 1, 'how': 1, 'what': 1, 'que': 1
};
return normalizeText(value).split(' ').filter(function (token) {
return token && (token.length >= 2 || token.charAt(0) === '@') && !stopWords[token];
});
}
function unique(values) {
var seen = Object.create(null);
return values.filter(function (value) {
var key = normalizeText(value);
if (!key || seen[key]) return false;
seen[key] = true;
return true;
});
}
function expandQuery(query, synonymMap) {
var originalTokens = tokenize(query);
var expandedPhrases = [];
originalTokens.forEach(function (token) {
var synonyms = synonymMap[token] || [];
synonyms.forEach(function (synonym) {
expandedPhrases.push(synonym);
});
});
return {
phrase: normalizeText(query),
originalTokens: unique(originalTokens),
expandedTokens: unique(expandedPhrases.reduce(function (all, phrase) {
return all.concat(tokenize(phrase));
}, [])),
expandedPhrases: unique(expandedPhrases.map(normalizeText))
};
}
function collectMenuEntries() {
var entries = [];
var titles = document.querySelectorAll('.oroza-sidebar .menu-title');
Array.prototype.forEach.call(titles, function (titleNode) {
var category = (titleNode.textContent || '').replace(/\s+/g, ' ').trim();
var list = titleNode.nextElementSibling;
if (!list || String(list.tagName).toLowerCase() !== 'ul') return;
Array.prototype.forEach.call(list.querySelectorAll('a[href]'), function (link) {
var title = decodePageName(link.getAttribute('href'));
var labelNode = link.querySelector('.menu-text');
var label = labelNode ? labelNode.textContent : link.textContent;
label = String(label || '').replace(/›/g, '').replace(/\s+/g, ' ').trim();
title = title || label;
if (!title || !link.href) return;
entries.push({
title: title,
label: label || title,
category: category,
href: link.href,
normalized: normalizeText(title + ' ' + label + ' ' + category)
});
});
});
return entries;
}
function findMenuMatches(entries, query) {
var normalizedQuery = normalizeText(query);
if (!normalizedQuery) return [];
return entries.map(function (entry) {
var normalizedTitle = normalizeText(entry.title + ' ' + entry.label);
var score = 0;
if (normalizedTitle === normalizedQuery) score = 100;
else if (normalizedTitle.indexOf(normalizedQuery) === 0) score = 80;
else if (normalizedTitle.indexOf(normalizedQuery) !== -1) score = 60;
else if (entry.normalized.indexOf(normalizedQuery) !== -1) score = 40;
return { entry: entry, score: score };
}).filter(function (item) {
return item.score > 0;
}).sort(function (a, b) {
return b.score - a.score || a.entry.label.localeCompare(b.entry.label);
}).slice(0, MENU_LIMIT).map(function (item) {
return item.entry;
});
}
function buildCategoryMap(entries) {
var map = Object.create(null);
entries.forEach(function (entry) {
map[normalizeText(entry.title)] = entry.category;
map[normalizeText(entry.label)] = entry.category;
});
return map;
}
function getCacheKey() {
return 'oroza-smart-wiki-index-v' + CACHE_VERSION + ':' + window.location.hostname;
}
function readIndexCache() {
try {
var raw = window.localStorage.getItem(getCacheKey());
if (!raw) return null;
var payload = JSON.parse(raw);
if (!payload || !Array.isArray(payload.pages) || !payload.createdAt) return null;
if (Date.now() - payload.createdAt > INDEX_TTL_MS) return null;
return payload.pages;
} catch (e) {
return null;
}
}
function writeIndexCache(pages) {
try {
window.localStorage.setItem(getCacheKey(), JSON.stringify({
createdAt: Date.now(),
pages: pages
}));
} catch (e) {}
}
function clearIndexCache() {
try { window.localStorage.removeItem(getCacheKey()); } catch (e) {}
memoryIndex = null;
indexPromise = null;
}
function getRevisionContent(page) {
var revision = page && page.revisions && page.revisions[0];
if (!revision) return '';
if (revision.slots && revision.slots.main) {
return revision.slots.main.content || revision.slots.main['*'] || '';
}
return revision.content || revision['*'] || '';
}
function createIndexedPage(page, categoryMap) {
var wikitext = getRevisionContent(page);
var title = page.title || '';
var sections = splitIntoSections(wikitext);
var keywords = extractKeywords(wikitext);
var categories = extractCategories(wikitext);
var category = categoryMap[normalizeText(title)] || categories[0] || '';
return {
title: title,
normalizedTitle: normalizeText(title),
url: (window.mw && mw.util) ? mw.util.getUrl(title) : '/wiki/index.php?title=' + encodeURIComponent(title.replace(/ /g, '_')),
category: category,
keywords: keywords,
normalizedKeywords: normalizeText(keywords.join(' ')),
sections: sections.map(function (section) {
return {
title: section.title,
normalizedTitle: normalizeText(section.title),
text: section.text,
normalizedText: normalizeText(section.text)
};
})
};
}
function buildSmartIndex(api, categoryMap) {
var pages = [];
var continueParams = {};
function fetchBatch() {
var params = {
action: 'query',
generator: 'allpages',
gapnamespace: 0,
gapfilterredir: 'nonredirects',
gaplimit: 'max',
prop: 'revisions',
rvprop: 'content',
rvslots: 'main',
rvlimit: 1,
formatversion: 2,
utf8: 1
};
Object.keys(continueParams).forEach(function (key) {
params[key] = continueParams[key];
});
return api.get(params).then(function (data) {
var batch = data && data.query && Array.isArray(data.query.pages) ? data.query.pages : [];
batch.forEach(function (page) {
if (pages.length >= MAX_INDEX_PAGES || page.missing) return;
pages.push(createIndexedPage(page, categoryMap));
});
if (data && data.continue && pages.length < MAX_INDEX_PAGES) {
continueParams = data.continue;
return fetchBatch();
}
writeIndexCache(pages);
memoryIndex = pages;
return pages;
});
}
return fetchBatch();
}
function ensureSmartIndex(api, categoryMap) {
if (memoryIndex) return Promise.resolve(memoryIndex);
var cached = readIndexCache();
if (cached) {
memoryIndex = cached;
return Promise.resolve(cached);
}
if (!api) return Promise.reject(new Error('MediaWiki API unavailable'));
if (!indexPromise) {
indexPromise = buildSmartIndex(api, categoryMap).catch(function (error) {
indexPromise = null;
throw error;
});
}
return indexPromise;
}
function countOccurrences(text, token) {
if (!token) return 0;
var count = 0;
var position = 0;
while ((position = text.indexOf(token, position)) !== -1) {
count++;
position += Math.max(token.length, 1);
if (count >= 8) break;
}
return count;
}
function makeSnippet(text, queryData, maxLength) {
var plain = cleanSearchCodeResidue(stripHtml(text)).replace(/\s+/g, ' ').trim();
if (!plain) return '';
var lower = plain.toLowerCase();
var candidates = [String(queryData.raw || '').toLowerCase()]
.concat(queryData.originalTokens || [])
.concat(queryData.expandedTokens || []);
var position = -1;
candidates.some(function (candidate) {
if (!candidate) return false;
position = lower.indexOf(candidate.toLowerCase());
return position !== -1;
});
if (position < 0) position = 0;
var radius = Math.floor((maxLength || 220) / 2);
var start = Math.max(0, position - radius);
var end = Math.min(plain.length, start + (maxLength || 220));
if (end - start < (maxLength || 220) && start > 0) {
start = Math.max(0, end - (maxLength || 220));
}
var snippet = cleanSearchCodeResidue(plain.slice(start, end)).trim();
if (!snippet) return '';
if (start > 0) snippet = '…' + snippet;
if (end < plain.length) snippet += '…';
return snippet;
}
function scoreSection(page, section, queryData) {
var title = page.normalizedTitle;
var sectionTitle = section.normalizedTitle;
var text = section.normalizedText;
var keywords = page.normalizedKeywords;
var phrase = queryData.phrase;
var originalTokens = queryData.originalTokens;
var expandedTokens = queryData.expandedTokens;
var score = 0;
var matchType = '';
if (title === phrase) {
score += 320;
matchType = 'title';
} else if (title.indexOf(phrase) === 0) {
score += 260;
matchType = 'title';
} else if (title.indexOf(phrase) !== -1) {
score += 220;
matchType = 'title';
}
if (keywords && phrase && keywords.indexOf(phrase) !== -1) {
score += 190;
if (!matchType) matchType = 'alias';
}
if (sectionTitle === phrase) {
score += 185;
if (!matchType) matchType = 'section';
} else if (sectionTitle && sectionTitle.indexOf(phrase) !== -1) {
score += 160;
if (!matchType) matchType = 'section';
}
if (phrase && text.indexOf(phrase) !== -1) {
score += 135;
if (!matchType) matchType = 'content';
}
var originalMatches = 0;
var expandedMatches = 0;
var frequency = 0;
originalTokens.forEach(function (token) {
var inTitle = title.indexOf(token) !== -1;
var inSection = sectionTitle.indexOf(token) !== -1;
var inKeywords = keywords.indexOf(token) !== -1;
var occurrences = countOccurrences(text, token);
if (inTitle || inSection || inKeywords || occurrences) originalMatches++;
if (inTitle) score += 24;
if (inSection) score += 19;
if (inKeywords) score += 17;
if (occurrences) score += Math.min(occurrences, 5) * 7;
frequency += occurrences;
});
expandedTokens.forEach(function (token) {
if (title.indexOf(token) !== -1 || sectionTitle.indexOf(token) !== -1 || keywords.indexOf(token) !== -1 || text.indexOf(token) !== -1) {
expandedMatches++;
}
});
var coverage = originalTokens.length ? originalMatches / originalTokens.length : 0;
if (originalTokens.length > 1 && originalMatches === originalTokens.length) score += 65;
score += Math.round(coverage * 50);
score += Math.min(expandedMatches, 4) * 8;
score += Math.min(frequency, 8) * 2;
var qualifies = false;
if (phrase && (title.indexOf(phrase) !== -1 || sectionTitle.indexOf(phrase) !== -1 || keywords.indexOf(phrase) !== -1 || text.indexOf(phrase) !== -1)) {
qualifies = true;
} else if (originalTokens.length === 1) {
qualifies = originalMatches === 1 || expandedMatches > 0;
} else if (originalTokens.length > 1) {
qualifies = coverage >= 0.67 || (coverage >= 0.5 && expandedMatches > 0);
}
return {
qualifies: qualifies,
score: score,
matchType: matchType || (expandedMatches ? 'alias' : 'content')
};
}
function searchSmartIndex(index, rawQuery, labels, synonymMap) {
var queryData = expandQuery(rawQuery, synonymMap);
queryData.raw = rawQuery;
var results = [];
(index || []).forEach(function (page) {
var best = null;
(page.sections || []).forEach(function (section) {
var scored = scoreSection(page, section, queryData);
if (!scored.qualifies) return;
if (!best || scored.score > best.score) {
best = {
score: scored.score,
matchType: scored.matchType,
section: section
};
}
});
if (!best) return;
var location = labels.insideGuide;
if (best.matchType === 'title') location = labels.titleMatch;
else if (best.matchType === 'alias') location = labels.aliasMatch;
if (best.section.title) {
location += ' · ' + labels.sectionPrefix + ': ' + best.section.title;
}
results.push({
title: page.title,
label: page.title,
href: page.url,
category: page.category || labels.categoryFallback,
location: location,
snippet: makeSnippet(best.section.text, queryData, 240),
score: best.score
});
});
return results.sort(function (a, b) {
return b.score - a.score || a.title.localeCompare(b.title);
}).slice(0, SMART_LIMIT);
}
function ensureSearchHighlightStyles() {
if (document.getElementById('oroza-search-highlight-styles')) return;
var style = document.createElement('style');
style.id = 'oroza-search-highlight-styles';
style.textContent =
'.oroza-search-highlight{' +
'display:inline;' +
'padding:1px 3px;' +
'border-radius:4px;' +
'background:linear-gradient(180deg,#fff0a6 0%,#ffd84d 100%);' +
'color:#17324f!important;' +
'font-weight:800;' +
'box-shadow:inset 0 -1px 0 rgba(185,132,0,.22);' +
'text-decoration:none;' +
'}' +
'.oroza-search-result-title .oroza-search-highlight{' +
'padding:1px 4px;' +
'background:linear-gradient(180deg,#ffe982 0%,#ffc928 100%);' +
'}' +
'.oroza-search-result-link[aria-selected="true"] .oroza-search-highlight{' +
'background:#fff4bd;' +
'color:#132a43!important;' +
'}' +
'@media (prefers-reduced-motion:reduce){' +
'.oroza-search-highlight{transition:none;}' +
'}';
(document.head || document.documentElement).appendChild(style);
}
function foldTextWithMap(value) {
var source = String(value || '');
var folded = '';
var map = [];
for (var i = 0; i < source.length; i++) {
var character = source.charAt(i);
var normalized = character;
try {
normalized = character.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
} catch (e) {}
normalized = normalized
.toLowerCase()
.replace(/[\u2010-\u2015]/g, '-');
for (var j = 0; j < normalized.length; j++) {
folded += normalized.charAt(j);
map.push(i);
}
}
return { text: folded, map: map };
}
function getHighlightTerms(query) {
var raw = String(query || '').replace(/\s+/g, ' ').trim();
if (!raw) return [];
var terms = [];
var seen = Object.create(null);
function addTerm(term) {
term = String(term || '').trim();
if (!term) return;
var key = normalizeText(term);
if (!key || seen[key]) return;
seen[key] = true;
terms.push(term);
}
addTerm(raw);
var tokens = raw.split(/\s+/);
tokens.forEach(function (token) {
var clean = token.replace(/^[^\w@+#áéíóúüñçàèìòùâêîôûäëïöü]+|[^\w@+#áéíóúüñçàèìòùâêîôûäëïöü]+$/gi, '');
if (!clean) return;
/* Avoid painting common two-letter words in long questions, but keep
useful short game terms such as IA, BG, HP or SP when searched alone. */
if (clean.length >= 3 || tokens.length === 1) addTerm(clean);
});
return terms.sort(function (a, b) { return b.length - a.length; });
}
function findHighlightRanges(text, query) {
var source = String(text || '');
var terms = getHighlightTerms(query);
if (!source || !terms.length) return [];
var foldedSource = foldTextWithMap(source);
var ranges = [];
terms.forEach(function (term) {
var foldedTerm = foldTextWithMap(term).text;
if (!foldedTerm) return;
var position = 0;
while ((position = foldedSource.text.indexOf(foldedTerm, position)) !== -1) {
var start = foldedSource.map[position];
var lastMapIndex = position + foldedTerm.length - 1;
var end = typeof foldedSource.map[lastMapIndex] === 'number'
? foldedSource.map[lastMapIndex] + 1
: start + term.length;
ranges.push({ start: start, end: end });
position += Math.max(foldedTerm.length, 1);
if (ranges.length >= 60) return;
}
});
ranges.sort(function (a, b) {
return a.start - b.start || b.end - a.end;
});
var merged = [];
ranges.forEach(function (range) {
var previous = merged.length ? merged[merged.length - 1] : null;
if (!previous || range.start > previous.end) {
merged.push({ start: range.start, end: range.end });
} else if (range.end > previous.end) {
previous.end = range.end;
}
});
return merged;
}
function appendHighlightedText(parent, text, query) {
var source = String(text || '');
var ranges = findHighlightRanges(source, query);
if (!ranges.length) {
parent.appendChild(document.createTextNode(source));
return parent;
}
var cursor = 0;
ranges.forEach(function (range) {
if (range.start > cursor) {
parent.appendChild(document.createTextNode(source.slice(cursor, range.start)));
}
var mark = document.createElement('mark');
mark.className = 'oroza-search-highlight';
mark.textContent = source.slice(range.start, range.end);
parent.appendChild(mark);
cursor = range.end;
});
if (cursor < source.length) {
parent.appendChild(document.createTextNode(source.slice(cursor)));
}
return parent;
}
function createElement(tag, className, text) {
var element = document.createElement(tag);
if (className) element.className = className;
if (typeof text !== 'undefined') element.textContent = text;
return element;
}
function initSearch(root) {
var widget = root.querySelector ? root.querySelector(SELECTOR) : null;
if (!widget || widget.dataset.orozaSmartReady === '23') return;
widget.dataset.orozaSmartReady = '23';
var form = widget.querySelector('.oroza-global-search-form');
var input = widget.querySelector('.oroza-global-search-input');
var results = widget.querySelector('.oroza-global-search-results');
var clearButton = widget.querySelector('[data-oroza-search-clear]');
var submitText = widget.querySelector('.oroza-global-search-submit span:last-child');
var helper = widget.querySelector('.oroza-global-search-helper');
if (!form || !input || !results || !clearButton) return;
ensureSearchHighlightStyles();
var labels = getLocale();
var isEnglish = window.location.hostname === 'en.orozaro.com';
var synonymMap = getSynonymMap(isEnglish);
var menuEntries = collectMenuEntries();
var categoryMap = buildCategoryMap(menuEntries);
var apiCache = Object.create(null);
var debounceTimer = null;
var requestSequence = 0;
var activeIndex = -1;
var api = (window.mw && mw.Api) ? new mw.Api() : null;
input.placeholder = labels.placeholder;
input.setAttribute('aria-label', labels.ariaLabel);
if (submitText) submitText.textContent = labels.searchButton;
if (helper) {
helper.innerHTML = isEnglish
? 'Searches inside every guide, section and paragraph. Use <kbd>↑</kbd> <kbd>↓</kbd> and <kbd>Enter</kbd>.'
: 'Busca dentro de cada guía, sección y párrafo. Usa <kbd>↑</kbd> <kbd>↓</kbd> y <kbd>Enter</kbd>.';
}
function getNativeSearchUrl(query) {
try {
var url = new URL(form.action, window.location.href);
url.searchParams.set('title', 'Special:Search');
url.searchParams.set('search', query);
url.searchParams.set('fulltext', '1');
return url.toString();
} catch (e) {
return '/wiki/index.php?title=Special:Search&fulltext=1&search=' + encodeURIComponent(query);
}
}
function setOpen(isOpen) {
results.hidden = !isOpen;
input.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
if (!isOpen) activeIndex = -1;
}
function clearResults() {
while (results.firstChild) results.removeChild(results.firstChild);
setOpen(false);
}
function addGroupTitle(text) {
results.appendChild(createElement('div', 'oroza-search-group-title', text));
}
function addResultItem(item, query) {
var link = createElement('a', 'oroza-search-result-link');
link.href = item.href;
link.setAttribute('role', 'option');
link.setAttribute('aria-selected', 'false');
var main = createElement('div', 'oroza-search-result-main');
var title = createElement('span', 'oroza-search-result-title');
appendHighlightedText(title, item.label || item.title, query);
main.appendChild(title);
if (item.location) {
var location = createElement('span', 'oroza-search-result-location');
appendHighlightedText(location, item.location, query);
main.appendChild(location);
}
if (item.snippet) {
var safeSnippet = cleanSearchCodeResidue(stripHtml(item.snippet));
if (safeSnippet) {
var snippet = createElement('p', 'oroza-search-result-snippet');
appendHighlightedText(snippet, safeSnippet, query);
main.appendChild(snippet);
}
}
link.appendChild(main);
link.appendChild(createElement('span', 'oroza-search-result-category', item.category || labels.categoryFallback));
results.appendChild(link);
}
function addSuggestion(suggestion) {
if (!suggestion) return;
var box = createElement('div', 'oroza-search-suggestion');
box.appendChild(document.createTextNode(labels.suggestion + ': '));
var button = createElement('button', '', suggestion);
button.type = 'button';
button.addEventListener('click', function () {
input.value = suggestion;
input.dispatchEvent(new Event('input', { bubbles: true }));
input.focus();
});
box.appendChild(button);
results.appendChild(box);
}
function addFooter(query, totalHits, indexState) {
var footer = createElement('div', 'oroza-search-footer');
var left = createElement('span', 'oroza-search-footer-count');
var countText = '';
if (typeof totalHits === 'number') {
countText = totalHits + ' ' + (totalHits === 1 ? labels.resultSingular : labels.resultPlural);
}
left.textContent = countText;
footer.appendChild(left);
if (indexState) {
footer.appendChild(createElement('span', 'oroza-search-index-state', indexState));
}
var link = createElement('a', '', labels.viewAll + ' →');
link.href = getNativeSearchUrl(query);
footer.appendChild(link);
results.appendChild(footer);
}
function renderLoading(menuMatches, query) {
while (results.firstChild) results.removeChild(results.firstChild);
if (menuMatches.length) {
addGroupTitle(labels.menuGroup);
menuMatches.forEach(function (item) {
addResultItem({
title: item.title,
label: item.label,
href: item.href,
category: item.category,
location: labels.titleMatch,
snippet: ''
}, query);
});
}
var status = createElement('div', 'oroza-search-status');
status.appendChild(createElement('span', 'oroza-search-spinner'));
status.appendChild(createElement('span', '', labels.loading));
results.appendChild(status);
setOpen(true);
}
function renderState(query, state) {
while (results.firstChild) results.removeChild(results.firstChild);
var seen = Object.create(null);
var smartResults = [];
var apiResults = [];
var menuMatches = [];
(state.smart || []).forEach(function (item) {
var key = normalizeText(item.title);
if (!key || seen[key]) return;
seen[key] = true;
smartResults.push(item);
});
(state.api || []).forEach(function (item) {
var key = normalizeText(item.title);
if (!key || seen[key]) return;
seen[key] = true;
apiResults.push(item);
});
(state.menu || []).forEach(function (item) {
var key = normalizeText(item.title);
if (!key || seen[key]) return;
seen[key] = true;
menuMatches.push(item);
});
if (smartResults.length) {
addGroupTitle(labels.smartGroup);
smartResults.forEach(function (item) { addResultItem(item, query); });
}
if (apiResults.length) {
addGroupTitle(labels.apiGroup);
apiResults.forEach(function (item) { addResultItem(item, query); });
}
if (menuMatches.length) {
addGroupTitle(labels.menuGroup);
menuMatches.forEach(function (item) {
addResultItem({
title: item.title,
label: item.label,
href: item.href,
category: item.category,
location: labels.titleMatch,
snippet: ''
}, query);
});
}
if (!smartResults.length && !apiResults.length && !menuMatches.length) {
var empty = createElement('div', 'oroza-search-empty');
empty.appendChild(createElement('span', 'oroza-search-empty-icon', '🔍'));
empty.appendChild(createElement('p', 'oroza-search-empty-title', labels.noResultsTitle));
empty.appendChild(createElement('p', 'oroza-search-empty-text', labels.noResultsText));
results.appendChild(empty);
}
if (state.smartLoading) {
var loading = createElement('div', 'oroza-search-smart-loading');
loading.appendChild(createElement('span', 'oroza-search-spinner'));
loading.appendChild(createElement('span', '', labels.smartLoading));
results.appendChild(loading);
}
if (state.apiFailed && !apiResults.length) {
var warning = createElement('div', 'oroza-search-inline-warning');
warning.textContent = labels.apiErrorTitle + '. ' + labels.apiErrorText;
results.appendChild(warning);
}
addSuggestion(state.suggestion);
var total = typeof state.totalHits === 'number'
? Math.max(state.totalHits, smartResults.length)
: smartResults.length + apiResults.length + menuMatches.length;
var indexState = '';
if (typeof state.indexCount === 'number') indexState = state.indexCount + ' ' + labels.indexedGuides;
else if (state.smartFailed) indexState = labels.indexUnavailable;
addFooter(query, total, indexState);
setOpen(true);
}
function transformApiData(data) {
var query = data && data.query ? data.query : {};
var searchInfo = query.searchinfo || {};
var items = Array.isArray(query.search) ? query.search : [];
return {
totalHits: typeof searchInfo.totalhits === 'number' ? searchInfo.totalhits : items.length,
suggestion: searchInfo.suggestion || '',
items: items.map(function (item) {
var category = categoryMap[normalizeText(item.title)] || labels.categoryFallback;
var section = cleanSearchCodeResidue(stripHtml(item.sectiontitle || ''));
var snippet = cleanSearchCodeResidue(stripHtml(item.snippet || ''));
var location = labels.insideGuide;
if (section) location += ' · ' + labels.sectionPrefix + ': ' + section;
return {
title: item.title,
label: item.title,
href: (window.mw && mw.util) ? mw.util.getUrl(item.title) : '/wiki/index.php?title=' + encodeURIComponent(item.title.replace(/ /g, '_')),
category: category,
location: location,
snippet: snippet
};
})
};
}
function searchNative(query) {
var key = normalizeText(query);
if (apiCache[key]) return Promise.resolve(apiCache[key]);
if (!api) return Promise.reject(new Error('MediaWiki API unavailable'));
return api.get({
action: 'query',
list: 'search',
srsearch: query,
srwhat: 'text',
srnamespace: 0,
srlimit: API_LIMIT,
srinfo: 'totalhits|suggestion|rewrittenquery',
srprop: 'snippet|titlesnippet|sectiontitle|redirecttitle',
srenablerewrites: 1,
formatversion: 2,
utf8: 1
}).then(function (data) {
var payload = transformApiData(data);
apiCache[key] = payload;
return payload;
});
}
function runSearch(rawQuery) {
var query = String(rawQuery || '').trim();
if (query.length < MIN_QUERY_LENGTH) {
clearResults();
return;
}
var currentRequest = ++requestSequence;
var state = {
menu: findMenuMatches(menuEntries, query),
smart: [],
api: [],
smartLoading: true,
smartFailed: false,
apiFailed: false,
suggestion: '',
totalHits: null,
indexCount: null
};
renderLoading(state.menu, query);
searchNative(query).then(function (payload) {
if (currentRequest !== requestSequence) return;
state.api = payload.items;
state.suggestion = payload.suggestion;
state.totalHits = payload.totalHits;
renderState(query, state);
}).catch(function () {
if (currentRequest !== requestSequence) return;
state.apiFailed = true;
renderState(query, state);
});
ensureSmartIndex(api, categoryMap).then(function (index) {
if (currentRequest !== requestSequence) return;
state.smart = searchSmartIndex(index, query, labels, synonymMap);
state.smartLoading = false;
state.indexCount = index.length;
renderState(query, state);
}).catch(function () {
if (currentRequest !== requestSequence) return;
state.smartLoading = false;
state.smartFailed = true;
renderState(query, state);
});
}
function getResultLinks() {
return Array.prototype.slice.call(results.querySelectorAll('.oroza-search-result-link'));
}
function setActiveResult(index) {
var links = getResultLinks();
if (!links.length) return;
if (index < 0) index = links.length - 1;
if (index >= links.length) index = 0;
activeIndex = index;
links.forEach(function (link, itemIndex) {
var active = itemIndex === activeIndex;
link.classList.toggle('is-active', active);
link.setAttribute('aria-selected', active ? 'true' : 'false');
if (active) link.scrollIntoView({ block: 'nearest' });
});
}
input.addEventListener('input', function () {
var query = input.value.trim();
clearButton.hidden = !query;
activeIndex = -1;
window.clearTimeout(debounceTimer);
if (query.length < MIN_QUERY_LENGTH) {
requestSequence++;
clearResults();
return;
}
debounceTimer = window.setTimeout(function () {
runSearch(query);
}, DEBOUNCE_MS);
});
input.addEventListener('focus', function () {
if (input.value.trim().length >= MIN_QUERY_LENGTH && results.hidden) {
runSearch(input.value);
}
});
input.addEventListener('keydown', function (event) {
if (event.key === 'ArrowDown') {
event.preventDefault();
if (results.hidden) runSearch(input.value);
setActiveResult(activeIndex + 1);
} else if (event.key === 'ArrowUp') {
event.preventDefault();
setActiveResult(activeIndex - 1);
} else if (event.key === 'Enter' && activeIndex >= 0) {
var links = getResultLinks();
if (links[activeIndex]) {
event.preventDefault();
window.location.href = links[activeIndex].href;
}
} else if (event.key === 'Escape') {
setOpen(false);
input.blur();
}
});
clearButton.addEventListener('click', function () {
input.value = '';
clearButton.hidden = true;
requestSequence++;
window.clearTimeout(debounceTimer);
clearResults();
input.focus();
});
document.addEventListener('click', function (event) {
if (!widget.contains(event.target)) setOpen(false);
});
form.addEventListener('submit', function (event) {
if (!input.value.trim()) {
event.preventDefault();
input.focus();
}
});
}
function boot(root) {
root = root || document;
var widgets = root.querySelectorAll ? root.querySelectorAll(SELECTOR) : [];
Array.prototype.forEach.call(widgets, function (widget) {
initSearch(widget.parentNode || document);
});
}
function bootWithModules(root) {
if (window.mw && mw.loader && mw.loader.using) {
mw.loader.using(['mediawiki.api', 'mediawiki.util']).then(function () {
boot(root);
}, function () {
boot(root);
});
} else {
boot(root);
}
}
window.OrozaWikiSearch = {
rebuild: function () {
clearIndexCache();
return true;
},
clearCache: clearIndexCache
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function () {
bootWithModules(document);
});
} else {
bootWithModules(document);
}
if (window.mw && mw.hook) {
mw.hook('wikipage.content').add(function ($content) {
var node = $content && $content[0] ? $content[0] : document;
bootWithModules(node);
bootWithModules(document);
});
}
})();