MediaWiki:Common.js: Difference between revisions
MediaWiki interface page
More actions
Strip subpage prefix from page titles (show only last segment) |
Add editor-only print button for build pages (funded and partially funded) |
||
| Line 493: | Line 493: | ||
} | } | ||
} | } | ||
}() ); | |||
/* ═══════════════════════════════════════════════════════════════ | |||
Build Page – Editor-only Print Button | |||
Only visible to users in editor, sysop, or bureaucrat groups. | |||
═══════════════════════════════════════════════════════════════ */ | |||
( function () { | |||
"use strict"; | |||
/* Bail out early if not logged in */ | |||
if ( !mw.config.get( "wgUserName" ) ) return; | |||
/* Check user groups – only editors, sysops, bureaucrats see the button */ | |||
var groups = mw.config.get( "wgUserGroups" ) || []; | |||
var allowed = [ "editor", "sysop", "bureaucrat", "interface-admin" ]; | |||
var isAllowed = groups.some( function ( g ) { | |||
return allowed.indexOf( g ) !== -1; | |||
} ); | |||
if ( !isAllowed ) return; | |||
$( function () { | |||
/* On a build page: add a "Print this build" button */ | |||
var buildPage = document.querySelector( ".build-page" ); | |||
if ( buildPage ) { | |||
var btn = document.createElement( "button" ); | |||
btn.className = "build-print-btn"; | |||
btn.textContent = "Print this build"; | |||
btn.addEventListener( "click", function () { | |||
window.print(); | |||
} ); | |||
buildPage.parentNode.insertBefore( btn, buildPage ); | |||
} | |||
/* On list pages: add print icons next to each build link */ | |||
var pageName = mw.config.get( "wgPageName" ); | |||
var isPartialList = pageName === "Crew:Partially_Funded_Builds"; | |||
var isFundedList = pageName === "Bankers_Guild_Builds/Funded_Builds"; | |||
if ( isPartialList || isFundedList ) { | |||
var contentArea = document.getElementById( "mw-content-text" ); | |||
if ( !contentArea ) return; | |||
var links = contentArea.querySelectorAll( "a[href]" ); | |||
links.forEach( function ( link ) { | |||
var href = link.getAttribute( "href" ) || ""; | |||
var isBuildLink = false; | |||
if ( isPartialList && href.indexOf( "Partially_Funded_Builds/" ) !== -1 ) { | |||
isBuildLink = true; | |||
} else if ( isFundedList && href.indexOf( "Funded_Builds/" ) !== -1 ) { | |||
isBuildLink = true; | |||
} | |||
if ( !isBuildLink ) return; | |||
if ( link.closest( "h2, h3, h4" ) ) return; | |||
var icon = document.createElement( "a" ); | |||
icon.className = "build-print-icon"; | |||
icon.textContent = "\uD83D\uDDA8"; | |||
icon.title = "Print this build"; | |||
icon.href = "#"; | |||
icon.addEventListener( "click", function ( e ) { | |||
e.preventDefault(); | |||
var printWin = window.open( link.href, "_blank" ); | |||
if ( printWin ) { | |||
printWin.addEventListener( "load", function () { | |||
setTimeout( function () { printWin.print(); }, 800 ); | |||
} ); | |||
} | |||
} ); | |||
link.parentNode.insertBefore( icon, link.nextSibling ); | |||
} ); | |||
} | |||
} ); | |||
}() ); | }() ); | ||
Latest revision as of 19:48, 16 April 2026
/* Menhirs Fate Wiki - Custom Header
* Menu items are loaded from MediaWiki:Sidebar
* Edit that page to change nav links, dropdowns, and ordering.
* The header reads directly from the sidebar DOM — no API call or caching needed.
*/
/* ── Favicon ──────────────────────────────────────────────────────────────── */
( function () {
var link = document.createElement( 'link' );
link.rel = 'icon';
link.type = 'image/x-icon';
link.href = '/images/favicon.png';
document.head.appendChild( link );
}() );
( function () {
"use strict";
/* ── Google Fonts ─────────────────────────────────────────────────── */
var link = document.createElement( "link" );
link.rel = "stylesheet";
link.href = "https://fonts.googleapis.com/css2?family=Crimson+Text:ital,wght@0,400;0,600;0,700;1,400&display=swap";
document.head.appendChild( link );
/* ── Build header shell ───────────────────────────────────────────── */
var header = document.createElement( "div" );
header.id = "mf-header";
header.innerHTML =
'<div id="mf-header-top">' +
'<a href="/wiki/Main_Page" id="mf-logo-link">' +
'<img src="https://www.menhirsfate.com/wp-content/uploads/2024/08/white-logo-scaled-120x65.png" alt="Menhirs Fate" id="mf-logo" />' +
'</a>' +
'</div>' +
'<nav id="mf-ribbon">' +
'<div id="mf-ribbon-inner">' +
'<span id="mf-nav-loading" style="color:#844725;font-style:italic;padding:0 17px;line-height:50px;">Loading…</span>' +
'</div>' +
'</nav>';
document.body.insertBefore( header, document.body.firstChild );
/* ── Hamburger button ─────────────────────────────────────────────── */
var burger = document.createElement( "button" );
burger.id = "mf-burger";
burger.setAttribute( "aria-label", "Toggle menu" );
burger.innerHTML = "<span></span><span></span><span></span>";
document.getElementById( "mf-header-top" ).appendChild( burger );
var ribbon = document.getElementById( "mf-ribbon-inner" );
burger.addEventListener( "click", function () {
ribbon.classList.toggle( "mf-open" );
burger.classList.toggle( "mf-open" );
} );
/* ── Parse navigation from sidebar DOM ─────────────────────────────── */
function parseSidebarFromDOM() {
var items = [];
var sidebar = document.getElementById( 'citizen-main-menu' );
if ( !sidebar ) return items;
var sections = sidebar.querySelectorAll( 'nav.citizen-menu.mw-portlet' );
Array.prototype.forEach.call( sections, function ( section ) {
if ( section.id === 'p-CrewWiki' ) return;
var heading = section.querySelector( '.citizen-menu__heading' );
var linkEls = section.querySelectorAll( '.citizen-menu__content-list a' );
if ( !heading || !linkEls.length ) return;
var sectionName = heading.textContent.trim();
if ( sectionName === 'Navigation' || sectionName === 'navigation' ) {
Array.prototype.forEach.call( linkEls, function ( a ) {
var label = ( a.querySelector( 'span' ) || a ).textContent.trim();
var url = a.getAttribute( 'href' );
if ( label === 'Main page' || label === 'Home' ) {
items.push( { label: 'Home', url: '/wiki/Main_Page' } );
} else if ( url && url.indexOf( 'menhirsfate.com' ) !== -1 &&
url.indexOf( 'wiki.' ) === -1 ) {
items.push( { label: '\u2190 MenhirsFate.com', url: url, flags: [ 'back' ] } );
}
} );
} else {
var dropdown = { label: sectionName, children: [] };
Array.prototype.forEach.call( linkEls, function ( a ) {
var label = ( a.querySelector( 'span' ) || a ).textContent.trim();
var url = a.getAttribute( 'href' );
dropdown.children.push( { label: label, url: url } );
} );
if ( dropdown.children.length ) {
items.push( dropdown );
}
}
} );
items.sort( function ( a, b ) {
if ( a.flags && a.flags.indexOf( 'back' ) !== -1 ) return -1;
if ( b.flags && b.flags.indexOf( 'back' ) !== -1 ) return 1;
return 0;
} );
return items;
}
/* ── Build nav HTML from parsed items ─────────────────────────────── */
function buildNav( items ) {
var html = "";
var dropdownCount = 0;
items.forEach( function ( item ) {
if ( item.children ) {
/* dropdown */
html += '<div class="mf-has-dropdown">' +
'<span class="mf-nav-label">' + item.label + ' <span class="mf-caret">▾</span></span>' +
'<div class="mf-dropdown">';
item.children.forEach( function ( child ) {
html += '<a href="' + child.url + '">' + child.label + '</a>';
} );
html += '</div></div>';
dropdownCount++;
} else {
/* simple link */
var cls = "mf-nav-item";
if ( item.flags && item.flags.indexOf( "back" ) !== -1 ) {
cls += " mf-nav-back";
}
html += '<a href="' + item.url + '" class="' + cls + '">' + item.label + '</a>';
}
} );
/* Search — inline input for desktop, icon-only for narrow screens */
html += '<div id="mf-search-wrap">' +
'<form action="/wiki/Special:Search" id="mf-search-form">' +
'<input type="text" name="search" id="mf-search-input" placeholder="Search…" autocomplete="off" />' +
'<button type="submit" id="mf-search-btn" aria-label="Search">' +
'<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>' +
'</button>' +
'</form>' +
'<button type="button" id="mf-search-icon" aria-label="Search">' +
'<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>' +
'</button>' +
'<div id="mf-search-suggestions"></div>' +
'</div>';
/* Update grid columns: back-link(auto) + N nav items(1fr each) + search(auto) */
var navItemCount = 0;
items.forEach( function(i) { if ( !i.flags || i.flags.indexOf("back") === -1 ) navItemCount++; } );
return { html: html, navItemCount: navItemCount };
}
/* ── Render nav and add Crew Wiki links ─────────────────────── */
function renderNav( items ) {
var result = buildNav( items );
ribbon.innerHTML = result.html;
var cols = "auto repeat(" + result.navItemCount + ", 1fr) auto";
ribbon.style.gridTemplateColumns = cols;
initDropdowns();
initSearch();
/* ── Dynamic "Crew Wiki" links (Volunteer/Editor/Admin only) ── */
var groups = mw.config.get( 'wgUserGroups' ) || [];
var isCrewMember = groups.indexOf( 'sysop' ) !== -1 ||
groups.indexOf( 'editor' ) !== -1 ||
groups.indexOf( 'volunteer' ) !== -1;
if ( mw.config.get( 'wgUserName' ) && isCrewMember ) {
if ( !document.getElementById( 'mf-crew-link' ) ) {
var crewLink = document.createElement( 'a' );
crewLink.id = 'mf-crew-link';
crewLink.className = 'mf-nav-item';
crewLink.href = mw.util.getUrl( 'Crew:Main_Page' );
crewLink.textContent = 'Crew Wiki';
var searchWrap = document.getElementById( 'mf-search-wrap' );
if ( searchWrap ) {
ribbon.insertBefore( crewLink, searchWrap );
} else {
ribbon.appendChild( crewLink );
}
var allNavItems = ribbon.querySelectorAll( '.mf-nav-item:not(.mf-nav-back), .mf-has-dropdown' );
var newCols = 'auto repeat(' + allNavItems.length + ', 1fr) auto';
ribbon.style.gridTemplateColumns = newCols;
}
var mainMenu = document.getElementById( 'citizen-main-menu' );
if ( mainMenu && !document.getElementById( 'p-CrewWiki' ) ) {
var crewNav = document.createElement( 'nav' );
crewNav.id = 'p-CrewWiki';
crewNav.className = 'citizen-menu mw-portlet mw-portlet-CrewWiki';
crewNav.innerHTML = '<div class="citizen-menu__heading">Crew Wiki</div>' +
'<div class="citizen-menu__content">' +
'<ul class="citizen-menu__content-list">' +
'<li class="mw-list-item"><a href="' + mw.util.getUrl( 'Crew:Main_Page' ) + '"><span>Crew Home</span></a></li>' +
'<li class="mw-list-item"><a href="' + mw.util.getUrl( 'Crew:Editor_Guide' ) + '"><span>Editor Guide</span></a></li>' +
'</ul></div>';
var resources = document.getElementById( 'p-Resources' );
if ( resources && resources.nextSibling ) {
mainMenu.insertBefore( crewNav, resources.nextSibling );
} else {
mainMenu.appendChild( crewNav );
}
}
}
}
/* ── Load nav directly from sidebar DOM (no API / no cache) ──────── */
var items = parseSidebarFromDOM();
if ( items.length ) {
renderNav( items );
} else {
ribbon.innerHTML = '<a href="/wiki/Main_Page" class="mf-nav-item">Home</a>';
}
/* ── Dropdown toggle logic ────────────────────────────────────────── */
function initDropdowns() {
var labels = document.querySelectorAll( ".mf-has-dropdown > .mf-nav-label" );
Array.prototype.forEach.call( labels, function ( label ) {
label.addEventListener( "click", function ( e ) {
e.stopPropagation();
var parent = this.parentNode;
var open = document.querySelectorAll( ".mf-has-dropdown.mf-dropdown-open" );
Array.prototype.forEach.call( open, function ( s ) {
if ( s !== parent ) s.classList.remove( "mf-dropdown-open" );
} );
parent.classList.toggle( "mf-dropdown-open" );
} );
} );
document.addEventListener( "click", function ( e ) {
if ( !e.target.closest( ".mf-has-dropdown" ) ) {
var open = document.querySelectorAll( ".mf-has-dropdown.mf-dropdown-open" );
Array.prototype.forEach.call( open, function ( el ) {
el.classList.remove( "mf-dropdown-open" );
} );
}
} );
/* Close mobile menu on link click */
ribbon.addEventListener( "click", function ( e ) {
if ( e.target.tagName === "A" ) {
ribbon.classList.remove( "mf-open" );
burger.classList.remove( "mf-open" );
}
} );
}
/* ── Search with suggestions ──────────────────────────────────────── */
function initSearch() {
var input = document.getElementById( "mf-search-input" );
var sugBox = document.getElementById( "mf-search-suggestions" );
var iconBtn = document.getElementById( "mf-search-icon" );
var debounce = null;
if ( !input || !sugBox ) return;
/* Icon button (shown on narrow screens) expands inline search */
var searchSvg = '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>';
var closeSvg = '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>';
if ( iconBtn ) {
iconBtn.addEventListener( "click", function () {
var wrap = document.getElementById( "mf-search-wrap" );
wrap.classList.toggle( "mf-search-expanded" );
if ( wrap.classList.contains( "mf-search-expanded" ) ) {
iconBtn.innerHTML = closeSvg;
input.focus();
} else {
iconBtn.innerHTML = searchSvg;
input.value = "";
sugBox.style.display = "none";
sugBox.innerHTML = "";
}
} );
}
/* Autocomplete suggestions */
input.addEventListener( "input", function () {
var q = input.value.trim();
clearTimeout( debounce );
if ( q.length < 2 ) {
sugBox.style.display = "none";
sugBox.innerHTML = "";
return;
}
debounce = setTimeout( function () {
mw.loader.using( "mediawiki.api" ).then( function () {
new mw.Api().get( {
action: "opensearch",
search: q,
limit: 6,
namespace: 0
} ).then( function ( data ) {
var titles = data[1] || [];
if ( !titles.length ) {
sugBox.style.display = "none";
sugBox.innerHTML = "";
return;
}
sugBox.innerHTML = "";
titles.forEach( function ( t ) {
var a = document.createElement( "a" );
a.href = mw.util.getUrl( t );
a.textContent = t;
a.className = "mf-suggestion";
sugBox.appendChild( a );
} );
sugBox.style.display = "block";
} );
} );
}, 200 );
} );
input.addEventListener( "focus", function () {
if ( sugBox.children.length ) sugBox.style.display = "block";
} );
document.addEventListener( "click", function ( e ) {
if ( !e.target.closest( "#mf-search-wrap" ) ) {
sugBox.style.display = "none";
}
} );
}
/* ── Fix sitename typo wherever it appears ────────────────────────── */
document.querySelectorAll( ".citizen-footer__sitetitle, .citizen-footer a, .mw-logo-wordmark" ).forEach( function ( el ) {
if ( el.textContent.indexOf( "Mehirs" ) !== -1 ) {
el.textContent = el.textContent.replace( /Mehirs/g, "Menhirs" );
}
} );
/* ── Citizen header z-index ───────────────────────────────────────── */
/* Handled in Common.css */
}() );
/* === ApprovedRevs: Approve Button Fix for Citizen Skin === */
/* Adds an "Approve" button to the page actions when viewing */
/* an unapproved revision that the current user can approve. */
/* Checks namespace support to avoid badtarget errors. */
(function () {
'use strict';
// Only run on view action
if (mw.config.get('wgAction') !== 'view') return;
// Check if ApprovedRevs is active on this page via body classes
var body = document.body;
var isNotApproved = body.classList.contains('approvedRevs-notapproved');
var hasNoApprovedRev = body.classList.contains('approvedRevs-noapprovedrev');
// If the page is already showing the approved revision, skip
if (!isNotApproved && !hasNoApprovedRev) return;
// Check if this namespace supports ApprovedRevs
// Only namespaces 0 (Main), 4 (Project), and 3000 (Crew) are enabled
var approvedNamespaces = [0, 4, 3000];
var currentNS = mw.config.get('wgNamespaceNumber');
if (approvedNamespaces.indexOf(currentNS) === -1) return;
// We need user to have approverevisions right
mw.loader.using('mediawiki.api').then(function () {
var api = new mw.Api();
// Check user rights
return api.get({
action: 'query',
meta: 'userinfo',
uiprop: 'rights',
format: 'json'
}).then(function (data) {
var rights = data.query.userinfo.rights || [];
if (rights.indexOf('approverevisions') === -1) return;
// Get the revision ID to approve
var revId = mw.config.get('wgRevisionId');
var curRevId = mw.config.get('wgCurRevisionId');
var title = mw.config.get('wgPageName');
// Determine which revision to approve
var approveRevId = revId || curRevId;
// Add approve button to the "More" actions menu (p-cactions)
var cactions = document.getElementById('p-cactions');
if (cactions) {
var list = cactions.querySelector('ul, .citizen-menu__content');
if (list) {
var li = document.createElement('li');
li.id = 'ca-approve';
li.className = 'mw-list-item';
var link = document.createElement('a');
link.textContent = 'Approve';
link.title = 'Approve revision ' + approveRevId;
link.style.cursor = 'pointer';
link.style.color = '#4aba5a';
link.style.fontWeight = 'bold';
link.addEventListener('click', function (e) {
e.preventDefault();
if (!confirm('Approve revision ' + approveRevId + ' of "' + title.replace(/_/g, ' ') + '"?')) return;
link.textContent = 'Approving...';
link.style.pointerEvents = 'none';
api.postWithToken('csrf', {
action: 'approve',
revid: approveRevId
}).then(function () {
link.textContent = 'Approved!';
link.style.color = '#2ecc71';
setTimeout(function () {
window.location.href = mw.util.getUrl(title);
}, 800);
}, function (code, result) {
link.textContent = 'Error: ' + code;
link.style.color = '#e74c3c';
link.style.pointerEvents = 'auto';
console.error('ApprovedRevs approve failed:', code, result);
});
});
li.appendChild(link);
list.appendChild(li);
}
}
// Also add a banner below the page title for visibility
var contentSub = document.getElementById('contentSub');
if (contentSub) {
var banner = document.createElement('div');
banner.style.cssText = 'background:#2d2d1f; border:1px solid #4aba5a; border-radius:4px; padding:8px 14px; margin:8px 0; display:inline-flex; align-items:center; gap:10px; font-size:0.9em;';
var msg = document.createElement('span');
if (isNotApproved) {
msg.textContent = 'This revision is not yet approved.';
} else {
msg.textContent = 'This page has no approved revision.';
}
msg.style.color = '#e0d0a0';
var btn = document.createElement('button');
btn.textContent = 'Approve Rev ' + approveRevId;
btn.style.cssText = 'background:#4aba5a; color:#fff; border:none; border-radius:3px; padding:4px 12px; cursor:pointer; font-weight:bold; font-size:0.9em;';
btn.addEventListener('mouseenter', function () { btn.style.background = '#3da34d'; });
btn.addEventListener('mouseleave', function () { btn.style.background = '#4aba5a'; });
btn.addEventListener('click', function () {
if (!confirm('Approve revision ' + approveRevId + ' of "' + title.replace(/_/g, ' ') + '"?')) return;
btn.textContent = 'Approving...';
btn.disabled = true;
api.postWithToken('csrf', {
action: 'approve',
revid: approveRevId
}).then(function () {
btn.textContent = 'Approved!';
btn.style.background = '#2ecc71';
banner.style.borderColor = '#2ecc71';
setTimeout(function () {
window.location.href = mw.util.getUrl(title);
}, 800);
}, function (code, result) {
btn.textContent = 'Error: ' + code;
btn.style.background = '#e74c3c';
btn.disabled = false;
console.error('ApprovedRevs approve failed:', code, result);
});
});
banner.appendChild(msg);
banner.appendChild(btn);
contentSub.appendChild(banner);
}
});
});
})();
/* ── Strip subpage prefix from page title ────────────────────────────────── */
/* Shows only the last segment (e.g. "Recent History" instead of
"The Commonwealth/The Hammerstadt Charter/Recent History").
The breadcrumb line below the title still provides parent navigation. */
( function () {
var titleEl = document.querySelector( '.mw-page-title-main' );
if ( titleEl ) {
var parts = titleEl.textContent.split( '/' );
if ( parts.length > 1 ) {
titleEl.textContent = parts[ parts.length - 1 ];
}
}
}() );
/* ═══════════════════════════════════════════════════════════════
Build Page – Editor-only Print Button
Only visible to users in editor, sysop, or bureaucrat groups.
═══════════════════════════════════════════════════════════════ */
( function () {
"use strict";
/* Bail out early if not logged in */
if ( !mw.config.get( "wgUserName" ) ) return;
/* Check user groups – only editors, sysops, bureaucrats see the button */
var groups = mw.config.get( "wgUserGroups" ) || [];
var allowed = [ "editor", "sysop", "bureaucrat", "interface-admin" ];
var isAllowed = groups.some( function ( g ) {
return allowed.indexOf( g ) !== -1;
} );
if ( !isAllowed ) return;
$( function () {
/* On a build page: add a "Print this build" button */
var buildPage = document.querySelector( ".build-page" );
if ( buildPage ) {
var btn = document.createElement( "button" );
btn.className = "build-print-btn";
btn.textContent = "Print this build";
btn.addEventListener( "click", function () {
window.print();
} );
buildPage.parentNode.insertBefore( btn, buildPage );
}
/* On list pages: add print icons next to each build link */
var pageName = mw.config.get( "wgPageName" );
var isPartialList = pageName === "Crew:Partially_Funded_Builds";
var isFundedList = pageName === "Bankers_Guild_Builds/Funded_Builds";
if ( isPartialList || isFundedList ) {
var contentArea = document.getElementById( "mw-content-text" );
if ( !contentArea ) return;
var links = contentArea.querySelectorAll( "a[href]" );
links.forEach( function ( link ) {
var href = link.getAttribute( "href" ) || "";
var isBuildLink = false;
if ( isPartialList && href.indexOf( "Partially_Funded_Builds/" ) !== -1 ) {
isBuildLink = true;
} else if ( isFundedList && href.indexOf( "Funded_Builds/" ) !== -1 ) {
isBuildLink = true;
}
if ( !isBuildLink ) return;
if ( link.closest( "h2, h3, h4" ) ) return;
var icon = document.createElement( "a" );
icon.className = "build-print-icon";
icon.textContent = "\uD83D\uDDA8";
icon.title = "Print this build";
icon.href = "#";
icon.addEventListener( "click", function ( e ) {
e.preventDefault();
var printWin = window.open( link.href, "_blank" );
if ( printWin ) {
printWin.addEventListener( "load", function () {
setTimeout( function () { printWin.print(); }, 800 );
} );
}
} );
link.parentNode.insertBefore( icon, link.nextSibling );
} );
}
} );
}() );