MediaWiki:Common.js
ShipLineNav: fixed-width tier column so ship names align |
ShipLineNav: tier in left indent so ship names align with Navigation items |
||
| Line 319: | Line 319: | ||
tier.className = 'wows-line-tier'; | tier.className = 'wows-line-tier'; | ||
tier.textContent = roman( s.tier ); | tier.textContent = roman( s.tier ); | ||
tier.style.cssText = ' | tier.style.cssText = 'position:absolute;left:3px;top:50%;transform:translateY(-50%);width:22px;text-align:right;color:#AD7A07;font-weight:bold;'; | ||
pt.appendChild( tier ); | pt.appendChild( tier ); | ||
pt.appendChild( document.createTextNode( s.name ) ); | pt.appendChild( document.createTextNode( s.name ) ); | ||
Revision as of 17:04, 21 July 2026
/* Any JavaScript here will be loaded for all users on every page load. */
/* ============================================================
Light / Dark Mode Toggle
Adds a fixed pill (top-right) that switches between the
current dark theme and the live-wiki light theme.
Styled to match the Wargaming sign-in page mode-switcher.
Preference is remembered via localStorage.
============================================================ */
( function () {
'use strict';
var STORAGE_KEY = 'wows-theme-mode';
var STYLE_ID = 'wows-light-mode-styles';
var LIGHT_CSS = [
'html, body, .b-main {',
' background-color: transparent !important;',
' background-image: url("https://wiki.wargaming.net/skins/globalwiki/images/bg.jpg?2026-01-22T09:18:20Z") !important;',
' background-repeat: repeat !important;',
' color: rgb(0, 0, 0) !important;',
'}',
'#firstHeading { color: rgb(56, 56, 56) !important; }',
'.b-game-info > b, .b-game-info .b-heading2 { color: rgb(56, 56, 56) !important; }',
'.mw-parser-output, .mw-body-content, #mw-content-text { color: rgb(0, 0, 0) !important; }',
'#mw-panel, #mw-panel a, .b-sidebar, .b-sidebar a, .b-left-menu_item, .b-portlet-link { color: rgb(67, 99, 115) !important; }',
'#mw-head, #mw-head a, .b-header a { color: rgb(56, 56, 56) !important; }',
'.b-tabs-wrp { border-color: initial !important; }',
'table, .wikitable, .b-infobox { background-color: transparent !important; color: rgb(0,0,0) !important; }',
'th { background-color: rgba(200,220,235,0.9) !important; color: rgb(0,0,0) !important; }',
'td { color: rgb(0,0,0) !important; }',
'.mw-body, #content { background-color: transparent !important; }',
'.b-sidebar_title, #footer, .b-footer { color: rgb(56, 56, 56) !important; }',
'.b-heading1, .b-heading2, .b-heading3, h1, h2, h3, h4, h5, h6, .mw-parser-output h1, .mw-parser-output h2, .mw-parser-output h3, .mw-parser-output h4, .mw-parser-output h5, .mw-parser-output h6 { color: rgb(56, 56, 56) !important; }',
'.b-search-input, #searchInput { background-color: rgb(245,245,245) !important; color: rgb(0,0,0) !important; }',
'.b-game-info { background-color: transparent !important; color: rgb(0,0,0) !important; border-color: transparent !important; }',
'.b-description.gw-frame-1, .mw-parser-output .gw-frame-1 { background-color: rgba(255,255,255,0.6) !important; border-color: rgba(212,175,55,0.4) !important; box-shadow: none !important; color: rgb(0,0,0) !important; }',
'.b-description.gw-frame-1 a, .mw-parser-output .gw-frame-1 a { color: rgb(2,93,177) !important; }'
].join( '\n' );
function applyLight() {
if ( document.getElementById( STYLE_ID ) ) { return; }
var s = document.createElement( 'style' );
s.id = STYLE_ID;
s.textContent = LIGHT_CSS;
document.head.appendChild( s );
}
function applyDark() {
var s = document.getElementById( STYLE_ID );
if ( s ) { s.remove(); }
}
var DAY_SVG = 'https://wargaming.net/id/static/2026.4.0/wgnet/img/mode-switcher/day.svg';
var NIGHT_SVG = 'https://wargaming.net/id/static/2026.4.0/wgnet/img/mode-switcher/night.svg';
function buildButton() {
var isDark = ( localStorage.getItem( STORAGE_KEY ) !== 'light' );
if ( !isDark ) { applyLight(); }
var wrap = document.createElement( 'div' );
wrap.id = 'wows-theme-toggle';
wrap.style.cssText = [
'position:fixed', 'top:14px', 'right:16px', 'z-index:9999',
'background:rgb(30,30,30)', 'border:1px solid rgb(52,52,52)',
'border-radius:8px', 'padding:3px', 'display:flex', 'gap:4px',
'align-items:center'
].join( ';' );
var BTN_BASE = [
'width:30px', 'height:24px', 'border:none', 'cursor:pointer', 'padding:0',
'border-radius:5px', 'background-color:transparent',
'background-repeat:no-repeat', 'background-position:center',
'background-size:20px', 'transition:opacity 0.2s'
].join( ';' );
var btnLight = document.createElement( 'button' );
btnLight.title = 'Light mode';
btnLight.style.cssText = BTN_BASE + ';background-image:url("' + DAY_SVG + '")';
btnLight.style.opacity = isDark ? '0.5' : '1';
var btnDark = document.createElement( 'button' );
btnDark.title = 'Dark mode';
btnDark.style.cssText = BTN_BASE + ';background-image:url("' + NIGHT_SVG + '")';
btnDark.style.opacity = isDark ? '1' : '0.5';
function updateOpacity() {
btnLight.style.opacity = isDark ? '0.5' : '1';
btnDark.style.opacity = isDark ? '1' : '0.5';
}
btnLight.addEventListener( 'click', function () {
if ( !isDark ) { return; }
isDark = false; applyLight();
localStorage.setItem( STORAGE_KEY, 'light' ); updateOpacity();
} );
btnDark.addEventListener( 'click', function () {
if ( isDark ) { return; }
isDark = true; applyDark();
localStorage.setItem( STORAGE_KEY, 'dark' ); updateOpacity();
} );
wrap.appendChild( btnLight );
wrap.appendChild( btnDark );
document.body.appendChild( wrap );
}
if ( document.readyState === 'loading' ) {
document.addEventListener( 'DOMContentLoaded', buildButton );
} else {
buildButton();
}
}() );
/* Theme class hook — exposes light/dark to CSS for readable theming */
(function () {
function applyThemeClass() {
var light = localStorage.getItem('wows-theme-mode') === 'light';
var h = document.documentElement;
h.classList.toggle('wows-theme-light', light);
h.classList.toggle('wows-theme-dark', !light);
}
applyThemeClass();
window.addEventListener('storage', function (e) {
if (e.key === 'wows-theme-mode') applyThemeClass();
});
document.addEventListener('click', function (e) {
var b = e.target.closest && e.target.closest('button');
if (b && /Light mode|Dark mode/i.test(b.title || b.textContent)) {
setTimeout(applyThemeClass, 0);
}
}, true);
})();
/* ============================================================
Ship pages: contextual Nation + Class links in the sidebar nav
On Ship: namespace pages (ns 10000), read the page's categories
to find its nation ("Ships of X") and class, then add links to
the matching overview pages into the navigation menu.
============================================================ */
( function () {
'use strict';
if ( !window.mw || mw.config.get( 'wgNamespaceNumber' ) !== 10000 ) { return; }
var cats = mw.config.get( 'wgCategories' ) || [];
var CLASSES = [ 'Aircraft Carriers', 'Battleships', 'Cruisers', 'Destroyers', 'Submarines' ];
var nation = null, klass = null;
cats.forEach( function ( c ) {
if ( /^Ships of /.test( c ) ) { nation = c; }
if ( CLASSES.indexOf( c ) !== -1 ) { klass = c; }
} );
if ( !nation && !klass ) { return; }
var artPath = mw.config.get( 'wgArticlePath' );
function url( t ) { return artPath.replace( '$1', t.replace( / /g, '_' ) ); }
function addItem( menu, id, label, target ) {
if ( document.getElementById( id ) ) { return; }
var li = document.createElement( 'li' );
li.id = id;
li.className = 'b-left-menu_item mw-list-item';
var a = document.createElement( 'a' );
a.className = 'b-left-menu_link b-darklink';
a.href = url( target );
var point = document.createElement( 'span' );
point.className = 'b-left-menu_point';
point.title = label;
point.textContent = label;
a.appendChild( point );
li.appendChild( a );
menu.appendChild( li );
}
function inject() {
var menu = document.querySelector( '#p-navigation .b-left-menu' ) ||
document.querySelector( '#mw-panel .b-left-menu' );
if ( !menu ) { return; }
if ( nation ) { addItem( menu, 'n-ship-nation', nation, 'Ship:' + nation ); }
if ( klass ) { addItem( menu, 'n-ship-class', klass, 'Ship:' + klass ); }
}
if ( document.readyState === 'loading' ) {
document.addEventListener( 'DOMContentLoaded', inject );
} else {
inject();
}
}() );
/* ============================================================
Article pages: move the table of contents into the sidebar
On any page with a TOC (#toc), relocate it into #mw-panel so it
scrolls with the reader (the sidebar is sticky). Pages without a
TOC (e.g. ship stat pages using __NOTOC__) are unaffected.
============================================================ */
( function () {
'use strict';
var toc = document.getElementById( 'toc' );
var panel = document.getElementById( 'mw-panel' );
if ( !toc || !panel ) { return; }
var sec = document.createElement( 'div' );
sec.id = 'p-toc';
sec.className = 'b-sidebar_item';
sec.appendChild( toc );
panel.appendChild( sec );
}() );
/* ===================================================================
Sidebar "Request Edit" link - open in new tab
=================================================================== */
( function () {
'use strict';
function fixLink() {
var link = document.querySelector( '#n-Request-Edit a' );
if ( !link ) { return; }
link.setAttribute( 'target', '_blank' );
var relParts = ( link.getAttribute( 'rel' ) || '' ).split( /\s+/ ).filter( Boolean );
if ( relParts.indexOf( 'noopener' ) === -1 ) { relParts.push( 'noopener' ); }
link.setAttribute( 'rel', relParts.join( ' ' ) );
}
if ( document.readyState === 'loading' ) {
document.addEventListener( 'DOMContentLoaded', fixLink );
} else {
fixLink();
}
// Re-apply shortly after load in case other scripts modify the link's attributes afterward.
setTimeout( fixLink, 500 );
} )();
/**
* ================================================================
* Tech-Tree Line Navigation (added via Cowork, 2026-07-21)
* ================================================================
* Adds a "Tech Tree Line" panel to the sidebar on Ship: pages,
* reusing the skin's b-left-menu markup so it matches Navigation.
* Data: MediaWiki:ShipLines.json (WG API encyclopedia/ships next_ships).
* Ship: namespace only (ns 10000); premium/non-tree ships skipped.
* Regenerate the JSON each patch and bump CONFIG.cacheVersion.
* ================================================================
*/
( function () {
'use strict';
var CONFIG = {
shipNamespace: 10000,
dataPage: 'MediaWiki:ShipLines.json',
cacheKey: 'wowsShipLines',
cacheVersionKey: 'wowsShipLinesVer',
cacheVersion: '15.6.0'
};
if ( mw.config.get( 'wgNamespaceNumber' ) !== CONFIG.shipNamespace ) { return; }
var shipName = mw.config.get( 'wgTitle' );
if ( !shipName ) { return; }
mw.loader.using( [ 'mediawiki.util' ] ).then( function () {
loadData().then( function ( data ) {
$( function () { setTimeout( function () { try { render( data ); } catch ( e ) { mw.log.warn( '[ShipLineNav] render failed:', e ); } }, 0 ); } );
} ).catch( function ( e ) { mw.log.warn( '[ShipLineNav] data load failed:', e ); } );
} );
function loadData() {
try {
if ( window.sessionStorage && sessionStorage.getItem( CONFIG.cacheVersionKey ) === CONFIG.cacheVersion ) {
var cached = sessionStorage.getItem( CONFIG.cacheKey );
if ( cached ) { return Promise.resolve( JSON.parse( cached ) ); }
}
} catch ( e ) {}
var url = mw.util.getUrl( CONFIG.dataPage, { action: 'raw' } );
return fetch( url, { credentials: 'same-origin' } ).then( function ( r ) { return r.json(); } ).then( function ( data ) {
try { sessionStorage.setItem( CONFIG.cacheKey, JSON.stringify( data ) ); sessionStorage.setItem( CONFIG.cacheVersionKey, CONFIG.cacheVersion ); } catch ( e ) {}
return data;
} );
}
function norm( s ) { return ( s || '' ).normalize( 'NFD' ).replace( /[̀-ͯ]/g, '' ).replace( /[^a-z0-9]/gi, '' ).toLowerCase(); }
function findShip( data ) {
var ships = data.ships || {}, id, exact = null, loose = null, want = norm( shipName );
for ( id in ships ) {
if ( ships[ id ].name === shipName ) { exact = ships[ id ]; break; }
if ( !loose && norm( ships[ id ].name ) === want ) { loose = ships[ id ]; }
}
return exact || loose;
}
function roman( t ) { var r = [ '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X', 'XI' ]; return r[ t ] || String( t ); }
function render( data ) {
var ship = findShip( data );
if ( !ship ) { return; }
var lineIds = ship.lines || [];
if ( !lineIds.length ) { return; }
var wrap = document.createElement( 'div' );
wrap.id = 'p-wows-line';
wrap.className = 'b-sidebar_item wows-line-portlet';
var h = document.createElement( 'h5' );
h.className = 'b-sidebar_title b-sidebar_title__opened';
h.textContent = 'Tech Tree Line';
wrap.appendChild( h );
var ul = document.createElement( 'ul' );
ul.className = 'b-left-menu b-left-menu__opened';
wrap.appendChild( ul );
var multi = lineIds.length > 1;
lineIds.forEach( function ( lid ) {
var line = ( data.lines || [] )[ lid ];
if ( !line ) { return; }
if ( multi ) {
var sub = document.createElement( 'li' );
sub.className = 'b-left-menu_item mw-list-item wows-line-subhead';
sub.textContent = line.line_name + ' line';
sub.style.cssText = 'opacity:.6;font-size:.85em;padding:4px 0 2px 20px;';
ul.appendChild( sub );
}
( line.ships || [] ).forEach( function ( s ) {
var li = document.createElement( 'li' );
li.className = 'b-left-menu_item mw-list-item';
var isCurrent = ( s.ship_id === ship.ship_id );
var a = document.createElement( 'a' );
a.className = 'b-left-menu_link b-darklink' + ( isCurrent ? ' wows-line-current' : '' );
if ( !isCurrent ) { a.href = mw.util.getUrl( 'Ship:' + s.name ); }
var pt = document.createElement( 'span' );
pt.className = 'b-left-menu_point';
var tier = document.createElement( 'span' );
tier.className = 'wows-line-tier';
tier.textContent = roman( s.tier );
tier.style.cssText = 'position:absolute;left:3px;top:50%;transform:translateY(-50%);width:22px;text-align:right;color:#AD7A07;font-weight:bold;';
pt.appendChild( tier );
pt.appendChild( document.createTextNode( s.name ) );
if ( isCurrent ) { a.style.fontWeight = 'bold'; pt.style.color = '#d4af37'; tier.style.color = '#d4af37'; }
a.appendChild( pt );
li.appendChild( a );
ul.appendChild( li );
} );
} );
var nav = document.querySelector( '#p-navigation, .mw-portlet-navigation, #p-Navigation' );
if ( nav && nav.parentNode ) { nav.parentNode.insertBefore( wrap, nav.nextSibling ); return; }
var sidebar = document.querySelector( '#mw-panel, .mw-panel, #column-one, .sidebar' );
if ( sidebar ) { sidebar.appendChild( wrap ); return; }
var content = document.querySelector( '#mw-content-text' );
if ( content && content.parentNode ) { content.parentNode.insertBefore( wrap, content ); }
}
}() );