MediaWiki:Common.js: Difference between revisions
Jump to navigation
Jump to search
No edit summary |
No edit summary |
||
| Line 1: | Line 1: | ||
// | // MainPage2 interactions. Designed for MediaWiki site usage. | ||
(function(){ | |||
'use strict'; | |||
(function() { | // 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); | |||
if ( | |||
// 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; | ||
} | |||
// | |||
document. | // 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 { | } 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); | |||
} | } | ||
function | // 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(); | |||
}); | |||
function | // 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) { | 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'); | |||
} | |||
}); | }); | ||
} | |||
// Initialize hamburger menu when DOM is ready | |||
if (document.readyState === 'loading') { | |||
document.addEventListener('DOMContentLoaded', createHamburgerMenu); | |||
} else { | |||
createHamburgerMenu(); | |||
} | |||
})(); | })(); | ||
Revision as of 05:58, 11 August 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');
}
});
}
// Initialize hamburger menu when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', createHamburgerMenu);
} else {
createHamburgerMenu();
}
})();