MediaWiki:Common.js: Difference between revisions
Jump to navigation
Jump to search
No edit summary Tag: Reverted |
No edit summary Tag: Manual revert |
||
| Line 1: | Line 1: | ||
/ | // MainPage2 interactions. Designed for MediaWiki site usage. | ||
(function(){ | |||
(function() { | |||
'use strict'; | 'use strict'; | ||
// ====== | // Simple tab controller (inspired by GuardianGuide.html) | ||
window.showTab = function(tabId, el){ | |||
var contents = document.querySelectorAll('.tab-content'); | |||
for (var i=0;i<contents.length;i++){ contents[i].classList.remove('active'); } | |||
var tabs = document.querySelectorAll('.nav-tab'); | |||
for (var j=0;j<tabs.length;j++){ tabs[j].classList.remove('active'); } | |||
var target = document.getElementById(tabId); | |||
if (target) target.classList.add('active'); | |||
if (el) el.classList.add('active'); | |||
}; | |||
/ | // MediaWiki-safe tab click handler using event delegation | ||
function onTabClick(e) { | |||
var tab = e.target.closest('.nav-tab[data-tab]'); | |||
if (!tab) return; | |||
e.preventDefault(); | |||
var targetId = tab.getAttribute('data-tab'); | |||
if (targetId) { | |||
showTab(targetId, tab); | |||
} | } | ||
}; | } | ||
document.addEventListener('click', onTabClick, false); | |||
// | // Handle special navigation clicks for tabs (like in Hologramsie) | ||
function onSpecialTabNav(e) { | |||
var trigger = e.target.closest('[data-tab-trigger]'); | |||
if (!trigger) return; | |||
e.preventDefault(); | |||
var tabData = trigger.getAttribute('data-tab-trigger'); | |||
var buttonId = trigger.getAttribute('data-tab-button'); | |||
// Support anchors via href="#section" when data-scroll-to is not provided | |||
var href = trigger.getAttribute('href'); | |||
var scrollToAttr = trigger.getAttribute('data-scroll-to'); | |||
var scrollTargetSelector = null; | |||
if (scrollToAttr && scrollToAttr.trim()) { | |||
scrollTargetSelector = '#' + scrollToAttr.replace(/^#/, ''); | |||
} else if (href && href.charAt(0) === '#') { | |||
scrollTargetSelector = href; // already a selector | |||
} | |||
if (tabData && buttonId) { | |||
var targetButton = document.getElementById(buttonId); | |||
if (targetButton) { | |||
showTab(tabData, targetButton); | |||
// Scroll to anchor if specified (from data-scroll-to or href) | |||
if (scrollTargetSelector) { | |||
var element = document.querySelector(scrollTargetSelector); | |||
if (element) { | |||
setTimeout(function() { | |||
element.scrollIntoView({behavior: 'smooth'}); | |||
}, 100); | |||
} | |||
} | |||
} | |||
} | } | ||
} | } | ||
document.addEventListener('click', onSpecialTabNav, false); | |||
/ | // Back to Top smooth scroll | ||
function onBackToTop(e){ | |||
var trigger = e.target.closest('.back-to-top'); | |||
if (!trigger) return; | |||
e.preventDefault(); | |||
window.scrollTo({ top: 0, behavior: 'smooth' }); | |||
function | |||
} | } | ||
document.addEventListener('click', onBackToTop, false); | |||
/ | // Card click-throughs using data-link to wiki pages or in-page anchors | ||
function onCardClick(e){ | |||
var card = e.target.closest('.card[data-link], .destaque-card[data-link]'); | |||
if (!card) return; | |||
var link = card.getAttribute('data-link'); | |||
if (!link) return; | |||
function | |||
// In-page anchor navigation: data-link="#section-id" | |||
if (link.charAt(0) === '#') { | |||
e.preventDefault(); | |||
var anchorEl = document.querySelector(link); | |||
if (anchorEl) { | |||
anchorEl.scrollIntoView({ behavior: 'smooth', block: 'start' }); | |||
} | |||
return; | |||
return | |||
} | } | ||
// Absolute external URL | |||
if (/^https?:\/\//i.test(link)) { | |||
window.location.href = link; | |||
return; | |||
return | |||
} | } | ||
// Navigate to the MediaWiki page (prefer mw.util if available) | |||
var targetUrl = (window.mw && mw.util && typeof mw.util.getUrl === 'function') | |||
? mw.util.getUrl(link) | |||
: ('index.php?title=' + encodeURIComponent(link)); | |||
window.location.href = targetUrl; | |||
} | } | ||
document.addEventListener('click', onCardClick, false); | |||
/ | // Helper: determine if a node is inside an excluded container | ||
function isInExcludedContext(node) { | function isInExcludedContext(node) { | ||
if (!node || !node.parentElement) return false; | if (!node || !node.parentElement) return false; | ||
var p = node.parentElement.closest('script, style, textarea, input, select, option, .ve-ui-surface, .mw-editform, .CodeMirror, .cm-editor, .ace_editor'); | |||
return !!p; | |||
return !! | |||
} | } | ||
// = | // Safely process MediaWiki-like internal link markup [[Title]] -> <a href="...">Title</a> | ||
// | function processWikiLinks() { | ||
// Skip if the page was already parsed by MediaWiki (no raw brackets found) | |||
if (document.body && document.body.textContent.indexOf('[[') === -1) return; | |||
var walker = document.createTreeWalker( | |||
document.body, | |||
NodeFilter.SHOW_TEXT, | |||
null, | |||
false | |||
); | |||
var textNodes = []; | |||
var node; | |||
while ((node = walker.nextNode())) { | |||
var value = node.nodeValue; | |||
if (!value) continue; | |||
if (value.indexOf('[[') === -1) continue; | |||
if (isInExcludedContext(node)) continue; | |||
textNodes.push(node); | |||
} | |||
var linkRe = /\[\[([^\|\]]+)(?:\|([^\]]+))?\]\]/g; | |||
textNodes.forEach(function(textNode) { | |||
var src = textNode.nodeValue; | |||
if (!linkRe.test(src)) return; // quick check | |||
linkRe.lastIndex = 0; // reset stateful regex | |||
var parent = textNode.parentNode; | |||
var frag = document.createDocumentFragment(); | |||
var last = 0; var m; | |||
while ((m = linkRe.exec(src)) !== null) { | |||
// text before match | |||
if (m.index > last) frag.appendChild(document.createTextNode(src.slice(last, m.index))); | |||
var rawTitle = (m[1] || '').trim(); | |||
var display = (m[2] != null ? m[2] : rawTitle).trim(); | |||
// Skip File: or Image: links - keep original text intact | |||
if (/^(File:|Image:)/i.test(rawTitle)) { | |||
frag.appendChild(document.createTextNode(m[0])); | |||
} else { | |||
// Remove leading colon if present | |||
if (rawTitle.charAt(0) === ':') rawTitle = rawTitle.slice(1); | |||
var href = (window.mw && mw.util && typeof mw.util.getUrl === 'function') | |||
? mw.util.getUrl(rawTitle) | |||
: ('index.php?title=' + encodeURIComponent(rawTitle)); | |||
var a = document.createElement('a'); | |||
a.className = 'mw-link-internal'; | |||
a.setAttribute('href', href); | |||
a.appendChild(document.createTextNode(display)); // avoid HTML injection | |||
frag.appendChild(a); | |||
} | |||
last = linkRe.lastIndex; | |||
} | } | ||
// | // trailing text | ||
if ( | if (last < src.length) frag.appendChild(document.createTextNode(src.slice(last))); | ||
// Replace the original text node safely | |||
parent.insertBefore(frag, textNode); | |||
parent.removeChild(textNode); | |||
}); | |||
} | |||
// Process wiki links when DOM is ready | |||
if (document.readyState === 'loading') { | |||
document.addEventListener('DOMContentLoaded', processWikiLinks); | |||
} else { | |||
processWikiLinks(); | |||
} | |||
// Mobile hamburger menu for sidebar | |||
function createHamburgerMenu() { | |||
// Only create if we're in MediaWiki Vector Legacy and on mobile | |||
if (!document.body.classList.contains('skin-vector-legacy')) return; | |||
var panel = document.getElementById('mw-panel'); | |||
if (!panel) return; | |||
// Create hamburger button | |||
var hamburger = document.createElement('button'); | |||
hamburger.className = 'mobile-hamburger-menu'; | |||
hamburger.setAttribute('aria-label', 'Toggle navigation menu'); | |||
hamburger.innerHTML = '<span></span><span></span><span></span>'; | |||
// Insert hamburger button at the top of the page | |||
var content = document.getElementById('content') || document.querySelector('.mw-body'); | |||
if (content) { | |||
content.parentNode.insertBefore(hamburger, content); | |||
/ | |||
} | } | ||
// Toggle sidebar function | |||
function toggleSidebar() { | |||
panel.classList.toggle('mobile-open'); | |||
hamburger.classList.toggle('active'); | |||
document.body.classList.toggle('sidebar-open'); | |||
/ | |||
document. | |||
} | } | ||
// Click handler for hamburger | |||
hamburger.addEventListener('click', function(e) { | |||
e.preventDefault(); | |||
e.stopPropagation(); | |||
toggleSidebar(); | |||
}); | |||
// Close sidebar when clicking outside | |||
document.addEventListener('click', function(e) { | |||
if (panel.classList.contains('mobile-open') && | |||
/ | !panel.contains(e.target) && | ||
!hamburger.contains(e.target)) { | |||
panel.classList.remove('mobile-open'); | |||
hamburger.classList.remove('active'); | |||
document.body.classList.remove('sidebar-open'); | |||
} | } | ||
}); | |||
// Close sidebar on escape key | |||
document.addEventListener('keydown', function(e) { | |||
if (e.key === 'Escape' && panel.classList.contains('mobile-open')) { | |||
panel.classList.remove('mobile-open'); | |||
hamburger.classList.remove('active'); | |||
document.body.classList.remove('sidebar-open'); | |||
} | } | ||
}); | |||
} | |||
// Collapsible sections functionality | |||
function initCollapsibleSections() { | |||
// Handle .collapsible-header elements (new style) | |||
var collapsibleHeaders = document.querySelectorAll('.collapsible-header'); | |||
collapsibleHeaders.forEach(function(header) { | |||
header.addEventListener('click', function(e) { | |||
e.preventDefault(); | |||
var section = header.parentElement; | |||
var content = section.querySelector('.collapsible-content'); | |||
if (content) { | |||
var isExpanded = section.classList.contains('expanded'); | |||
if (isExpanded) { | |||
section.classList.remove('expanded'); | |||
content.style.maxHeight = '0'; | |||
if ( | |||
} else { | } else { | ||
section.classList.add('expanded'); | |||
content.style.maxHeight = content.scrollHeight + 'px'; | |||
} | } | ||
} | } | ||
}); | }); | ||
} | }); | ||
/ | // Handle .collapsible elements (original GuardianGuide style) | ||
var collapsibleButtons = document.querySelectorAll('.collapsible'); | |||
collapsibleButtons.forEach(function(button) { | |||
button.addEventListener('click', function(e) { | |||
e.preventDefault(); | |||
var content = button.nextElementSibling; | |||
if (content && content.classList.contains('collapsible-content')) { | |||
var isActive = button.classList.contains('active'); | |||
if (isActive) { | |||
button.classList.remove('active'); | |||
content.style.maxHeight = '0'; | |||
} else { | |||
button.classList.add('active'); | |||
content.style.maxHeight = content.scrollHeight + 'px'; | |||
} | |||
} | } | ||
}); | }); | ||
}); | |||
} | |||
// Vector Legacy Sidebar Collapsible functionality | |||
function initSidebarCollapsible() { | |||
// Only initialize if we're in Vector Legacy skin | |||
if (!document.body.classList.contains('skin-vector-legacy')) return; | |||
var panel = document.getElementById('mw-panel'); | |||
if (!panel) return; | |||
// Get all portals (both vector-menu-portal and portal classes) | |||
var portals = panel.querySelectorAll('.vector-menu-portal, .portal'); | |||
// Auto-collapse all sections except the first one (Navigation) on page load | |||
portals.forEach(function(portal, index) { | |||
if (index > 0) { // Skip first portal (Navigation) | |||
portal.classList.add('collapsed'); | |||
var content = portal.querySelector('.vector-menu-content, .body'); | |||
if (content) { | |||
content.style.maxHeight = '0'; | |||
} | } | ||
}); | } | ||
}); | |||
/ | // Add click handlers to portal headings | ||
var headings = panel.querySelectorAll('.vector-menu-heading, .portal h3'); | |||
headings.forEach(function(heading) { | |||
heading.addEventListener('click', function(e) { | |||
e.preventDefault(); | |||
e.stopPropagation(); | |||
var portal = heading.closest('.vector-menu-portal, .portal'); | |||
if (!portal) return; | |||
var content = portal.querySelector('.vector-menu-content, .body'); | |||
if (!content) return; | |||
var isCollapsed = portal.classList.contains('collapsed'); | |||
if (isCollapsed) { | |||
// Expand | |||
portal.classList.remove('collapsed'); | |||
content.style.maxHeight = content.scrollHeight + 'px'; | |||
// Store the expanded state | |||
try { | |||
localStorage.setItem('sidebar-' + getPortalId(portal), 'expanded'); | |||
} catch (e) { | |||
// Ignore localStorage errors | |||
} | |||
} else { | |||
// Collapse | |||
portal.classList.add('collapsed'); | |||
content.style.maxHeight = '0'; | |||
// Store the collapsed state | |||
try { | |||
localStorage.setItem('sidebar-' + getPortalId(portal), 'collapsed'); | |||
} catch (e) { | |||
// Ignore localStorage errors | |||
} | } | ||
} | } | ||
}); | }); | ||
} | }); | ||
/ | // Restore saved states from localStorage | ||
portals.forEach(function(portal, index) { | |||
if (index === 0) return; // Skip first portal (always expanded) | |||
try { | |||
var savedState = localStorage.getItem('sidebar-' + getPortalId(portal)); | |||
var content = portal.querySelector('.vector-menu-content, .body'); | |||
if (savedState === 'expanded' && content) { | |||
portal.classList.remove('collapsed'); | |||
content.style.maxHeight = content.scrollHeight + 'px'; | |||
} | |||
} catch (e) { | |||
// Ignore localStorage errors | |||
} | |||
}); | |||
// Helper function to get a unique ID for each portal | |||
function getPortalId(portal) { | |||
var heading = portal.querySelector('.vector-menu-heading, h3'); | |||
} | |||
}) | |||
getPortalId | |||
if (heading) { | if (heading) { | ||
return heading.textContent.trim().toLowerCase().replace(/\s+/g, '-'); | return heading.textContent.trim().toLowerCase().replace(/\s+/g, '-'); | ||
} | } | ||
return 'unknown-portal'; | return 'unknown-portal'; | ||
} | } | ||
/ | // Recalculate heights when window is resized | ||
window.addEventListener('resize', function() { | |||
portals.forEach(function(portal) { | |||
if (!portal.classList.contains('collapsed')) { | |||
var content = portal.querySelector('.vector-menu-content, .body'); | |||
portals.forEach((portal | |||
if ( | |||
if (content) { | if (content) { | ||
content.style.maxHeight = ' | content.style.maxHeight = content.scrollHeight + 'px'; | ||
} | } | ||
} | } | ||
}); | }); | ||
}); | |||
} | |||
// Guardian Type Decision Helper functionality | |||
function initGuardianDecisionHelper() { | |||
// Initialize accordion functionality for guardian types | |||
function initAccordion() { | |||
const accordions = document.getElementsByClassName("guardian-accordion"); | |||
for (let i = 0; i < accordions.length; i++) { | |||
accordions[i].addEventListener("click", function() { | |||
this.classList.toggle("active"); | |||
const panel = this.nextElementSibling; | |||
if (panel.style.maxHeight) { | |||
panel.style.maxHeight = null; | |||
const | |||
if ( | |||
} else { | } else { | ||
panel.style.maxHeight = panel.scrollHeight + "px"; | |||
} | } | ||
}); | }); | ||
} | } | ||
} | } | ||
// Function to show guardian by difficulty level | |||
function showGuardianByDifficulty(level) { | |||
const accordions = document.getElementsByClassName("guardian-accordion"); | |||
let targetAccordion; | |||
// Reset all accordions | |||
for (let i = 0; i < accordions.length; i++) { | |||
accordions[i].classList.remove("active"); | |||
accordions[i].nextElementSibling.style.maxHeight = null; | |||
} | |||
// Select the appropriate accordion based on difficulty level | |||
switch(level) { | |||
case "1": | |||
targetAccordion = document.querySelector(".regular-accordion"); | |||
break; | |||
case "2": | |||
targetAccordion = document.querySelector(".mighty-accordion"); | |||
break; | |||
case "3": | |||
targetAccordion = document.querySelector(".legendary-accordion"); | |||
break; | |||
case "4": | |||
targetAccordion = document.querySelector(".superior-accordion"); | |||
break; | |||
case "5": | |||
targetAccordion = document.querySelector(".accomplished-accordion"); | |||
break; | |||
} | |||
// | // Activate the target accordion | ||
if (targetAccordion) { | |||
targetAccordion.classList.add("active"); | |||
const panel = targetAccordion.nextElementSibling; | |||
panel.style.maxHeight = panel.scrollHeight + "px"; | |||
// Scroll to the accordion | |||
setTimeout(function() { | |||
targetAccordion.scrollIntoView({ behavior: 'smooth', block: 'center' }); | |||
}, 300); | |||
if ( | |||
} | } | ||
} | } | ||
/ | // Initialize the decision helper | ||
function initDecisionHelper() { | |||
// Find all option elements | |||
initDecisionHelper | const options = document.querySelectorAll('.decision-options .option'); | ||
const options = | |||
// Add click event listeners to each option | |||
options.forEach(option => { | options.forEach(option => { | ||
option.addEventListener('click', function() { | option.addEventListener('click', function() { | ||
// Remove selected class from siblings | // Remove selected class from siblings | ||
const siblings = this.parentElement.querySelectorAll('.option'); | const siblings = this.parentElement.querySelectorAll('.option'); | ||
siblings.forEach( | siblings.forEach(sib => sib.classList.remove('selected')); | ||
// Add selected class to clicked option | // Add selected class to clicked option | ||
| Line 882: | Line 466: | ||
// Update recommendation | // Update recommendation | ||
updateRecommendation(); | |||
}); | }); | ||
}); | }); | ||
} | } | ||
/ | // Function to update recommendation based on selections | ||
function updateRecommendation() { | |||
const selectedOptions = document.querySelectorAll('.decision-options .option.selected'); | |||
updateRecommendation | |||
const selectedOptions = | |||
// If no options selected, show default message | |||
// | |||
if (selectedOptions.length === 0) { | if (selectedOptions.length === 0) { | ||
document.getElementById('recommendedType').textContent = 'Select options above'; | |||
return; | return; | ||
} | } | ||
// Count votes for each guardian type | // Count votes for each guardian type | ||
const votes = { | const votes = { | ||
| Line 910: | Line 489: | ||
'accomplished': 0 | 'accomplished': 0 | ||
}; | }; | ||
// Tally votes from selected options | // Tally votes from selected options | ||
selectedOptions.forEach(option => { | selectedOptions.forEach(option => { | ||
const types = option.getAttribute('data-type') | const types = option.getAttribute('data-type').split(','); | ||
types.forEach(type => { | |||
votes[type]++; | |||
}); | |||
}); | }); | ||
// Find type with most votes | // Find the type with the most votes | ||
let maxVotes = 0; | let maxVotes = 0; | ||
let recommendedType = ''; | let recommendedType = ''; | ||
let tiedTypes = []; | let tiedTypes = []; | ||
for (const type in votes) { | for (const type in votes) { | ||
if (votes[type] > maxVotes) { | if (votes[type] > maxVotes) { | ||
| Line 937: | Line 512: | ||
} | } | ||
} | } | ||
// Handle ties by | // Handle ties by recommending the more challenging type | ||
if (tiedTypes.length > 1) { | if (tiedTypes.length > 1) { | ||
const difficultyOrder = ['regular', 'mighty', 'legendary', 'superior', 'accomplished']; | const difficultyOrder = ['regular', 'mighty', 'legendary', 'superior', 'accomplished']; | ||
| Line 951: | Line 526: | ||
}); | }); | ||
} | } | ||
// Format recommendation | // Format the recommendation with additional context | ||
let formattedType = recommendedType.charAt(0).toUpperCase() + recommendedType.slice(1) + ' Guardian'; | |||
let recommendationText = formattedType; | |||
// Add context based on the recommended type | |||
// Update | switch(recommendedType) { | ||
case 'regular': | |||
recommendationText += ' - Perfect for beginners with limited time'; | |||
} | break; | ||
case 'mighty': | |||
/ | recommendationText += ' - Good balance of effort and power'; | ||
break; | |||
case 'legendary': | |||
recommendationText += ' - Excellent for endgame content'; | |||
highlightRecommendedType | break; | ||
case 'superior': | |||
recommendationText += ' - For dedicated players seeking optimization'; | |||
break; | |||
case 'accomplished': | |||
recommendationText += ' - For true perfectionists'; | |||
break; | |||
} | |||
// Update the recommendation text | |||
document.getElementById('recommendedType').textContent = recommendationText; | |||
// Highlight the recommended guardian type in the accordion | |||
highlightRecommendedType(recommendedType); | |||
} | |||
// Function to highlight the recommended guardian type in the accordion | |||
function highlightRecommendedType(type) { | |||
// Remove highlight from all accordions | // Remove highlight from all accordions | ||
const accordions = | const accordions = document.querySelectorAll('.guardian-accordion'); | ||
accordions.forEach(accordion => { | accordions.forEach(accordion => { | ||
accordion.classList.remove( | accordion.classList.remove('recommended'); | ||
}); | }); | ||
// Add highlight to recommended type | // Add highlight to the recommended type | ||
const recommendedAccordion = | const recommendedAccordion = document.querySelector('.' + type + '-accordion'); | ||
if (recommendedAccordion) { | if (recommendedAccordion) { | ||
recommendedAccordion.classList.add( | recommendedAccordion.classList.add('recommended'); | ||
// Scroll to recommended type if not visible | // Scroll to the recommended type if not visible | ||
const isVisible = isElementInViewport(recommendedAccordion); | |||
recommendedAccordion.scrollIntoView({ | if (!isVisible) { | ||
recommendedAccordion.scrollIntoView({ behavior: 'smooth', block: 'center' }); | |||
} | } | ||
} | } | ||
} | } | ||
/ | // Helper function to check if an element is in the viewport | ||
function isElementInViewport(el) { | |||
const rect = el.getBoundingClientRect(); | |||
return ( | |||
rect.top >= 0 && | |||
rect.left >= 0 && | |||
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && | |||
rect.right <= (window.innerWidth || document.documentElement.clientWidth) | |||
); | |||
} | |||
// Initialize components if they exist on the page | |||
if (document.querySelector('.guardian-accordion') || document.querySelector('.decision-options')) { | |||
initAccordion(); | |||
initDecisionHelper(); | |||
// Make functions available globally | |||
window.updateGuardianRecommendation = updateRecommendation; | |||
window.showGuardianByDifficulty = showGuardianByDifficulty; | |||
} | } | ||
} | } | ||
// | // User menu collapsible functionality | ||
function initUserMenuCollapsible() { | |||
// Only initialize if we're in Vector Legacy skin | |||
if (!document.body.classList.contains('skin-vector-legacy')) return; | |||
// Get the personal tools container | |||
var personalTools = document.getElementById('p-personal'); | |||
if (!personalTools) return; | |||
// Get the list of user menu items | |||
var userMenuItems = [ | |||
document.getElementById('pt-userpage'), | |||
document.getElementById('pt-mytalk'), | |||
document.getElementById('pt-preferences'), | |||
document.getElementById('pt-watchlist'), | |||
document.getElementById('pt-mycontris'), | |||
document.getElementById('pt-logout') | |||
].filter(Boolean); // Filter out any null items | |||
if (userMenuItems.length === 0) return; | |||
// Create a container for our collapsible menu | |||
var menuContainer = document.createElement('div'); | |||
menuContainer.id = 'user-menu-collapsible'; | |||
menuContainer.className = 'user-menu-container'; | |||
// Create the header/toggle button | |||
var menuHeader = document.createElement('div'); | |||
menuHeader.className = 'user-menu-header'; | |||
menuHeader.textContent = 'User'; | |||
menuHeader.addEventListener('click', function() { | |||
menuContainer.classList.toggle('expanded'); | |||
// Store the expanded state | |||
try { | |||
if (menuContainer.classList.contains('expanded')) { | |||
localStorage.setItem('user-menu-state', 'expanded'); | |||
} else { | |||
localStorage.setItem('user-menu-state', 'collapsed'); | |||
} | } | ||
}); | } catch (e) { | ||
// Ignore localStorage errors | |||
} | |||
menuContent.appendChild(menuList); | }); | ||
// Create the content container | |||
var menuContent = document.createElement('div'); | |||
menuContent.className = 'user-menu-content'; | |||
// Create a list to hold the menu items | |||
var menuList = document.createElement('ul'); | |||
menuList.className = 'user-menu-list'; | |||
// Move the user menu items to our new container | |||
userMenuItems.forEach(function(item) { | |||
if (item && item.parentNode) { | |||
menuList.appendChild(item); | |||
} | |||
}); | |||
// Add the list to the content container | |||
menuContent.appendChild(menuList); | |||
// Assemble the menu | |||
menuContainer.appendChild(menuHeader); | |||
menuContainer.appendChild(menuContent); | |||
// Add the menu to the body for fixed positioning | |||
document.body.appendChild(menuContainer); | |||
// Check for saved state | |||
try { | |||
var savedState = localStorage.getItem('user-menu-state'); | |||
if (savedState === 'expanded') { | if (savedState === 'expanded') { | ||
menuContainer.classList.add( | menuContainer.classList.add('expanded'); | ||
} | } | ||
} catch (e) { | |||
// Close menu | // Ignore localStorage errors | ||
} | |||
// Close menu when clicking outside | |||
document.addEventListener('click', function(e) { | |||
if (menuContainer.classList.contains('expanded') && | |||
!menuContainer.contains(e.target)) { | |||
menuContainer.classList.remove('expanded'); | |||
// Update stored state | |||
try { | |||
localStorage.setItem('user-menu-state', 'collapsed'); | |||
} catch (e) { | |||
// Ignore localStorage errors | |||
} | } | ||
} | } | ||
}); | |||
// Add hover functionality for better usability | |||
menuContainer.addEventListener('mouseenter', function() { | |||
menuContainer.classList.add('expanded'); | |||
}); | |||
menuContainer.addEventListener('mouseleave', function() { | |||
menuContainer.classList.remove('expanded'); | |||
// | // Update stored state | ||
try { | |||
localStorage.setItem('user-menu-state', 'collapsed'); | |||
} catch (e) { | |||
// Ignore localStorage errors | |||
// | |||
} | } | ||
}); | |||
} | |||
// Initialize collapsible sections when DOM is ready | |||
// | |||
function initializeAll() { | function initializeAll() { | ||
createHamburgerMenu(); | |||
initCollapsibleSections(); | |||
initSidebarCollapsible(); | |||
initGuardianDecisionHelper(); | |||
initUserMenuCollapsible(); | |||
} | } | ||
// Initialize when DOM is ready | // Initialize all functionality when DOM is ready | ||
if (document.readyState === 'loading') { | if (document.readyState === 'loading') { | ||
document.addEventListener('DOMContentLoaded', initializeAll); | document.addEventListener('DOMContentLoaded', initializeAll); | ||
Revision as of 06:05, 17 September 2025
// MainPage2 interactions. Designed for MediaWiki site usage.
(function(){
'use strict';
// Simple tab controller (inspired by GuardianGuide.html)
window.showTab = function(tabId, el){
var contents = document.querySelectorAll('.tab-content');
for (var i=0;i<contents.length;i++){ contents[i].classList.remove('active'); }
var tabs = document.querySelectorAll('.nav-tab');
for (var j=0;j<tabs.length;j++){ tabs[j].classList.remove('active'); }
var target = document.getElementById(tabId);
if (target) target.classList.add('active');
if (el) el.classList.add('active');
};
// MediaWiki-safe tab click handler using event delegation
function onTabClick(e) {
var tab = e.target.closest('.nav-tab[data-tab]');
if (!tab) return;
e.preventDefault();
var targetId = tab.getAttribute('data-tab');
if (targetId) {
showTab(targetId, tab);
}
}
document.addEventListener('click', onTabClick, false);
// Handle special navigation clicks for tabs (like in Hologramsie)
function onSpecialTabNav(e) {
var trigger = e.target.closest('[data-tab-trigger]');
if (!trigger) return;
e.preventDefault();
var tabData = trigger.getAttribute('data-tab-trigger');
var buttonId = trigger.getAttribute('data-tab-button');
// Support anchors via href="#section" when data-scroll-to is not provided
var href = trigger.getAttribute('href');
var scrollToAttr = trigger.getAttribute('data-scroll-to');
var scrollTargetSelector = null;
if (scrollToAttr && scrollToAttr.trim()) {
scrollTargetSelector = '#' + scrollToAttr.replace(/^#/, '');
} else if (href && href.charAt(0) === '#') {
scrollTargetSelector = href; // already a selector
}
if (tabData && buttonId) {
var targetButton = document.getElementById(buttonId);
if (targetButton) {
showTab(tabData, targetButton);
// Scroll to anchor if specified (from data-scroll-to or href)
if (scrollTargetSelector) {
var element = document.querySelector(scrollTargetSelector);
if (element) {
setTimeout(function() {
element.scrollIntoView({behavior: 'smooth'});
}, 100);
}
}
}
}
}
document.addEventListener('click', onSpecialTabNav, false);
// Back to Top smooth scroll
function onBackToTop(e){
var trigger = e.target.closest('.back-to-top');
if (!trigger) return;
e.preventDefault();
window.scrollTo({ top: 0, behavior: 'smooth' });
}
document.addEventListener('click', onBackToTop, false);
// Card click-throughs using data-link to wiki pages or in-page anchors
function onCardClick(e){
var card = e.target.closest('.card[data-link], .destaque-card[data-link]');
if (!card) return;
var link = card.getAttribute('data-link');
if (!link) return;
// In-page anchor navigation: data-link="#section-id"
if (link.charAt(0) === '#') {
e.preventDefault();
var anchorEl = document.querySelector(link);
if (anchorEl) {
anchorEl.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
return;
}
// Absolute external URL
if (/^https?:\/\//i.test(link)) {
window.location.href = link;
return;
}
// Navigate to the MediaWiki page (prefer mw.util if available)
var targetUrl = (window.mw && mw.util && typeof mw.util.getUrl === 'function')
? mw.util.getUrl(link)
: ('index.php?title=' + encodeURIComponent(link));
window.location.href = targetUrl;
}
document.addEventListener('click', onCardClick, false);
// Helper: determine if a node is inside an excluded container
function isInExcludedContext(node) {
if (!node || !node.parentElement) return false;
var p = node.parentElement.closest('script, style, textarea, input, select, option, .ve-ui-surface, .mw-editform, .CodeMirror, .cm-editor, .ace_editor');
return !!p;
}
// Safely process MediaWiki-like internal link markup [[Title]] -> <a href="...">Title</a>
function processWikiLinks() {
// Skip if the page was already parsed by MediaWiki (no raw brackets found)
if (document.body && document.body.textContent.indexOf('[[') === -1) return;
var walker = document.createTreeWalker(
document.body,
NodeFilter.SHOW_TEXT,
null,
false
);
var textNodes = [];
var node;
while ((node = walker.nextNode())) {
var value = node.nodeValue;
if (!value) continue;
if (value.indexOf('[[') === -1) continue;
if (isInExcludedContext(node)) continue;
textNodes.push(node);
}
var linkRe = /\[\[([^\|\]]+)(?:\|([^\]]+))?\]\]/g;
textNodes.forEach(function(textNode) {
var src = textNode.nodeValue;
if (!linkRe.test(src)) return; // quick check
linkRe.lastIndex = 0; // reset stateful regex
var parent = textNode.parentNode;
var frag = document.createDocumentFragment();
var last = 0; var m;
while ((m = linkRe.exec(src)) !== null) {
// text before match
if (m.index > last) frag.appendChild(document.createTextNode(src.slice(last, m.index)));
var rawTitle = (m[1] || '').trim();
var display = (m[2] != null ? m[2] : rawTitle).trim();
// Skip File: or Image: links - keep original text intact
if (/^(File:|Image:)/i.test(rawTitle)) {
frag.appendChild(document.createTextNode(m[0]));
} else {
// Remove leading colon if present
if (rawTitle.charAt(0) === ':') rawTitle = rawTitle.slice(1);
var href = (window.mw && mw.util && typeof mw.util.getUrl === 'function')
? mw.util.getUrl(rawTitle)
: ('index.php?title=' + encodeURIComponent(rawTitle));
var a = document.createElement('a');
a.className = 'mw-link-internal';
a.setAttribute('href', href);
a.appendChild(document.createTextNode(display)); // avoid HTML injection
frag.appendChild(a);
}
last = linkRe.lastIndex;
}
// trailing text
if (last < src.length) frag.appendChild(document.createTextNode(src.slice(last)));
// Replace the original text node safely
parent.insertBefore(frag, textNode);
parent.removeChild(textNode);
});
}
// Process wiki links when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', processWikiLinks);
} else {
processWikiLinks();
}
// Mobile hamburger menu for sidebar
function createHamburgerMenu() {
// Only create if we're in MediaWiki Vector Legacy and on mobile
if (!document.body.classList.contains('skin-vector-legacy')) return;
var panel = document.getElementById('mw-panel');
if (!panel) return;
// Create hamburger button
var hamburger = document.createElement('button');
hamburger.className = 'mobile-hamburger-menu';
hamburger.setAttribute('aria-label', 'Toggle navigation menu');
hamburger.innerHTML = '<span></span><span></span><span></span>';
// Insert hamburger button at the top of the page
var content = document.getElementById('content') || document.querySelector('.mw-body');
if (content) {
content.parentNode.insertBefore(hamburger, content);
}
// Toggle sidebar function
function toggleSidebar() {
panel.classList.toggle('mobile-open');
hamburger.classList.toggle('active');
document.body.classList.toggle('sidebar-open');
}
// Click handler for hamburger
hamburger.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
toggleSidebar();
});
// Close sidebar when clicking outside
document.addEventListener('click', function(e) {
if (panel.classList.contains('mobile-open') &&
!panel.contains(e.target) &&
!hamburger.contains(e.target)) {
panel.classList.remove('mobile-open');
hamburger.classList.remove('active');
document.body.classList.remove('sidebar-open');
}
});
// Close sidebar on escape key
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape' && panel.classList.contains('mobile-open')) {
panel.classList.remove('mobile-open');
hamburger.classList.remove('active');
document.body.classList.remove('sidebar-open');
}
});
}
// Collapsible sections functionality
function initCollapsibleSections() {
// Handle .collapsible-header elements (new style)
var collapsibleHeaders = document.querySelectorAll('.collapsible-header');
collapsibleHeaders.forEach(function(header) {
header.addEventListener('click', function(e) {
e.preventDefault();
var section = header.parentElement;
var content = section.querySelector('.collapsible-content');
if (content) {
var isExpanded = section.classList.contains('expanded');
if (isExpanded) {
section.classList.remove('expanded');
content.style.maxHeight = '0';
} else {
section.classList.add('expanded');
content.style.maxHeight = content.scrollHeight + 'px';
}
}
});
});
// Handle .collapsible elements (original GuardianGuide style)
var collapsibleButtons = document.querySelectorAll('.collapsible');
collapsibleButtons.forEach(function(button) {
button.addEventListener('click', function(e) {
e.preventDefault();
var content = button.nextElementSibling;
if (content && content.classList.contains('collapsible-content')) {
var isActive = button.classList.contains('active');
if (isActive) {
button.classList.remove('active');
content.style.maxHeight = '0';
} else {
button.classList.add('active');
content.style.maxHeight = content.scrollHeight + 'px';
}
}
});
});
}
// Vector Legacy Sidebar Collapsible functionality
function initSidebarCollapsible() {
// Only initialize if we're in Vector Legacy skin
if (!document.body.classList.contains('skin-vector-legacy')) return;
var panel = document.getElementById('mw-panel');
if (!panel) return;
// Get all portals (both vector-menu-portal and portal classes)
var portals = panel.querySelectorAll('.vector-menu-portal, .portal');
// Auto-collapse all sections except the first one (Navigation) on page load
portals.forEach(function(portal, index) {
if (index > 0) { // Skip first portal (Navigation)
portal.classList.add('collapsed');
var content = portal.querySelector('.vector-menu-content, .body');
if (content) {
content.style.maxHeight = '0';
}
}
});
// Add click handlers to portal headings
var headings = panel.querySelectorAll('.vector-menu-heading, .portal h3');
headings.forEach(function(heading) {
heading.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
var portal = heading.closest('.vector-menu-portal, .portal');
if (!portal) return;
var content = portal.querySelector('.vector-menu-content, .body');
if (!content) return;
var isCollapsed = portal.classList.contains('collapsed');
if (isCollapsed) {
// Expand
portal.classList.remove('collapsed');
content.style.maxHeight = content.scrollHeight + 'px';
// Store the expanded state
try {
localStorage.setItem('sidebar-' + getPortalId(portal), 'expanded');
} catch (e) {
// Ignore localStorage errors
}
} else {
// Collapse
portal.classList.add('collapsed');
content.style.maxHeight = '0';
// Store the collapsed state
try {
localStorage.setItem('sidebar-' + getPortalId(portal), 'collapsed');
} catch (e) {
// Ignore localStorage errors
}
}
});
});
// Restore saved states from localStorage
portals.forEach(function(portal, index) {
if (index === 0) return; // Skip first portal (always expanded)
try {
var savedState = localStorage.getItem('sidebar-' + getPortalId(portal));
var content = portal.querySelector('.vector-menu-content, .body');
if (savedState === 'expanded' && content) {
portal.classList.remove('collapsed');
content.style.maxHeight = content.scrollHeight + 'px';
}
} catch (e) {
// Ignore localStorage errors
}
});
// Helper function to get a unique ID for each portal
function getPortalId(portal) {
var heading = portal.querySelector('.vector-menu-heading, h3');
if (heading) {
return heading.textContent.trim().toLowerCase().replace(/\s+/g, '-');
}
return 'unknown-portal';
}
// Recalculate heights when window is resized
window.addEventListener('resize', function() {
portals.forEach(function(portal) {
if (!portal.classList.contains('collapsed')) {
var content = portal.querySelector('.vector-menu-content, .body');
if (content) {
content.style.maxHeight = content.scrollHeight + 'px';
}
}
});
});
}
// Guardian Type Decision Helper functionality
function initGuardianDecisionHelper() {
// Initialize accordion functionality for guardian types
function initAccordion() {
const accordions = document.getElementsByClassName("guardian-accordion");
for (let i = 0; i < accordions.length; i++) {
accordions[i].addEventListener("click", function() {
this.classList.toggle("active");
const panel = this.nextElementSibling;
if (panel.style.maxHeight) {
panel.style.maxHeight = null;
} else {
panel.style.maxHeight = panel.scrollHeight + "px";
}
});
}
}
// Function to show guardian by difficulty level
function showGuardianByDifficulty(level) {
const accordions = document.getElementsByClassName("guardian-accordion");
let targetAccordion;
// Reset all accordions
for (let i = 0; i < accordions.length; i++) {
accordions[i].classList.remove("active");
accordions[i].nextElementSibling.style.maxHeight = null;
}
// Select the appropriate accordion based on difficulty level
switch(level) {
case "1":
targetAccordion = document.querySelector(".regular-accordion");
break;
case "2":
targetAccordion = document.querySelector(".mighty-accordion");
break;
case "3":
targetAccordion = document.querySelector(".legendary-accordion");
break;
case "4":
targetAccordion = document.querySelector(".superior-accordion");
break;
case "5":
targetAccordion = document.querySelector(".accomplished-accordion");
break;
}
// Activate the target accordion
if (targetAccordion) {
targetAccordion.classList.add("active");
const panel = targetAccordion.nextElementSibling;
panel.style.maxHeight = panel.scrollHeight + "px";
// Scroll to the accordion
setTimeout(function() {
targetAccordion.scrollIntoView({ behavior: 'smooth', block: 'center' });
}, 300);
}
}
// Initialize the decision helper
function initDecisionHelper() {
// Find all option elements
const options = document.querySelectorAll('.decision-options .option');
// Add click event listeners to each option
options.forEach(option => {
option.addEventListener('click', function() {
// Remove selected class from siblings
const siblings = this.parentElement.querySelectorAll('.option');
siblings.forEach(sib => sib.classList.remove('selected'));
// Add selected class to clicked option
this.classList.add('selected');
// Update recommendation
updateRecommendation();
});
});
}
// Function to update recommendation based on selections
function updateRecommendation() {
const selectedOptions = document.querySelectorAll('.decision-options .option.selected');
// If no options selected, show default message
if (selectedOptions.length === 0) {
document.getElementById('recommendedType').textContent = 'Select options above';
return;
}
// Count votes for each guardian type
const votes = {
'regular': 0,
'mighty': 0,
'legendary': 0,
'superior': 0,
'accomplished': 0
};
// Tally votes from selected options
selectedOptions.forEach(option => {
const types = option.getAttribute('data-type').split(',');
types.forEach(type => {
votes[type]++;
});
});
// Find the type with the most votes
let maxVotes = 0;
let recommendedType = '';
let tiedTypes = [];
for (const type in votes) {
if (votes[type] > maxVotes) {
maxVotes = votes[type];
recommendedType = type;
tiedTypes = [type];
} else if (votes[type] === maxVotes && maxVotes > 0) {
tiedTypes.push(type);
}
}
// Handle ties by recommending the more challenging type
if (tiedTypes.length > 1) {
const difficultyOrder = ['regular', 'mighty', 'legendary', 'superior', 'accomplished'];
let highestDifficultyIndex = -1;
tiedTypes.forEach(type => {
const typeIndex = difficultyOrder.indexOf(type);
if (typeIndex > highestDifficultyIndex) {
highestDifficultyIndex = typeIndex;
recommendedType = type;
}
});
}
// Format the recommendation with additional context
let formattedType = recommendedType.charAt(0).toUpperCase() + recommendedType.slice(1) + ' Guardian';
let recommendationText = formattedType;
// Add context based on the recommended type
switch(recommendedType) {
case 'regular':
recommendationText += ' - Perfect for beginners with limited time';
break;
case 'mighty':
recommendationText += ' - Good balance of effort and power';
break;
case 'legendary':
recommendationText += ' - Excellent for endgame content';
break;
case 'superior':
recommendationText += ' - For dedicated players seeking optimization';
break;
case 'accomplished':
recommendationText += ' - For true perfectionists';
break;
}
// Update the recommendation text
document.getElementById('recommendedType').textContent = recommendationText;
// Highlight the recommended guardian type in the accordion
highlightRecommendedType(recommendedType);
}
// Function to highlight the recommended guardian type in the accordion
function highlightRecommendedType(type) {
// Remove highlight from all accordions
const accordions = document.querySelectorAll('.guardian-accordion');
accordions.forEach(accordion => {
accordion.classList.remove('recommended');
});
// Add highlight to the recommended type
const recommendedAccordion = document.querySelector('.' + type + '-accordion');
if (recommendedAccordion) {
recommendedAccordion.classList.add('recommended');
// Scroll to the recommended type if not visible
const isVisible = isElementInViewport(recommendedAccordion);
if (!isVisible) {
recommendedAccordion.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
}
}
// Helper function to check if an element is in the viewport
function isElementInViewport(el) {
const rect = el.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
}
// Initialize components if they exist on the page
if (document.querySelector('.guardian-accordion') || document.querySelector('.decision-options')) {
initAccordion();
initDecisionHelper();
// Make functions available globally
window.updateGuardianRecommendation = updateRecommendation;
window.showGuardianByDifficulty = showGuardianByDifficulty;
}
}
// User menu collapsible functionality
function initUserMenuCollapsible() {
// Only initialize if we're in Vector Legacy skin
if (!document.body.classList.contains('skin-vector-legacy')) return;
// Get the personal tools container
var personalTools = document.getElementById('p-personal');
if (!personalTools) return;
// Get the list of user menu items
var userMenuItems = [
document.getElementById('pt-userpage'),
document.getElementById('pt-mytalk'),
document.getElementById('pt-preferences'),
document.getElementById('pt-watchlist'),
document.getElementById('pt-mycontris'),
document.getElementById('pt-logout')
].filter(Boolean); // Filter out any null items
if (userMenuItems.length === 0) return;
// Create a container for our collapsible menu
var menuContainer = document.createElement('div');
menuContainer.id = 'user-menu-collapsible';
menuContainer.className = 'user-menu-container';
// Create the header/toggle button
var menuHeader = document.createElement('div');
menuHeader.className = 'user-menu-header';
menuHeader.textContent = 'User';
menuHeader.addEventListener('click', function() {
menuContainer.classList.toggle('expanded');
// Store the expanded state
try {
if (menuContainer.classList.contains('expanded')) {
localStorage.setItem('user-menu-state', 'expanded');
} else {
localStorage.setItem('user-menu-state', 'collapsed');
}
} catch (e) {
// Ignore localStorage errors
}
});
// Create the content container
var menuContent = document.createElement('div');
menuContent.className = 'user-menu-content';
// Create a list to hold the menu items
var menuList = document.createElement('ul');
menuList.className = 'user-menu-list';
// Move the user menu items to our new container
userMenuItems.forEach(function(item) {
if (item && item.parentNode) {
menuList.appendChild(item);
}
});
// Add the list to the content container
menuContent.appendChild(menuList);
// Assemble the menu
menuContainer.appendChild(menuHeader);
menuContainer.appendChild(menuContent);
// Add the menu to the body for fixed positioning
document.body.appendChild(menuContainer);
// Check for saved state
try {
var savedState = localStorage.getItem('user-menu-state');
if (savedState === 'expanded') {
menuContainer.classList.add('expanded');
}
} catch (e) {
// Ignore localStorage errors
}
// Close menu when clicking outside
document.addEventListener('click', function(e) {
if (menuContainer.classList.contains('expanded') &&
!menuContainer.contains(e.target)) {
menuContainer.classList.remove('expanded');
// Update stored state
try {
localStorage.setItem('user-menu-state', 'collapsed');
} catch (e) {
// Ignore localStorage errors
}
}
});
// Add hover functionality for better usability
menuContainer.addEventListener('mouseenter', function() {
menuContainer.classList.add('expanded');
});
menuContainer.addEventListener('mouseleave', function() {
menuContainer.classList.remove('expanded');
// Update stored state
try {
localStorage.setItem('user-menu-state', 'collapsed');
} catch (e) {
// Ignore localStorage errors
}
});
}
// Initialize collapsible sections when DOM is ready
function initializeAll() {
createHamburgerMenu();
initCollapsibleSections();
initSidebarCollapsible();
initGuardianDecisionHelper();
initUserMenuCollapsible();
}
// Initialize all functionality when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initializeAll);
} else {
initializeAll();
}
})();