Diferencia entre revisiones de «MediaWiki:Common.js»
Ir a la navegación
Ir a la búsqueda
Sin resumen de edición |
Sin resumen de edición |
||
| Línea 298: | Línea 298: | ||
var node = $content && $content[0] ? $content[0] : document; | var node = $content && $content[0] ? $content[0] : document; | ||
initOrozaPetSearchV9(node); | initOrozaPetSearchV9(node); | ||
}); | |||
} | |||
})(); | |||
/* ========================================================= | |||
OROZA RO UNIVERSAL WIKI SEARCH | |||
--------------------------------------------------------- | |||
- Quick results from sidebar links and categories | |||
- Full title and page-content search through mw.Api() | |||
- Keyboard navigation, debounce, cache, and native fallback | |||
========================================================= */ | |||
(function () { | |||
'use strict'; | |||
var SELECTOR = '[data-oroza-global-search]'; | |||
var MIN_QUERY_LENGTH = 2; | |||
var API_LIMIT = 8; | |||
var MENU_LIMIT = 5; | |||
var DEBOUNCE_MS = 280; | |||
function normalizeText(value) { | |||
var text = String(value || '') | |||
.toLowerCase() | |||
.replace(/_/g, ' ') | |||
.replace(/\s+/g, ' ') | |||
.trim(); | |||
try { | |||
text = text.normalize('NFD').replace(/[\u0300-\u036f]/g, ''); | |||
} catch (e) {} | |||
return text; | |||
} | |||
function stripHtml(value) { | |||
var node = document.createElement('div'); | |||
node.innerHTML = String(value || ''); | |||
return (node.textContent || node.innerText || '') | |||
.replace(/\s+/g, ' ') | |||
.trim(); | |||
} | |||
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, systems, events, equipment...', | |||
ariaLabel: 'Search the entire Oroza RO Wiki', | |||
searchButton: 'Search', | |||
loading: 'Searching the entire Wiki...', | |||
menuGroup: 'Quick links by category', | |||
wikiGroup: 'Results across the Wiki', | |||
noResultsTitle: 'No results found', | |||
noResultsText: 'Try a shorter term, another spelling, or a related word.', | |||
categoryFallback: 'Wiki content', | |||
sectionPrefix: 'Section', | |||
resultSingular: 'result', | |||
resultPlural: 'results', | |||
viewAll: 'View all results', | |||
suggestion: 'Did you mean', | |||
apiErrorTitle: 'Live search is temporarily unavailable', | |||
apiErrorText: 'You can still press Enter or use the Search button to open MediaWiki search.' | |||
} : { | |||
placeholder: 'Buscar guías, comandos, sistemas, eventos, equipo...', | |||
ariaLabel: 'Buscar en toda la Wiki de Oroza RO', | |||
searchButton: 'Buscar', | |||
loading: 'Buscando en toda la Wiki...', | |||
menuGroup: 'Accesos rápidos por categoría', | |||
wikiGroup: 'Resultados en toda la Wiki', | |||
noResultsTitle: 'No encontramos resultados', | |||
noResultsText: 'Prueba con una palabra más corta, otra escritura o un término relacionado.', | |||
categoryFallback: 'Contenido de la Wiki', | |||
sectionPrefix: 'Sección', | |||
resultSingular: 'resultado', | |||
resultPlural: 'resultados', | |||
viewAll: 'Ver todos los resultados', | |||
suggestion: 'Quizá quisiste decir', | |||
apiErrorTitle: 'La búsqueda instantánea no está disponible temporalmente', | |||
apiErrorText: 'Aun puedes presionar Enter o usar el botón Buscar para abrir la búsqueda nativa de MediaWiki.' | |||
}; | |||
} | |||
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; | |||
var links = list.querySelectorAll('a[href]'); | |||
Array.prototype.forEach.call(links, 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 (entry.normalized.indexOf(normalizedQuery) !== -1) score = 50; | |||
return { entry: entry, score: score }; | |||
}) | |||
.filter(function (item) { return item.score > 0; }) | |||
.sort(function (a, b) { | |||
if (b.score !== a.score) return b.score - a.score; | |||
return 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 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.orozaReady === '1') return; | |||
widget.dataset.orozaReady = '1'; | |||
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'); | |||
if (!form || !input || !results) return; | |||
var labels = getLocale(); | |||
var menuEntries = collectMenuEntries(); | |||
var categoryMap = buildCategoryMap(menuEntries); | |||
var cache = 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; | |||
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) { | |||
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'); | |||
main.appendChild(createElement('span', 'oroza-search-result-title', item.label || item.title)); | |||
if (item.snippet) { | |||
main.appendChild(createElement('p', 'oroza-search-result-snippet', item.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) { | |||
var footer = createElement('div', 'oroza-search-footer'); | |||
var countText = ''; | |||
if (typeof totalHits === 'number') { | |||
countText = totalHits + ' ' + (totalHits === 1 ? labels.resultSingular : labels.resultPlural); | |||
} | |||
footer.appendChild(createElement('span', '', countText)); | |||
var link = createElement('a', '', labels.viewAll + ' →'); | |||
link.href = getNativeSearchUrl(query); | |||
footer.appendChild(link); | |||
results.appendChild(footer); | |||
} | |||
function renderLoading(menuMatches) { | |||
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, | |||
snippet: '' | |||
}); | |||
}); | |||
} | |||
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 renderEmpty(query, menuMatches) { | |||
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, | |||
snippet: '' | |||
}); | |||
}); | |||
addFooter(query, menuMatches.length); | |||
} else { | |||
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); | |||
addFooter(query, 0); | |||
} | |||
setOpen(true); | |||
} | |||
function renderApiError(query, menuMatches) { | |||
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, | |||
snippet: '' | |||
}); | |||
}); | |||
} | |||
var empty = createElement('div', 'oroza-search-empty'); | |||
empty.appendChild(createElement('span', 'oroza-search-empty-icon', '⚠️')); | |||
empty.appendChild(createElement('p', 'oroza-search-empty-title', labels.apiErrorTitle)); | |||
empty.appendChild(createElement('p', 'oroza-search-empty-text', labels.apiErrorText)); | |||
results.appendChild(empty); | |||
addFooter(query); | |||
setOpen(true); | |||
} | |||
function renderResults(query, menuMatches, payload) { | |||
while (results.firstChild) results.removeChild(results.firstChild); | |||
var apiResults = payload && payload.items ? payload.items : []; | |||
var seen = Object.create(null); | |||
var visibleMenuMatches = []; | |||
var visibleApiResults = []; | |||
menuMatches.forEach(function (item) { | |||
var key = normalizeText(item.title); | |||
if (!key || seen[key]) return; | |||
seen[key] = true; | |||
visibleMenuMatches.push(item); | |||
}); | |||
apiResults.forEach(function (item) { | |||
var key = normalizeText(item.title); | |||
if (!key || seen[key]) return; | |||
seen[key] = true; | |||
visibleApiResults.push(item); | |||
}); | |||
if (!visibleMenuMatches.length && !visibleApiResults.length) { | |||
renderEmpty(query, []); | |||
return; | |||
} | |||
if (visibleMenuMatches.length) { | |||
addGroupTitle(labels.menuGroup); | |||
visibleMenuMatches.forEach(function (item) { | |||
addResultItem({ | |||
title: item.title, | |||
label: item.label, | |||
href: item.href, | |||
category: item.category, | |||
snippet: '' | |||
}); | |||
}); | |||
} | |||
if (visibleApiResults.length) { | |||
addGroupTitle(labels.wikiGroup); | |||
visibleApiResults.forEach(addResultItem); | |||
} | |||
addSuggestion(payload.suggestion); | |||
addFooter(query, payload.totalHits); | |||
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 = stripHtml(item.sectiontitle || ''); | |||
var snippet = stripHtml(item.snippet || ''); | |||
if (section) { | |||
snippet = labels.sectionPrefix + ': ' + section + (snippet ? ' — ' + snippet : ''); | |||
} | |||
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, | |||
snippet: snippet | |||
}; | |||
}) | |||
}; | |||
} | |||
function runSearch(rawQuery) { | |||
var query = String(rawQuery || '').trim(); | |||
var menuMatches = findMenuMatches(menuEntries, query); | |||
var normalizedQuery = normalizeText(query); | |||
if (query.length < MIN_QUERY_LENGTH) { | |||
clearResults(); | |||
return; | |||
} | |||
renderLoading(menuMatches); | |||
if (cache[normalizedQuery]) { | |||
renderResults(query, menuMatches, cache[normalizedQuery]); | |||
return; | |||
} | |||
if (!api) { | |||
renderApiError(query, menuMatches); | |||
return; | |||
} | |||
var thisRequest = ++requestSequence; | |||
api.get({ | |||
action: 'query', | |||
list: 'search', | |||
srsearch: query, | |||
srnamespace: 0, | |||
srlimit: API_LIMIT, | |||
srinfo: 'totalhits|suggestion|rewrittenquery', | |||
srprop: 'snippet|titlesnippet|sectiontitle|redirecttitle', | |||
srenablerewrites: 1, | |||
formatversion: 2, | |||
utf8: 1 | |||
}).done(function (data) { | |||
if (thisRequest !== requestSequence) return; | |||
var payload = transformApiData(data); | |||
cache[normalizedQuery] = payload; | |||
renderResults(query, menuMatches, payload); | |||
}).fail(function () { | |||
if (thisRequest !== requestSequence) return; | |||
renderApiError(query, menuMatches); | |||
}); | |||
} | |||
function getResultLinks() { | |||
return Array.prototype.slice.call(results.querySelectorAll('.oroza-search-result-link')); | |||
} | |||
function setActiveResult(index) { | |||
var links = getResultLinks(); | |||
if (!links.length) return; | |||
links.forEach(function (link) { | |||
link.classList.remove('is-active'); | |||
link.setAttribute('aria-selected', 'false'); | |||
}); | |||
if (index < 0) index = links.length - 1; | |||
if (index >= links.length) index = 0; | |||
activeIndex = index; | |||
links[activeIndex].classList.add('is-active'); | |||
links[activeIndex].setAttribute('aria-selected', 'true'); | |||
links[activeIndex].scrollIntoView({ block: 'nearest' }); | |||
} | |||
input.addEventListener('input', function () { | |||
var query = input.value; | |||
clearButton.hidden = !query; | |||
activeIndex = -1; | |||
window.clearTimeout(debounceTimer); | |||
if (String(query || '').trim().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.childNodes.length) { | |||
setOpen(true); | |||
} | |||
}); | |||
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 bootWithMediaWikiModules(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); | |||
} | |||
} | |||
if (document.readyState === 'loading') { | |||
document.addEventListener('DOMContentLoaded', function () { | |||
bootWithMediaWikiModules(document); | |||
}); | |||
} else { | |||
bootWithMediaWikiModules(document); | |||
} | |||
if (window.mw && mw.hook) { | |||
mw.hook('wikipage.content').add(function ($content) { | |||
var node = $content && $content[0] ? $content[0] : document; | |||
bootWithMediaWikiModules(node); | |||
bootWithMediaWikiModules(document); | |||
}); | }); | ||
} | } | ||
})(); | })(); | ||
Revisión del 10:53 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(/\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 UNIVERSAL WIKI SEARCH
---------------------------------------------------------
- Quick results from sidebar links and categories
- Full title and page-content search through mw.Api()
- Keyboard navigation, debounce, cache, and native fallback
========================================================= */
(function () {
'use strict';
var SELECTOR = '[data-oroza-global-search]';
var MIN_QUERY_LENGTH = 2;
var API_LIMIT = 8;
var MENU_LIMIT = 5;
var DEBOUNCE_MS = 280;
function normalizeText(value) {
var text = String(value || '')
.toLowerCase()
.replace(/_/g, ' ')
.replace(/\s+/g, ' ')
.trim();
try {
text = text.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
} catch (e) {}
return text;
}
function stripHtml(value) {
var node = document.createElement('div');
node.innerHTML = String(value || '');
return (node.textContent || node.innerText || '')
.replace(/\s+/g, ' ')
.trim();
}
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, systems, events, equipment...',
ariaLabel: 'Search the entire Oroza RO Wiki',
searchButton: 'Search',
loading: 'Searching the entire Wiki...',
menuGroup: 'Quick links by category',
wikiGroup: 'Results across the Wiki',
noResultsTitle: 'No results found',
noResultsText: 'Try a shorter term, another spelling, or a related word.',
categoryFallback: 'Wiki content',
sectionPrefix: 'Section',
resultSingular: 'result',
resultPlural: 'results',
viewAll: 'View all results',
suggestion: 'Did you mean',
apiErrorTitle: 'Live search is temporarily unavailable',
apiErrorText: 'You can still press Enter or use the Search button to open MediaWiki search.'
} : {
placeholder: 'Buscar guías, comandos, sistemas, eventos, equipo...',
ariaLabel: 'Buscar en toda la Wiki de Oroza RO',
searchButton: 'Buscar',
loading: 'Buscando en toda la Wiki...',
menuGroup: 'Accesos rápidos por categoría',
wikiGroup: 'Resultados en toda la Wiki',
noResultsTitle: 'No encontramos resultados',
noResultsText: 'Prueba con una palabra más corta, otra escritura o un término relacionado.',
categoryFallback: 'Contenido de la Wiki',
sectionPrefix: 'Sección',
resultSingular: 'resultado',
resultPlural: 'resultados',
viewAll: 'Ver todos los resultados',
suggestion: 'Quizá quisiste decir',
apiErrorTitle: 'La búsqueda instantánea no está disponible temporalmente',
apiErrorText: 'Aun puedes presionar Enter o usar el botón Buscar para abrir la búsqueda nativa de MediaWiki.'
};
}
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;
var links = list.querySelectorAll('a[href]');
Array.prototype.forEach.call(links, 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 (entry.normalized.indexOf(normalizedQuery) !== -1) score = 50;
return { entry: entry, score: score };
})
.filter(function (item) { return item.score > 0; })
.sort(function (a, b) {
if (b.score !== a.score) return b.score - a.score;
return 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 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.orozaReady === '1') return;
widget.dataset.orozaReady = '1';
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');
if (!form || !input || !results) return;
var labels = getLocale();
var menuEntries = collectMenuEntries();
var categoryMap = buildCategoryMap(menuEntries);
var cache = 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;
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) {
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');
main.appendChild(createElement('span', 'oroza-search-result-title', item.label || item.title));
if (item.snippet) {
main.appendChild(createElement('p', 'oroza-search-result-snippet', item.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) {
var footer = createElement('div', 'oroza-search-footer');
var countText = '';
if (typeof totalHits === 'number') {
countText = totalHits + ' ' + (totalHits === 1 ? labels.resultSingular : labels.resultPlural);
}
footer.appendChild(createElement('span', '', countText));
var link = createElement('a', '', labels.viewAll + ' →');
link.href = getNativeSearchUrl(query);
footer.appendChild(link);
results.appendChild(footer);
}
function renderLoading(menuMatches) {
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,
snippet: ''
});
});
}
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 renderEmpty(query, menuMatches) {
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,
snippet: ''
});
});
addFooter(query, menuMatches.length);
} else {
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);
addFooter(query, 0);
}
setOpen(true);
}
function renderApiError(query, menuMatches) {
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,
snippet: ''
});
});
}
var empty = createElement('div', 'oroza-search-empty');
empty.appendChild(createElement('span', 'oroza-search-empty-icon', '⚠️'));
empty.appendChild(createElement('p', 'oroza-search-empty-title', labels.apiErrorTitle));
empty.appendChild(createElement('p', 'oroza-search-empty-text', labels.apiErrorText));
results.appendChild(empty);
addFooter(query);
setOpen(true);
}
function renderResults(query, menuMatches, payload) {
while (results.firstChild) results.removeChild(results.firstChild);
var apiResults = payload && payload.items ? payload.items : [];
var seen = Object.create(null);
var visibleMenuMatches = [];
var visibleApiResults = [];
menuMatches.forEach(function (item) {
var key = normalizeText(item.title);
if (!key || seen[key]) return;
seen[key] = true;
visibleMenuMatches.push(item);
});
apiResults.forEach(function (item) {
var key = normalizeText(item.title);
if (!key || seen[key]) return;
seen[key] = true;
visibleApiResults.push(item);
});
if (!visibleMenuMatches.length && !visibleApiResults.length) {
renderEmpty(query, []);
return;
}
if (visibleMenuMatches.length) {
addGroupTitle(labels.menuGroup);
visibleMenuMatches.forEach(function (item) {
addResultItem({
title: item.title,
label: item.label,
href: item.href,
category: item.category,
snippet: ''
});
});
}
if (visibleApiResults.length) {
addGroupTitle(labels.wikiGroup);
visibleApiResults.forEach(addResultItem);
}
addSuggestion(payload.suggestion);
addFooter(query, payload.totalHits);
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 = stripHtml(item.sectiontitle || '');
var snippet = stripHtml(item.snippet || '');
if (section) {
snippet = labels.sectionPrefix + ': ' + section + (snippet ? ' — ' + snippet : '');
}
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,
snippet: snippet
};
})
};
}
function runSearch(rawQuery) {
var query = String(rawQuery || '').trim();
var menuMatches = findMenuMatches(menuEntries, query);
var normalizedQuery = normalizeText(query);
if (query.length < MIN_QUERY_LENGTH) {
clearResults();
return;
}
renderLoading(menuMatches);
if (cache[normalizedQuery]) {
renderResults(query, menuMatches, cache[normalizedQuery]);
return;
}
if (!api) {
renderApiError(query, menuMatches);
return;
}
var thisRequest = ++requestSequence;
api.get({
action: 'query',
list: 'search',
srsearch: query,
srnamespace: 0,
srlimit: API_LIMIT,
srinfo: 'totalhits|suggestion|rewrittenquery',
srprop: 'snippet|titlesnippet|sectiontitle|redirecttitle',
srenablerewrites: 1,
formatversion: 2,
utf8: 1
}).done(function (data) {
if (thisRequest !== requestSequence) return;
var payload = transformApiData(data);
cache[normalizedQuery] = payload;
renderResults(query, menuMatches, payload);
}).fail(function () {
if (thisRequest !== requestSequence) return;
renderApiError(query, menuMatches);
});
}
function getResultLinks() {
return Array.prototype.slice.call(results.querySelectorAll('.oroza-search-result-link'));
}
function setActiveResult(index) {
var links = getResultLinks();
if (!links.length) return;
links.forEach(function (link) {
link.classList.remove('is-active');
link.setAttribute('aria-selected', 'false');
});
if (index < 0) index = links.length - 1;
if (index >= links.length) index = 0;
activeIndex = index;
links[activeIndex].classList.add('is-active');
links[activeIndex].setAttribute('aria-selected', 'true');
links[activeIndex].scrollIntoView({ block: 'nearest' });
}
input.addEventListener('input', function () {
var query = input.value;
clearButton.hidden = !query;
activeIndex = -1;
window.clearTimeout(debounceTimer);
if (String(query || '').trim().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.childNodes.length) {
setOpen(true);
}
});
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 bootWithMediaWikiModules(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);
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function () {
bootWithMediaWikiModules(document);
});
} else {
bootWithMediaWikiModules(document);
}
if (window.mw && mw.hook) {
mw.hook('wikipage.content').add(function ($content) {
var node = $content && $content[0] ? $content[0] : document;
bootWithMediaWikiModules(node);
bootWithMediaWikiModules(document);
});
}
})();