|
|
| Line 1: |
Line 1: |
| /* Any JavaScript here will be loaded for all users on every page load. */ | | /* Any JavaScript here will be loaded for all users on every page load. */ |
| /* =========================================================================
| |
| aslimona.xyz — inline lightbox for gallery / thumbnail images
| |
| Paste this ENTIRE file into: Special:MyLanguage/MediaWiki:Common.js
| |
| (as an admin: go to https://aslimona.xyz/index.php/MediaWiki:Common.js
| |
| click "Edit", paste, save.)
| |
|
| |
| Fixes the "click image -> File page -> back -> click next" problem:
| |
| clicking any gallery or thumbnail image now opens a big in-page viewer
| |
| with Prev/Next arrows and keyboard navigation, on every article,
| |
| automatically. No per-page changes needed.
| |
| ========================================================================= */
| |
| ( function () {
| |
| 'use strict';
| |
|
| |
| function onReady( fn ) {
| |
| if ( document.readyState !== 'loading' ) {
| |
| fn();
| |
| } else {
| |
| document.addEventListener( 'DOMContentLoaded', fn );
| |
| }
| |
| }
| |
|
| |
| // Convert a MediaWiki thumbnail URL to the full-resolution original URL.
| |
| // e.g. .../images/a/a1/Foo.jpg/800px-Foo.jpg -> .../images/a/a1/Foo.jpg
| |
| // .../images/thumb/a/a1/Foo.jpg/800px-Foo.jpg -> .../images/a/a1/Foo.jpg
| |
| function originalFromThumb( src ) {
| |
| var m = src.match( /^(.*\/thumb\/([0-9a-f])\/([0-9a-f]{2})\/([^\/]+))\/\d+px-[^\/]+$/i );
| |
| if ( m ) {
| |
| return src.replace( '/thumb/', '/' ).replace( /\/\d+px-[^\/]+$/, '' );
| |
| }
| |
| var m2 = src.match( /^(.*\/[0-9a-f]\/[0-9a-f]{2}\/[^\/]+)\/\d+px-[^\/]+$/i );
| |
| if ( m2 ) {
| |
| return m2[ 1 ];
| |
| }
| |
| return src;
| |
| }
| |
|
| |
| function injectStyles() {
| |
| var css = [
| |
| '.aslimona-lb-overlay{position:fixed;inset:0;background:rgba(6,6,7,0.94);',
| |
| 'z-index:100000;display:flex;flex-direction:column;align-items:center;',
| |
| 'justify-content:center;padding:4vh 4vw;box-sizing:border-box;}',
| |
| '.aslimona-lb-overlay[hidden]{display:none;}',
| |
| '.aslimona-lb-img{max-width:92vw;max-height:80vh;object-fit:contain;',
| |
| 'border:1px solid #3a372f;background:#111;}',
| |
| '.aslimona-lb-caption{color:#a89f8c;font-family:"IBM Plex Mono","Courier New",monospace;',
| |
| 'font-size:0.8rem;margin-top:14px;text-align:center;max-width:80vw;}',
| |
| '.aslimona-lb-counter{color:#6e6858;font-family:"IBM Plex Mono","Courier New",monospace;',
| |
| 'font-size:0.7rem;margin-top:4px;letter-spacing:0.08em;}',
| |
| '.aslimona-lb-close,.aslimona-lb-prev,.aslimona-lb-next{position:fixed;',
| |
| 'background:transparent;border:1px solid #3a372f;color:#e9e4d8;',
| |
| 'font-family:Georgia,serif;cursor:pointer;transition:background .2s,color .2s;}',
| |
| '.aslimona-lb-close:hover,.aslimona-lb-prev:hover,.aslimona-lb-next:hover{',
| |
| 'background:#c9a15a;color:#0c0c0d;}',
| |
| '.aslimona-lb-close{top:24px;right:24px;width:40px;height:40px;font-size:20px;',
| |
| 'line-height:38px;text-align:center;}',
| |
| '.aslimona-lb-prev,.aslimona-lb-next{top:50%;transform:translateY(-50%);',
| |
| 'width:48px;height:64px;font-size:22px;}',
| |
| '.aslimona-lb-prev{left:16px;}',
| |
| '.aslimona-lb-next{right:16px;}',
| |
| '@media (max-width:600px){',
| |
| '.aslimona-lb-prev,.aslimona-lb-next{width:36px;height:52px;font-size:18px;}',
| |
| '.aslimona-lb-close{width:34px;height:34px;font-size:17px;line-height:32px;}',
| |
| '}'
| |
| ].join( '' );
| |
| var style = document.createElement( 'style' );
| |
| style.textContent = css;
| |
| document.head.appendChild( style );
| |
| }
| |
|
| |
| function buildOverlay() {
| |
| var overlay = document.createElement( 'div' );
| |
| overlay.className = 'aslimona-lb-overlay';
| |
| overlay.hidden = true;
| |
|
| |
| var img = document.createElement( 'img' );
| |
| img.className = 'aslimona-lb-img';
| |
| overlay.appendChild( img );
| |
|
| |
| var caption = document.createElement( 'div' );
| |
| caption.className = 'aslimona-lb-caption';
| |
| overlay.appendChild( caption );
| |
|
| |
| var counter = document.createElement( 'div' );
| |
| counter.className = 'aslimona-lb-counter';
| |
| overlay.appendChild( counter );
| |
|
| |
| var closeBtn = document.createElement( 'button' );
| |
| closeBtn.className = 'aslimona-lb-close';
| |
| closeBtn.setAttribute( 'aria-label', 'Close' );
| |
| closeBtn.textContent = '×';
| |
| overlay.appendChild( closeBtn );
| |
|
| |
| var prevBtn = document.createElement( 'button' );
| |
| prevBtn.className = 'aslimona-lb-prev';
| |
| prevBtn.setAttribute( 'aria-label', 'Previous image' );
| |
| prevBtn.textContent = '‹';
| |
| overlay.appendChild( prevBtn );
| |
|
| |
| var nextBtn = document.createElement( 'button' );
| |
| nextBtn.className = 'aslimona-lb-next';
| |
| nextBtn.setAttribute( 'aria-label', 'Next image' );
| |
| nextBtn.textContent = '›';
| |
| overlay.appendChild( nextBtn );
| |
|
| |
| document.body.appendChild( overlay );
| |
|
| |
| return {
| |
| overlay: overlay,
| |
| img: img,
| |
| caption: caption,
| |
| counter: counter,
| |
| closeBtn: closeBtn,
| |
| prevBtn: prevBtn,
| |
| nextBtn: nextBtn
| |
| };
| |
| }
| |
|
| |
| onReady( function () {
| |
| var content = document.querySelector( '.mw-parser-output' );
| |
| if ( !content ) {
| |
| return;
| |
| }
| |
|
| |
| // Collect every lightbox-able image link on the page, in document order:
| |
| // - <gallery> thumbnails: li.gallerybox a.image
| |
| // - inline [[File:x|thumb]]: div.thumb a.image
| |
| var links = Array.prototype.slice.call(
| |
| content.querySelectorAll( 'li.gallerybox a.image, div.thumb a.image, a.image' )
| |
| );
| |
| if ( !links.length ) {
| |
| return;
| |
| }
| |
| // de-duplicate while preserving order
| |
| var seen = {};
| |
| links = links.filter( function ( a ) {
| |
| if ( seen[ a.href ] ) {
| |
| return false;
| |
| }
| |
| seen[ a.href ] = true;
| |
| return true;
| |
| } );
| |
|
| |
| var items = links.map( function ( a ) {
| |
| var thumbImg = a.querySelector( 'img' );
| |
| var full = thumbImg ? originalFromThumb( thumbImg.src ) : a.href;
| |
| var captionEl = null;
| |
| var gallerytext = a.closest( 'li.gallerybox' );
| |
| if ( gallerytext ) {
| |
| captionEl = gallerytext.querySelector( '.gallerytext' );
| |
| } else {
| |
| var thumbinner = a.closest( '.thumbinner' );
| |
| if ( thumbinner ) {
| |
| captionEl = thumbinner.querySelector( '.thumbcaption' );
| |
| }
| |
| }
| |
| return {
| |
| full: full,
| |
| caption: captionEl ? captionEl.textContent.trim() : ( thumbImg ? thumbImg.alt : '' ),
| |
| link: a
| |
| };
| |
| } );
| |
|
| |
| injectStyles();
| |
| var ui = buildOverlay();
| |
| var currentIndex = 0;
| |
|
| |
| function show( index ) {
| |
| currentIndex = ( index + items.length ) % items.length;
| |
| var item = items[ currentIndex ];
| |
| ui.img.src = item.full;
| |
| ui.caption.textContent = item.caption || '';
| |
| ui.counter.textContent = ( currentIndex + 1 ) + ' / ' + items.length;
| |
| }
| |
|
| |
| function open( index ) {
| |
| show( index );
| |
| ui.overlay.hidden = false;
| |
| document.body.style.overflow = 'hidden';
| |
| }
| |
|
| |
| function close() {
| |
| ui.overlay.hidden = true;
| |
| document.body.style.overflow = '';
| |
| }
| |
|
| |
| items.forEach( function ( item, index ) {
| |
| item.link.addEventListener( 'click', function ( ev ) {
| |
| // let ctrl/cmd/shift/middle-clicks behave normally (open in new tab etc.)
| |
| if ( ev.ctrlKey || ev.metaKey || ev.shiftKey || ev.button === 1 ) {
| |
| return;
| |
| }
| |
| ev.preventDefault();
| |
| open( index );
| |
| } );
| |
| } );
| |
|
| |
| ui.closeBtn.addEventListener( 'click', close );
| |
| ui.prevBtn.addEventListener( 'click', function () {
| |
| show( currentIndex - 1 );
| |
| } );
| |
| ui.nextBtn.addEventListener( 'click', function () {
| |
| show( currentIndex + 1 );
| |
| } );
| |
| ui.overlay.addEventListener( 'click', function ( ev ) {
| |
| if ( ev.target === ui.overlay ) {
| |
| close();
| |
| }
| |
| } );
| |
| document.addEventListener( 'keydown', function ( ev ) {
| |
| if ( ui.overlay.hidden ) {
| |
| return;
| |
| }
| |
| if ( ev.key === 'Escape' ) {
| |
| close();
| |
| } else if ( ev.key === 'ArrowLeft' ) {
| |
| show( currentIndex - 1 );
| |
| } else if ( ev.key === 'ArrowRight' ) {
| |
| show( currentIndex + 1 );
| |
| }
| |
| } );
| |
| } );
| |
| }() );
| |