Jump to content

MediaWiki:Common.js

From Manath's Campaign Wiki
Revision as of 17:00, 1 November 2026 by Manath (talk | contribs) (Page turning for the story book)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/* The Story So Far: show chapters as facing pages, with tabs and arrows to turn between spreads. */
mw.hook( 'wikipage.content' ).add( function ( $content ) {
	$content.find( '.story-book' ).each( function () {
		var book = this;
		if ( book.getAttribute( 'data-ready' ) ) {
			return;
		}
		book.setAttribute( 'data-ready', '1' );

		var pages = Array.prototype.filter.call( book.children, function ( el ) {
			return el.classList.contains( 'story-box' );
		} );
		if ( !pages.length ) {
			return;
		}

		// Group the chapters into spreads of two facing pages.
		var spreads = [];
		for ( var i = 0; i < pages.length; i += 2 ) {
			var spread = document.createElement( 'div' );
			spread.className = 'story-spread';
			book.insertBefore( spread, pages[ i ] );
			spread.appendChild( pages[ i ] );
			if ( pages[ i + 1 ] ) {
				spread.appendChild( pages[ i + 1 ] );
			}
			spreads.push( spread );
		}
		// Drop stray empty paragraphs the parser left between chapters.
		Array.prototype.forEach.call( book.querySelectorAll( ':scope > p' ), function ( p ) {
			if ( /^\s*$/.test( p.textContent ) ) {
				p.remove();
			}
		} );
		if ( spreads.length < 2 ) {
			return;
		}

		function chapterNumber( page, fallback ) {
			var title = page.querySelector( '.story-title' );
			var m = title && title.textContent.match( /Chapter\s+([0-9IVXLC]+)/i );
			return m ? m[ 1 ] : String( fallback );
		}

		var labels = spreads.map( function ( spread, n ) {
			var first = chapterNumber( spread.children[ 0 ], n * 2 + 1 );
			return spread.children[ 1 ] ?
				'Chapters ' + first + '–' + chapterNumber( spread.children[ 1 ], n * 2 + 2 ) :
				'Chapter ' + first;
		} );

		var current = 0;
		var navs = [];

		function makeButton( text, title, onClick ) {
			var b = document.createElement( 'button' );
			b.type = 'button';
			b.textContent = text;
			if ( title ) {
				b.title = title;
			}
			b.addEventListener( 'click', onClick );
			return b;
		}

		function makeNav() {
			var nav = document.createElement( 'div' );
			nav.className = 'story-nav';
			var tabs = labels.map( function ( label, n ) {
				var tab = makeButton( label, null, function () {
					show( n, nav );
				} );
				tab.setAttribute( 'role', 'tab' );
				nav.appendChild( tab );
				return tab;
			} );
			var arrows = document.createElement( 'span' );
			arrows.className = 'story-arrows';
			var prev = makeButton( '◂ Previous', null, function () {
				show( current - 1, nav );
			} );
			var next = makeButton( 'Next ▸', null, function () {
				show( current + 1, nav );
			} );
			arrows.appendChild( prev );
			arrows.appendChild( next );
			nav.appendChild( arrows );
			navs.push( { tabs: tabs, prev: prev, next: next } );
			return nav;
		}

		function show( n, fromNav ) {
			if ( n < 0 || n >= spreads.length ) {
				return;
			}
			current = n;
			spreads.forEach( function ( spread, k ) {
				spread.hidden = k !== n;
			} );
			navs.forEach( function ( nav ) {
				nav.tabs.forEach( function ( tab, k ) {
					tab.setAttribute( 'aria-selected', k === n ? 'true' : 'false' );
				} );
				nav.prev.disabled = n === 0;
				nav.next.disabled = n === spreads.length - 1;
			} );
			// Turning the page from the bottom bar jumps back up to the top of the book.
			if ( fromNav && fromNav === bottomNav && book.getBoundingClientRect().top < 0 ) {
				book.scrollIntoView( { behavior: 'smooth' } );
			}
		}

		var topNav = makeNav();
		var bottomNav = makeNav();
		book.insertBefore( topNav, book.firstChild );
		book.appendChild( bottomNav );
		show( 0 );
	} );
} );