Toggle menu
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

MediaWiki:Common.js: Difference between revisions

MediaWiki interface page
No edit summary
No edit summary
 
Line 1: Line 1:
/* Any JavaScript here will be loaded for all users on every page load. */
/**
/**
  * eve.wiki — MediaWiki:Common.js
  * eve.wiki — MediaWiki:Common.js
  * EVE-specific JS for Citizen skin
  * Clean rewrite no syntax errors
* No external dependencies vanilla JS only
  */
  */


Line 11: Line 9:
/* ============================================================
/* ============================================================
  1. WAVE ACCORDION
  1. WAVE ACCORDION
  Finds all .evewiki-wave elements and wires up click toggles.
  To use in wikitext: wrap content in <div class="evewiki-wave">
    <div class="evewiki-wave-header">...</div>
    <div class="evewiki-wave-body">...</div>
  </div>
  ============================================================ */
  ============================================================ */
function initWaveAccordions() {
function initWaveAccordions() {
var waves = document.querySelectorAll( '.evewiki-wave' );
var waves = document.querySelectorAll( '.evewiki-wave' );
waves.forEach( function ( wave ) {
if ( !waves.length ) return;
 
waves.forEach( function ( wave, i ) {
var header = wave.querySelector( '.evewiki-wave-header' );
var header = wave.querySelector( '.evewiki-wave-header' );
if ( !header ) return;
if ( !header ) return;


// Open first wave by default
if ( i === 0 ) {
if ( wave === waves[ 0 ] ) {
wave.classList.add( 'is-open' );
wave.classList.add( 'is-open' );
}
}
header.setAttribute( 'tabindex', '0' );
header.setAttribute( 'role', 'button' );


header.addEventListener( 'click', function () {
header.addEventListener( 'click', function () {
Line 32: Line 29:
} );
} );


// Keyboard support
header.setAttribute( 'tabindex', '0' );
header.setAttribute( 'role', 'button' );
header.addEventListener( 'keydown', function ( e ) {
header.addEventListener( 'keydown', function ( e ) {
if ( e.key === 'Enter' || e.key === ' ' ) {
if ( e.key === 'Enter' || e.key === ' ' ) {
Line 45: Line 39:


/* ============================================================
/* ============================================================
  2. SEARCH HOTKEY
  2. SEARCH HOTKEY / to focus search
  Press / to focus search (standard wiki UX pattern)
  ============================================================ */
  ============================================================ */
function initSearchHotkey() {
function initSearchHotkey() {
var searchInput = document.querySelector(
'#searchInput, .citizen-search__input, input[name="search"]'
);
if ( !searchInput ) return;
document.addEventListener( 'keydown', function ( e ) {
document.addEventListener( 'keydown', function ( e ) {
var tag = document.activeElement ? document.activeElement.tagName : '';
var tag = document.activeElement ? document.activeElement.tagName : '';
if ( e.key === '/' && tag !== 'INPUT' && tag !== 'TEXTAREA' ) {
if ( e.key === '/' && tag !== 'INPUT' && tag !== 'TEXTAREA' ) {
e.preventDefault();
e.preventDefault();
searchInput.focus();
var input = document.querySelector(
searchInput.select();
'#evewiki-search-input, .mw-inputbox-input, input.mw-searchInput, .citizen-search__input'
);
if ( input ) {
input.focus();
input.select();
}
}
}
if ( e.key === 'Escape' && document.activeElement === searchInput ) {
} );
searchInput.blur();
}
 
/* ============================================================
  3. HOMEPAGE SEARCH — Enter key handler
  ============================================================ */
function initHomepageSearch() {
var input = document.querySelector( '.evewiki-hp-search .mw-inputbox-input' );
if ( !input ) return;
 
input.addEventListener( 'keydown', function ( e ) {
if ( e.key === 'Enter' ) {
var q = input.value.trim();
if ( q ) {
window.location = mw.util.getUrl( 'Special:Search' ) + '?search=' + encodeURIComponent( q ) + '&fulltext=1';
}
}
}
} );
} );
Line 68: Line 75:


/* ============================================================
/* ============================================================
  3. ACTIVE NAV HIGHLIGHT
  4. ACTIVE NAV HIGHLIGHT
  Adds .evewiki-nav-active to sidebar links matching current page path
  ============================================================ */
  ============================================================ */
function initActiveNav() {
function initActiveNav() {
var path = window.location.pathname;
var path = window.location.pathname;
var links = document.querySelectorAll(
var links = document.querySelectorAll( '.citizen-nav a, #citizen-sidebar a' );
'.citizen-nav a, #citizen-sidebar a, .citizen-sidebar a'
);
links.forEach( function ( link ) {
links.forEach( function ( link ) {
var href = link.getAttribute( 'href' );
var href = link.getAttribute( 'href' );
if ( href && href !== '/' && path.indexOf( href ) === 0 ) {
if ( href && href !== '/' && path.indexOf( href ) === 0 ) {
link.closest( 'li' )?.classList.add( 'citizen-nav__item--active' );
var li = link.closest( 'li' );
if ( li ) {
li.classList.add( 'citizen-nav__item--active' );
}
}
}
} );
} );
Line 85: Line 92:


/* ============================================================
/* ============================================================
  4. INFOBOX IMAGE LAZY EXPAND
  5. INFOBOX IMAGE ZOOM
  Click on infobox portrait to open full-size in overlay
  ============================================================ */
  ============================================================ */
function initInfoboxPortrait() {
function initInfoboxZoom() {
var portraits = document.querySelectorAll( '.infobox img, .evewiki-portrait img' );
var imgs = document.querySelectorAll( '.infobox img, .evewiki-portrait img' );
portraits.forEach( function ( img ) {
imgs.forEach( function ( img ) {
img.style.cursor = 'zoom-in';
img.style.cursor = 'zoom-in';
img.addEventListener( 'click', function () {
img.addEventListener( 'click', function () {
var overlay = document.createElement( 'div' );
var overlay = document.createElement( 'div' );
overlay.style.cssText = [
overlay.style.cssText = 'position:fixed;inset:0;z-index:99999;background:rgba(0,0,0,.85);display:flex;align-items:center;justify-content:center;cursor:zoom-out';
'position:fixed', 'inset:0', 'z-index:99999',
'background:rgba(0,0,0,.85)',
'display:flex', 'align-items:center', 'justify-content:center',
'cursor:zoom-out'
].join(';');


var full = document.createElement( 'img' );
var full = document.createElement( 'img' );
full.src = img.src;
full.src = img.src;
full.style.cssText = 'max-width:90vw;max-height:90vh;object-fit:contain;border:1px solid #233042;';
full.style.cssText = 'max-width:90vw;max-height:90vh;object-fit:contain;border:1px solid #233042';


overlay.appendChild( full );
overlay.appendChild( full );
Line 109: Line 110:


overlay.addEventListener( 'click', function () {
overlay.addEventListener( 'click', function () {
document.body.removeChild( overlay );
if ( document.body.contains( overlay ) ) {
document.body.removeChild( overlay );
}
} );
} );


document.addEventListener( 'keydown', function esc( e ) {
document.addEventListener( 'keydown', function escHandler( e ) {
if ( e.key === 'Escape' ) {
if ( e.key === 'Escape' ) {
if ( document.body.contains( overlay ) ) {
if ( document.body.contains( overlay ) ) {
document.body.removeChild( overlay );
document.body.removeChild( overlay );
}
}
document.removeEventListener( 'keydown', esc );
document.removeEventListener( 'keydown', escHandler );
}
}
} );
} );
Line 125: Line 128:


/* ============================================================
/* ============================================================
  5. SORTABLE TABLE ENHANCEMENTS
  6. CODE BLOCK COPY BUTTON
  Adds row hover highlight class for wikitables
  ============================================================ */
function initTableEnhancements() {
var tables = document.querySelectorAll( '.wikitable, .evewiki-table' );
tables.forEach( function ( table ) {
var rows = table.querySelectorAll( 'tbody tr' );
rows.forEach( function ( row ) {
row.addEventListener( 'mouseenter', function () {
row.style.background = '#1c2230';
} );
row.addEventListener( 'mouseleave', function () {
row.style.background = '';
} );
} );
} );
}
 
/* ============================================================
  6. COPY-TO-CLIPBOARD FOR CODE BLOCKS
  Adds a small copy button to <pre> blocks
  ============================================================ */
  ============================================================ */
function initCodeCopy() {
function initCodeCopy() {
Line 154: Line 137:
var btn = document.createElement( 'button' );
var btn = document.createElement( 'button' );
btn.textContent = 'Copy';
btn.textContent = 'Copy';
btn.style.cssText = [
btn.style.cssText = 'position:absolute;top:6px;right:6px;background:#131720;border:1px solid #233042;color:#93a4b5;font-family:Rajdhani,sans-serif;font-size:10px;font-weight:600;letter-spacing:1px;text-transform:uppercase;padding:3px 8px;cursor:pointer';
'position:absolute', 'top:6px', 'right:6px',
'background:#131720', 'border:1px solid #233042',
'color:#93a4b5', 'font-family:Rajdhani,sans-serif',
'font-size:10px', 'font-weight:600', 'letter-spacing:1px',
'text-transform:uppercase', 'padding:3px 8px',
'cursor:pointer', 'transition:color .12s,border-color .12s'
].join(';');
 
btn.addEventListener( 'mouseenter', function () {
btn.style.color = '#4fc3e8';
btn.style.borderColor = '#4fc3e8';
} );
btn.addEventListener( 'mouseleave', function () {
btn.style.color = '#93a4b5';
btn.style.borderColor = '#233042';
} );


btn.addEventListener( 'click', function () {
btn.addEventListener( 'click', function () {
navigator.clipboard.writeText( pre.textContent.trim() ).then( function () {
var text = pre.textContent.trim();
btn.textContent = 'Copied!';
if ( navigator.clipboard ) {
btn.style.color = '#3acc7a';
navigator.clipboard.writeText( text ).then( function () {
setTimeout( function () {
btn.textContent = 'Copied!';
btn.textContent = 'Copy';
btn.style.color = '#3acc7a';
btn.style.color = '#93a4b5';
setTimeout( function () {
}, 2000 );
btn.textContent = 'Copy';
} );
btn.style.color = '#93a4b5';
}, 2000 );
} );
}
} );
} );


Line 188: Line 158:


/* ============================================================
/* ============================================================
  7. HOMEPAGE-ONLY: CATEGORY CARD HOVER GLOW
  INIT
  Only runs on Main_Page
  ============================================================ */
function initHomepageEffects() {
if ( mw.config.get( 'wgPageName' ) !== 'Main_Page' ) return;
 
var cards = document.querySelectorAll( '.evewiki-hp-card' );
cards.forEach( function ( card ) {
card.addEventListener( 'mouseenter', function () {
card.style.borderTopColor = '#4fc3e8';
card.style.boxShadow = '0 0 20px rgba(79,195,232,.08)';
} );
card.addEventListener( 'mouseleave', function () {
card.style.borderTopColor = '';
card.style.boxShadow = '';
} );
} );
}
 
/* ============================================================
  INIT — run after DOM ready
  ============================================================ */
  ============================================================ */
function init() {
function init() {
initWaveAccordions();
initWaveAccordions();
initSearchHotkey();
initSearchHotkey();
initHomepageSearch();
initActiveNav();
initActiveNav();
initInfoboxPortrait();
initInfoboxZoom();
initTableEnhancements();
initCodeCopy();
initCodeCopy();
initHomepageEffects();
}
}


Line 226: Line 175:
}
}


}() );
/* ============================================================
  HOMEPAGE SEARCH BAR — Enter key handler
  ============================================================ */
( function () {
  var input = document.getElementById( 'evewiki-search-input' );
  if ( !input ) return;
  input.addEventListener( 'keydown', function ( e ) {
    if ( e.key === 'Enter' ) {
      var q = input.value.trim();
      if ( q ) {
        window.location = '/wiki/index.php?title=Special:Search&search=' +
          encodeURIComponent( q ) + '&fulltext=1';
      }
    }
  } );
  // Also wire the / hotkey to focus this input on homepage
  document.addEventListener( 'keydown', function ( e ) {
    var tag = document.activeElement ? document.activeElement.tagName : '';
    if ( e.key === '/' && tag !== 'INPUT' && tag !== 'TEXTAREA' ) {
      e.preventDefault();
      input.focus();
    }
  } );
}() );
}() );

Latest revision as of 23:47, 13 April 2026

/**
 * eve.wiki — MediaWiki:Common.js
 * Clean rewrite — no syntax errors
 */

( function () {
	'use strict';

	/* ============================================================
	   1. WAVE ACCORDION
	   ============================================================ */
	function initWaveAccordions() {
		var waves = document.querySelectorAll( '.evewiki-wave' );
		if ( !waves.length ) return;

		waves.forEach( function ( wave, i ) {
			var header = wave.querySelector( '.evewiki-wave-header' );
			if ( !header ) return;

			if ( i === 0 ) {
				wave.classList.add( 'is-open' );
			}

			header.setAttribute( 'tabindex', '0' );
			header.setAttribute( 'role', 'button' );

			header.addEventListener( 'click', function () {
				wave.classList.toggle( 'is-open' );
			} );

			header.addEventListener( 'keydown', function ( e ) {
				if ( e.key === 'Enter' || e.key === ' ' ) {
					e.preventDefault();
					wave.classList.toggle( 'is-open' );
				}
			} );
		} );
	}

	/* ============================================================
	   2. SEARCH HOTKEY — / to focus search
	   ============================================================ */
	function initSearchHotkey() {
		document.addEventListener( 'keydown', function ( e ) {
			var tag = document.activeElement ? document.activeElement.tagName : '';
			if ( e.key === '/' && tag !== 'INPUT' && tag !== 'TEXTAREA' ) {
				e.preventDefault();
				var input = document.querySelector(
					'#evewiki-search-input, .mw-inputbox-input, input.mw-searchInput, .citizen-search__input'
				);
				if ( input ) {
					input.focus();
					input.select();
				}
			}
		} );
	}

	/* ============================================================
	   3. HOMEPAGE SEARCH — Enter key handler
	   ============================================================ */
	function initHomepageSearch() {
		var input = document.querySelector( '.evewiki-hp-search .mw-inputbox-input' );
		if ( !input ) return;

		input.addEventListener( 'keydown', function ( e ) {
			if ( e.key === 'Enter' ) {
				var q = input.value.trim();
				if ( q ) {
					window.location = mw.util.getUrl( 'Special:Search' ) + '?search=' + encodeURIComponent( q ) + '&fulltext=1';
				}
			}
		} );
	}

	/* ============================================================
	   4. ACTIVE NAV HIGHLIGHT
	   ============================================================ */
	function initActiveNav() {
		var path = window.location.pathname;
		var links = document.querySelectorAll( '.citizen-nav a, #citizen-sidebar a' );
		links.forEach( function ( link ) {
			var href = link.getAttribute( 'href' );
			if ( href && href !== '/' && path.indexOf( href ) === 0 ) {
				var li = link.closest( 'li' );
				if ( li ) {
					li.classList.add( 'citizen-nav__item--active' );
				}
			}
		} );
	}

	/* ============================================================
	   5. INFOBOX IMAGE ZOOM
	   ============================================================ */
	function initInfoboxZoom() {
		var imgs = document.querySelectorAll( '.infobox img, .evewiki-portrait img' );
		imgs.forEach( function ( img ) {
			img.style.cursor = 'zoom-in';
			img.addEventListener( 'click', function () {
				var overlay = document.createElement( 'div' );
				overlay.style.cssText = 'position:fixed;inset:0;z-index:99999;background:rgba(0,0,0,.85);display:flex;align-items:center;justify-content:center;cursor:zoom-out';

				var full = document.createElement( 'img' );
				full.src = img.src;
				full.style.cssText = 'max-width:90vw;max-height:90vh;object-fit:contain;border:1px solid #233042';

				overlay.appendChild( full );
				document.body.appendChild( overlay );

				overlay.addEventListener( 'click', function () {
					if ( document.body.contains( overlay ) ) {
						document.body.removeChild( overlay );
					}
				} );

				document.addEventListener( 'keydown', function escHandler( e ) {
					if ( e.key === 'Escape' ) {
						if ( document.body.contains( overlay ) ) {
							document.body.removeChild( overlay );
						}
						document.removeEventListener( 'keydown', escHandler );
					}
				} );
			} );
		} );
	}

	/* ============================================================
	   6. CODE BLOCK COPY BUTTON
	   ============================================================ */
	function initCodeCopy() {
		var pres = document.querySelectorAll( '.mw-parser-output pre' );
		pres.forEach( function ( pre ) {
			pre.style.position = 'relative';

			var btn = document.createElement( 'button' );
			btn.textContent = 'Copy';
			btn.style.cssText = 'position:absolute;top:6px;right:6px;background:#131720;border:1px solid #233042;color:#93a4b5;font-family:Rajdhani,sans-serif;font-size:10px;font-weight:600;letter-spacing:1px;text-transform:uppercase;padding:3px 8px;cursor:pointer';

			btn.addEventListener( 'click', function () {
				var text = pre.textContent.trim();
				if ( navigator.clipboard ) {
					navigator.clipboard.writeText( text ).then( function () {
						btn.textContent = 'Copied!';
						btn.style.color = '#3acc7a';
						setTimeout( function () {
							btn.textContent = 'Copy';
							btn.style.color = '#93a4b5';
						}, 2000 );
					} );
				}
			} );

			pre.appendChild( btn );
		} );
	}

	/* ============================================================
	   INIT
	   ============================================================ */
	function init() {
		initWaveAccordions();
		initSearchHotkey();
		initHomepageSearch();
		initActiveNav();
		initInfoboxZoom();
		initCodeCopy();
	}

	if ( document.readyState === 'loading' ) {
		document.addEventListener( 'DOMContentLoaded', init );
	} else {
		init();
	}

}() );