MediaWiki:Common.js: Difference between revisions
Appearance
Content deleted Content added
No edit summary |
Show five unique recent article pages |
||
| (5 intermediate revisions by the same user not shown) | |||
| Line 1: | Line 1: | ||
/* ======================================================================== |
/* ======================================================================== |
||
ERINDAL WIKI - |
ERINDAL WIKI - VECTOR 2022.JS v1.0 |
||
Skin-specific enhancements. Common.js continues to own article behaviour. |
|||
Target: MediaWiki 1.43.x, legacy Vector |
|||
The topbar itself is now rendered by MediaWiki:Sidebar. JavaScript only |
|||
adds dropdowns, the remembered sidebar toggle and dynamic homepage data. |
|||
It deliberately does not load fonts or construct the navigation bar. |
|||
======================================================================== */ |
======================================================================== */ |
||
| Line 11: | Line 7: | ||
'use strict'; |
'use strict'; |
||
var themeKey = 'erindal-theme'; |
|||
var sidebarKey = 'erindal-sidebar'; |
var sidebarKey = 'erindal-sidebar'; |
||
var themes = [ 'system', 'light', 'dark' ]; |
|||
/* Apply the saved state again as a fallback. LocalSettings.php applies the |
|||
same class in the document head, before first paint. */ |
|||
try { |
|||
if ( window.localStorage.getItem( sidebarKey ) === 'collapsed' ) { |
|||
document.documentElement.classList.add( 'ew-sidebar-collapsed' ); |
|||
} |
|||
} catch ( error ) { |
|||
/* localStorage can be unavailable in strict/private browser modes. */ |
|||
} |
|||
function onReady( callback ) { |
function onReady( callback ) { |
||
| Line 31: | Line 19: | ||
} |
} |
||
function |
function readTheme() { |
||
var value = 'system'; |
|||
if ( !document.getElementById( 'mw-panel' ) || document.getElementById( 'ew-sidebar-toggle' ) ) { |
|||
try { |
|||
value = window.localStorage.getItem( themeKey ) || 'system'; |
|||
} catch ( error ) { |
|||
value = 'system'; |
|||
} |
|||
return themes.indexOf( value ) === -1 ? 'system' : value; |
|||
} |
|||
function applyTheme( value ) { |
|||
document.documentElement.setAttribute( 'data-ew-theme', value ); |
|||
try { |
|||
window.localStorage.setItem( themeKey, value ); |
|||
} catch ( error ) { |
|||
/* The selected mode still applies to the current page. */ |
|||
} |
|||
} |
|||
function installThemeToggle() { |
|||
var topbar = document.getElementById( 'ew-server-topbar' ); |
|||
if ( !topbar || document.getElementById( 'ew-theme-toggle' ) ) { |
|||
return; |
return; |
||
} |
|||
var tools = topbar.querySelector( '.ew-topbar-tools' ); |
|||
if ( !tools ) { |
|||
tools = document.createElement( 'div' ); |
|||
tools.className = 'ew-topbar-tools'; |
|||
topbar.appendChild( tools ); |
|||
} |
} |
||
var button = document.createElement( 'button' ); |
var button = document.createElement( 'button' ); |
||
button.id = 'ew- |
button.id = 'ew-theme-toggle'; |
||
button.type = 'button'; |
button.type = 'button'; |
||
function updateButton() { |
|||
var value = readTheme(); |
|||
var labels = { |
|||
system: '\u25d0 Auto', |
|||
light: '\u2600 Light', |
|||
dark: '\u263e Dark' |
|||
}; |
|||
button.textContent = labels[ value ]; |
|||
button.dataset.ewTheme = value; |
|||
button.setAttribute( 'aria-label', labels[ value ] + ' colour mode. Activate to change mode.' ); |
|||
button.title = labels[ value ] + ' colour mode'; |
|||
} |
|||
button.addEventListener( 'click', function () { |
|||
var current = readTheme(); |
|||
var next = themes[ ( themes.indexOf( current ) + 1 ) % themes.length ]; |
|||
applyTheme( next ); |
|||
updateButton(); |
|||
} ); |
|||
tools.appendChild( button ); |
|||
applyTheme( readTheme() ); |
|||
updateButton(); |
|||
} |
|||
function installSidebarToggleFallback() { |
|||
var button = document.getElementById( 'ew-sidebar-toggle' ); |
|||
if ( !button || button.dataset.ewVectorBound === '1' || button.getAttribute( 'data-ew-bound' ) === '1' ) { |
|||
return; |
|||
} |
|||
button.dataset.ewVectorBound = '1'; |
|||
function updateButton() { |
function updateButton() { |
||
| Line 54: | Line 106: | ||
window.localStorage.setItem( sidebarKey, collapsed ? 'collapsed' : 'open' ); |
window.localStorage.setItem( sidebarKey, collapsed ? 'collapsed' : 'open' ); |
||
} catch ( error ) { |
} catch ( error ) { |
||
/* The button still works for |
/* The button still works for the current page. */ |
||
} |
} |
||
| Line 61: | Line 113: | ||
updateButton(); |
updateButton(); |
||
document.body.appendChild( button ); |
|||
} |
} |
||
function |
function installTopbarDetails() { |
||
var topbar = document.getElementById( ' |
var topbar = document.getElementById( 'ew-server-topbar' ); |
||
if ( !topbar || topbar.dataset. |
if ( !topbar || topbar.dataset.ewDetailsBound === '1' ) { |
||
return; |
return; |
||
} |
} |
||
topbar.dataset. |
topbar.dataset.ewDetailsBound = '1'; |
||
Array.prototype.forEach.call( topbar.querySelectorAll( 'details.ew-global-section' ), function ( section ) { |
|||
var menus = { |
|||
' |
section.addEventListener( 'toggle', function () { |
||
if ( !section.open ) { |
|||
return; |
|||
} |
|||
[ 'Special:Categories', 'Categories' ] |
|||
], |
|||
'World': [ |
|||
[ 'Category:Nations and Realms', 'Nations & Realms' ], |
|||
[ 'Category:Races and Peoples', 'Races & Peoples' ], |
|||
[ 'Category:Locations', 'Locations' ] |
|||
], |
|||
'People': [ |
|||
[ 'Category:Characters', 'Characters' ], |
|||
[ 'Category:Family Lines', 'Family Lines' ] |
|||
], |
|||
'Organizations': [ |
|||
[ 'Category:Organizations', 'All Organizations' ], |
|||
[ 'The Conclave', 'The Conclave' ], |
|||
[ 'Order of the Holy Flame', 'Order of the Holy Flame' ], |
|||
[ 'The Dominion', 'The Dominion' ] |
|||
], |
|||
'Magic & Lore': [ |
|||
[ 'Category:Religion and Deities', 'Religion & Deities' ], |
|||
[ 'Category:Magic and Cosmology', 'Magic & Cosmology' ] |
|||
], |
|||
'History': [ |
|||
[ 'Category:History and Timeline', 'History & Timeline' ], |
|||
[ 'Timeline', 'Timeline' ] |
|||
] |
|||
}; |
|||
Array.prototype.forEach.call( topbar.querySelectorAll( '. |
Array.prototype.forEach.call( topbar.querySelectorAll( 'details.ew-global-section[open]' ), function ( other ) { |
||
if ( other !== section ) { |
|||
other.open = false; |
|||
return; |
|||
} |
|||
var label = link.textContent.trim(); |
|||
var entries = menus[ label ]; |
|||
if ( !entries ) { |
|||
return; |
|||
} |
|||
item.classList.add( 'ew-has-dropdown' ); |
|||
var caret = document.createElement( 'button' ); |
|||
caret.type = 'button'; |
|||
caret.className = 'ew-topbar-caret'; |
|||
caret.textContent = '\u2304'; |
|||
caret.setAttribute( 'aria-label', 'Open ' + label + ' menu' ); |
|||
caret.setAttribute( 'aria-expanded', 'false' ); |
|||
var dropdown = document.createElement( 'div' ); |
|||
dropdown.className = 'ew-topbar-dropdown'; |
|||
entries.forEach( function ( entry ) { |
|||
var menuLink = document.createElement( 'a' ); |
|||
menuLink.href = mw.util.getUrl( entry[ 0 ] ); |
|||
menuLink.textContent = entry[ 1 ]; |
|||
dropdown.appendChild( menuLink ); |
|||
} ); |
|||
caret.addEventListener( 'click', function ( event ) { |
|||
event.preventDefault(); |
|||
event.stopPropagation(); |
|||
var willOpen = !item.classList.contains( 'ew-menu-open' ); |
|||
Array.prototype.forEach.call( topbar.querySelectorAll( '.ew-menu-open' ), function ( openItem ) { |
|||
openItem.classList.remove( 'ew-menu-open' ); |
|||
var openCaret = openItem.querySelector( '.ew-topbar-caret' ); |
|||
if ( openCaret ) { |
|||
openCaret.setAttribute( 'aria-expanded', 'false' ); |
|||
} |
} |
||
} ); |
} ); |
||
item.classList.toggle( 'ew-menu-open', willOpen ); |
|||
caret.setAttribute( 'aria-expanded', willOpen ? 'true' : 'false' ); |
|||
} ); |
} ); |
||
item.appendChild( caret ); |
|||
item.appendChild( dropdown ); |
|||
} ); |
} ); |
||
document.addEventListener( 'click', function () { |
document.addEventListener( 'click', function ( event ) { |
||
if ( topbar.contains( event.target ) ) { |
|||
return; |
|||
} |
|||
var caret = item.querySelector( '.ew-topbar-caret' ); |
|||
if ( caret ) { |
|||
Array.prototype.forEach.call( topbar.querySelectorAll( 'details.ew-global-section[open]' ), function ( section ) { |
|||
caret.setAttribute( 'aria-expanded', 'false' ); |
|||
section.open = false; |
|||
} ); |
} ); |
||
} ); |
} ); |
||
document.addEventListener( 'keydown', function ( event ) { |
document.addEventListener( 'keydown', function ( event ) { |
||
if ( event.key |
if ( event.key !== 'Escape' ) { |
||
Array.prototype.forEach.call( topbar.querySelectorAll( '.ew-menu-open' ), function ( item ) { |
|||
item.classList.remove( 'ew-menu-open' ); |
|||
var caret = item.querySelector( '.ew-topbar-caret' ); |
|||
if ( caret ) { |
|||
caret.setAttribute( 'aria-expanded', 'false' ); |
|||
} |
|||
} ); |
|||
} |
|||
} ); |
|||
} |
|||
function makeHomepageCardsClickable() { |
|||
var destinations = { |
|||
'nations & realms': 'Category:Nations and Realms', |
|||
'races & peoples': 'Category:Races and Peoples', |
|||
'characters': 'Category:Characters', |
|||
'organizations': 'Category:Organizations', |
|||
'religion & deities': 'Category:Religion and Deities', |
|||
'magic & cosmology': 'Category:Magic and Cosmology', |
|||
'locations': 'Category:Locations', |
|||
'history & timeline': 'Category:History and Timeline', |
|||
'family lines': 'Category:Family Lines' |
|||
}; |
|||
Array.prototype.forEach.call( document.querySelectorAll( '.ew-card' ), function ( card ) { |
|||
var link = card.querySelector( 'a[href]' ); |
|||
var labelElement = card.querySelector( '.ew-card-label' ) || link; |
|||
var label = labelElement ? labelElement.textContent.trim().toLowerCase() : ''; |
|||
var destination = link ? link.href : ( destinations[ label ] ? mw.util.getUrl( destinations[ label ] ) : '' ); |
|||
if ( !destination || card.dataset.ewClickable === '1' ) { |
|||
return; |
return; |
||
} |
} |
||
Array.prototype.forEach.call( topbar.querySelectorAll( 'details.ew-global-section[open]' ), function ( section ) { |
|||
card.dataset.ewClickable = '1'; |
|||
section.open = false; |
|||
card.setAttribute( 'role', 'link' ); |
|||
card.setAttribute( 'aria-label', 'Open ' + ( labelElement ? labelElement.textContent.trim() : 'category' ) ); |
|||
card.addEventListener( 'click', function ( event ) { |
|||
if ( !event.target.closest( 'a' ) ) { |
|||
window.location.href = destination; |
|||
} |
|||
} ); |
|||
card.addEventListener( 'keydown', function ( event ) { |
|||
if ( event.key === 'Enter' || event.key === ' ' ) { |
|||
event.preventDefault(); |
|||
window.location.href = destination; |
|||
} |
|||
} ); |
} ); |
||
} ); |
} ); |
||
} |
} |
||
function |
function installRecentChanges() { |
||
var |
var container = document.querySelector( '.ew-recent .ew-recent-list' ); |
||
if ( ! |
if ( !container ) { |
||
return; |
return; |
||
} |
} |
||
function showUnavailable() { |
|||
container.textContent = 'Recent changes are temporarily unavailable.'; |
|||
api.get( { |
|||
} |
|||
list: 'recentchanges', |
|||
mw.loader.using( [ 'mediawiki.api', 'mediawiki.util' ] ).then( function () { |
|||
rcnamespace: 0, |
|||
var api = new mw.Api(); |
|||
return api.get( { |
|||
action: 'query', |
|||
list: 'recentchanges', |
|||
rcnamespace: 0, |
|||
rcprop: 'title|timestamp', |
|||
rclimit: 50, |
|||
rcshow: '!bot' |
|||
} ); |
|||
} ).then( function ( data ) { |
} ).then( function ( data ) { |
||
var changes = data && data.query |
var changes = data && data.query ? data.query.recentchanges : []; |
||
var seenTitles = Object.create( null ); |
|||
changes = ( changes || [] ).filter( function ( change ) { |
|||
if ( /\.(?:css|js)$/i.test( change.title ) || seenTitles[ change.title ] ) { |
|||
return false; |
|||
} |
|||
seenTitles[ change.title ] = true; |
|||
return true; |
|||
} ).slice( 0, 5 ); |
|||
if ( !changes.length ) { |
if ( !changes.length ) { |
||
container.textContent = 'No recent changes yet.'; |
|||
return; |
return; |
||
} |
} |
||
container.textContent = ''; |
|||
changes.forEach( function ( change ) { |
changes.forEach( function ( change ) { |
||
var row = document.createElement( 'div' ); |
var row = document.createElement( 'div' ); |
||
row.className = 'ew-recent-row'; |
|||
var icon = document.createElement( 'span' ); |
var icon = document.createElement( 'span' ); |
||
var details = document.createElement( 'div' ); |
|||
var link = document.createElement( 'a' ); |
|||
var meta = document.createElement( 'small' ); |
|||
var date = new Date( change.timestamp ); |
|||
row.className = 'ew-recent-row'; |
|||
icon.className = 'ew-recent-icon'; |
icon.className = 'ew-recent-icon'; |
||
icon. |
icon.setAttribute( 'aria-hidden', 'true' ); |
||
icon.textContent = '\u25c6'; |
|||
var copy = document.createElement( 'span' ); |
|||
var link = document.createElement( 'a' ); |
|||
link.href = mw.util.getUrl( change.title ); |
link.href = mw.util.getUrl( change.title ); |
||
link.textContent = change.title; |
link.textContent = change.title; |
||
meta.textContent = 'Updated ' + date.toLocaleDateString( undefined, { |
|||
year: 'numeric', |
|||
month: 'short', |
|||
day: 'numeric' |
|||
} ); |
|||
details.appendChild( link ); |
|||
details.appendChild( meta ); |
|||
detail.textContent = date.toLocaleDateString() + ( change.user ? ' - ' + change.user : '' ); |
|||
copy.appendChild( link ); |
|||
copy.appendChild( detail ); |
|||
row.appendChild( icon ); |
row.appendChild( icon ); |
||
row.appendChild( |
row.appendChild( details ); |
||
container.appendChild( row ); |
|||
} ); |
} ); |
||
} |
}, showUnavailable ); |
||
loading.textContent = 'Recent changes could not be loaded.'; |
|||
} ); |
|||
} |
} |
||
onReady( function () { |
|||
installThemeToggle(); |
|||
installSidebarToggleFallback(); |
|||
installTopbarDetails(); |
|||
installRecentChanges(); |
|||
loadRecentChanges(); |
|||
} ); |
|||
} ); |
} ); |
||
}() ); |
}() ); |
||
Latest revision as of 09:59, 14 September 2026
/* ========================================================================
ERINDAL WIKI - VECTOR 2022.JS v1.0
Skin-specific enhancements. Common.js continues to own article behaviour.
======================================================================== */
( function () {
'use strict';
var themeKey = 'erindal-theme';
var sidebarKey = 'erindal-sidebar';
var themes = [ 'system', 'light', 'dark' ];
function onReady( callback ) {
if ( document.readyState === 'loading' ) {
document.addEventListener( 'DOMContentLoaded', callback, { once: true } );
} else {
callback();
}
}
function readTheme() {
var value = 'system';
try {
value = window.localStorage.getItem( themeKey ) || 'system';
} catch ( error ) {
value = 'system';
}
return themes.indexOf( value ) === -1 ? 'system' : value;
}
function applyTheme( value ) {
document.documentElement.setAttribute( 'data-ew-theme', value );
try {
window.localStorage.setItem( themeKey, value );
} catch ( error ) {
/* The selected mode still applies to the current page. */
}
}
function installThemeToggle() {
var topbar = document.getElementById( 'ew-server-topbar' );
if ( !topbar || document.getElementById( 'ew-theme-toggle' ) ) {
return;
}
var tools = topbar.querySelector( '.ew-topbar-tools' );
if ( !tools ) {
tools = document.createElement( 'div' );
tools.className = 'ew-topbar-tools';
topbar.appendChild( tools );
}
var button = document.createElement( 'button' );
button.id = 'ew-theme-toggle';
button.type = 'button';
function updateButton() {
var value = readTheme();
var labels = {
system: '\u25d0 Auto',
light: '\u2600 Light',
dark: '\u263e Dark'
};
button.textContent = labels[ value ];
button.dataset.ewTheme = value;
button.setAttribute( 'aria-label', labels[ value ] + ' colour mode. Activate to change mode.' );
button.title = labels[ value ] + ' colour mode';
}
button.addEventListener( 'click', function () {
var current = readTheme();
var next = themes[ ( themes.indexOf( current ) + 1 ) % themes.length ];
applyTheme( next );
updateButton();
} );
tools.appendChild( button );
applyTheme( readTheme() );
updateButton();
}
function installSidebarToggleFallback() {
var button = document.getElementById( 'ew-sidebar-toggle' );
if ( !button || button.dataset.ewVectorBound === '1' || button.getAttribute( 'data-ew-bound' ) === '1' ) {
return;
}
button.dataset.ewVectorBound = '1';
function updateButton() {
var collapsed = document.documentElement.classList.contains( 'ew-sidebar-collapsed' );
button.textContent = collapsed ? '\u203a' : '\u2039';
button.setAttribute( 'aria-expanded', collapsed ? 'false' : 'true' );
button.setAttribute( 'aria-label', collapsed ? 'Open sidebar' : 'Close sidebar' );
button.title = collapsed ? 'Open sidebar' : 'Close sidebar';
}
button.addEventListener( 'click', function () {
var collapsed = document.documentElement.classList.toggle( 'ew-sidebar-collapsed' );
try {
window.localStorage.setItem( sidebarKey, collapsed ? 'collapsed' : 'open' );
} catch ( error ) {
/* The button still works for the current page. */
}
updateButton();
} );
updateButton();
}
function installTopbarDetails() {
var topbar = document.getElementById( 'ew-server-topbar' );
if ( !topbar || topbar.dataset.ewDetailsBound === '1' ) {
return;
}
topbar.dataset.ewDetailsBound = '1';
Array.prototype.forEach.call( topbar.querySelectorAll( 'details.ew-global-section' ), function ( section ) {
section.addEventListener( 'toggle', function () {
if ( !section.open ) {
return;
}
Array.prototype.forEach.call( topbar.querySelectorAll( 'details.ew-global-section[open]' ), function ( other ) {
if ( other !== section ) {
other.open = false;
}
} );
} );
} );
document.addEventListener( 'click', function ( event ) {
if ( topbar.contains( event.target ) ) {
return;
}
Array.prototype.forEach.call( topbar.querySelectorAll( 'details.ew-global-section[open]' ), function ( section ) {
section.open = false;
} );
} );
document.addEventListener( 'keydown', function ( event ) {
if ( event.key !== 'Escape' ) {
return;
}
Array.prototype.forEach.call( topbar.querySelectorAll( 'details.ew-global-section[open]' ), function ( section ) {
section.open = false;
} );
} );
}
function installRecentChanges() {
var container = document.querySelector( '.ew-recent .ew-recent-list' );
if ( !container ) {
return;
}
function showUnavailable() {
container.textContent = 'Recent changes are temporarily unavailable.';
}
mw.loader.using( [ 'mediawiki.api', 'mediawiki.util' ] ).then( function () {
var api = new mw.Api();
return api.get( {
action: 'query',
list: 'recentchanges',
rcnamespace: 0,
rcprop: 'title|timestamp',
rclimit: 50,
rcshow: '!bot'
} );
} ).then( function ( data ) {
var changes = data && data.query ? data.query.recentchanges : [];
var seenTitles = Object.create( null );
changes = ( changes || [] ).filter( function ( change ) {
if ( /\.(?:css|js)$/i.test( change.title ) || seenTitles[ change.title ] ) {
return false;
}
seenTitles[ change.title ] = true;
return true;
} ).slice( 0, 5 );
if ( !changes.length ) {
container.textContent = 'No recent changes yet.';
return;
}
container.textContent = '';
changes.forEach( function ( change ) {
var row = document.createElement( 'div' );
var icon = document.createElement( 'span' );
var details = document.createElement( 'div' );
var link = document.createElement( 'a' );
var meta = document.createElement( 'small' );
var date = new Date( change.timestamp );
row.className = 'ew-recent-row';
icon.className = 'ew-recent-icon';
icon.setAttribute( 'aria-hidden', 'true' );
icon.textContent = '\u25c6';
link.href = mw.util.getUrl( change.title );
link.textContent = change.title;
meta.textContent = 'Updated ' + date.toLocaleDateString( undefined, {
year: 'numeric',
month: 'short',
day: 'numeric'
} );
details.appendChild( link );
details.appendChild( meta );
row.appendChild( icon );
row.appendChild( details );
container.appendChild( row );
} );
}, showUnavailable );
}
onReady( function () {
installThemeToggle();
installSidebarToggleFallback();
installTopbarDetails();
installRecentChanges();
} );
}() );