MediaWiki:Vector-2022.js

From LinnDocs
Jump to navigation Jump to search

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.
/* All JavaScript here will be loaded for users of the Vector skin */

/* Second logo in the header, linking to www.linn.co.uk */
mw.loader.using( 'mediawiki.util' ).then( function () {
	var start = document.querySelector( '.vector-header-start' );
	if ( !start || document.getElementById( 'linn-site-logo' ) ) {
		return;
	}
	var link = document.createElement( 'a' );
	link.id = 'linn-site-logo';
	link.href = 'https://www.linn.co.uk/';
	link.title = 'Linn – www.linn.co.uk';
	link.target = '_blank';
	link.rel = 'noopener';

	var img = document.createElement( 'img' );
	img.src = mw.config.get( 'wgScriptPath' ) + '/images/Linn_Logo.png';
	img.alt = 'Linn';

	link.appendChild( img );
	start.appendChild( link );
} );

/* In-page table of contents for Vector 2022 (in addition to the sidebar) */
$( function () {
	var content = document.querySelector( '#mw-content-text .mw-parser-output' );
	
// Pages can opt out of the in-text TOC with <span class="no-inline-toc"></span>
	if ( content.querySelector( '.no-inline-toc' ) ) {
		return;
	}
	// MediaWiki leaves this marker where the TOC belongs; none means no TOC
	// (e.g. __NOTOC__, or fewer than four headings without __FORCETOC__)
	var marker = content.querySelector( 'meta[property="mw:PageProp/toc"]' );
	if ( !marker ) {
		return;
	}
	var headings = content.querySelectorAll( 'h2[id], h3[id], h4[id], h5[id], h6[id]' );
	if ( !headings.length ) {
		return;
	}

	var levels = Array.prototype.map.call( headings, function ( h ) {
		return Number( h.tagName.charAt( 1 ) );
	} );
	var base = Math.min.apply( null, levels );
	var root = document.createElement( 'ul' );
	var stack = [ root ];
	var nums = [];

	Array.prototype.forEach.call( headings, function ( h, i ) {
		var depth = levels[ i ] - base;
		while ( stack.length - 1 > depth ) {
			stack.pop();
		}
		while ( stack.length - 1 < depth ) {
			var parent = stack[ stack.length - 1 ];
			var lastLi = parent.lastElementChild || parent.appendChild( document.createElement( 'li' ) );
			stack.push( lastLi.appendChild( document.createElement( 'ul' ) ) );
		}
		nums.length = depth + 1;
		nums[ depth ] = ( nums[ depth ] || 0 ) + 1;

		var li = document.createElement( 'li' );
		li.className = 'toclevel-' + ( depth + 1 );
		var a = document.createElement( 'a' );
		a.href = '#' + h.id;
		var num = document.createElement( 'span' );
		num.className = 'tocnumber';
		num.textContent = nums.map( function ( n ) { return n || 1; } ).join( '.' );
		var text = document.createElement( 'span' );
		text.className = 'toctext';
		text.textContent = h.textContent.trim();
		a.appendChild( num );
		a.appendChild( document.createTextNode( ' ' ) );
		a.appendChild( text );
		li.appendChild( a );
		stack[ stack.length - 1 ].appendChild( li );
	} );

	var toc = document.createElement( 'div' );
	toc.id = 'toc';
	toc.className = 'toc';
	toc.setAttribute( 'role', 'navigation' );
	var title = document.createElement( 'div' );
	title.className = 'toctitle';
	title.textContent = 'Contents';
	toc.appendChild( title );
	toc.appendChild( root );
	marker.parentNode.replaceChild( toc, marker );
} );


/* Quick guide: first-visit overlay, tooltips and "?" button (Vector 2022) */
mw.loader.using( 'mediawiki.cookie' ).then( function () {
	var COOKIE = 'linnHelpSeen';
	var steps = [
		{
			sel: [ '#vector-main-menu-dropdown', '#vector-main-menu-pinned-container' ],
			title: 'Main menu',
			text: 'Site navigation. Choose "move to sidebar" to keep it open on the left, or "hide" to tuck it back into this button.'
		},
		{
			sel: [ '#vector-page-titlebar-toc', '#vector-toc-pinned-container' ],
			title: 'Contents',
			text: 'Jump to any section of this page. It can sit in the sidebar or be collapsed into this button.'
		},
		{
			sel: [ '#vector-appearance-dropdown', '#vector-appearance-pinned-container' ],
			title: 'Appearance',
			text: 'Change the text size and page width.'
		}
	];

	// First matching element that is actually visible on screen
	function findVisible( selectors ) {
		for ( var i = 0; i < selectors.length; i++ ) {
			var el = document.querySelector( selectors[ i ] );
			if ( el ) {
				var r = el.getBoundingClientRect();
				if ( r.width && r.height ) {
					return el;
				}
			}
		}
		return null;
	}

	// 1. Hover tooltips on the header buttons
	steps.forEach( function ( s ) {
		var el = document.querySelector( s.sel[ 0 ] );
		if ( el ) {
			el.title = s.title + ': ' + s.text;
		}
	} );

	function closeTour() {
		var old = document.getElementById( 'linn-tour' );
		if ( old ) {
			old.parentNode.removeChild( old );
		}
		document.removeEventListener( 'keydown', onKey );
	}

	function onKey( e ) {
		if ( e.key === 'Escape' ) {
			finishTour();
		}
	}

	function finishTour() {
		mw.cookie.set( COOKIE, '1', { expires: 365 * 24 * 60 * 60 } ); // one year
		closeTour();
	}

	// 2. The overlay
	function showTour() {
		closeTour();
		window.scrollTo( 0, 0 );

		var overlay = document.createElement( 'div' );
		overlay.id = 'linn-tour';
		overlay.addEventListener( 'click', finishTour );
		document.body.appendChild( overlay );
		var placed = [];

		steps.forEach( function ( s,i ) {
			var el = findVisible( s.sel );
			if ( !el ) {
				return;
			}
			var r = el.getBoundingClientRect();

			var ring = document.createElement( 'div' );
			ring.className = 'linn-tour-ring';
            ring.setAttribute( 'data-step', i + 1 )
			ring.style.left = ( r.left - 4 ) + 'px';
			ring.style.top = ( r.top - 4 ) + 'px';
			ring.style.width = ( r.width + 8 ) + 'px';
			ring.style.height = ( r.height + 8 ) + 'px';
			overlay.appendChild( ring );

			var tip = document.createElement( 'div' );
			tip.className = 'linn-tour-tip';
			var h = document.createElement( 'strong' );
			h.textContent = ( i + 1 ) + '. ' + s.title;
			tip.appendChild( h );
			tip.appendChild( document.createTextNode( s.text ) );

			if ( r.height > 100 ) {
				// Tall sidebar panel: put the tip to its right
				tip.style.left = ( r.right + 14 ) + 'px';
				tip.style.top = ( r.top + 10 ) + 'px';
			} else {
				// Header button: put the tip below it
				tip.style.top = ( r.bottom + 14 ) + 'px';
				if ( r.left > window.innerWidth - 300 ) {
					tip.style.right = ( window.innerWidth - r.right ) + 'px';
				} else {
					tip.style.left = r.left + 'px';
				}
			}
			overlay.appendChild( tip );
			// Move this tip down until it no longer overlaps an earlier one
			var moved = true;
			while ( moved ) {
				moved = false;
				var tr = tip.getBoundingClientRect();
				for ( var j = 0; j < placed.length; j++ ) {
					var p = placed[ j ];
					if ( tr.left < p.right && tr.right > p.left &&
						tr.top < p.bottom && tr.bottom > p.top ) {
						tip.style.top = ( p.bottom + 10 ) + 'px';
						moved = true;
						break;
					}
				}
			}
			placed.push( tip.getBoundingClientRect() );
		} );

		var panel = document.createElement( 'div' );
		panel.className = 'linn-tour-panel';
		var heading = document.createElement( 'strong' );
		heading.textContent = 'Welcome to LinnDocs';
		var intro = document.createElement( 'p' );
		intro.textContent = 'Here’s a quick guide to the buttons at the top of the page. ' +
			'You can see this again at any time with the ? button.';
		var btn = document.createElement( 'button' );
		btn.type = 'button';
		btn.textContent = 'Got it';
		panel.appendChild( heading );
		panel.appendChild( intro );
		panel.appendChild( btn );
		overlay.appendChild( panel );

		document.addEventListener( 'keydown', onKey );
		btn.focus();
	}

	// 3. "?" button in the header to reopen the guide
	var end = document.querySelector( '.vector-header-end' );
	if ( end && !document.getElementById( 'linn-help-btn' ) ) {
		var helpBtn = document.createElement( 'button' );
		helpBtn.id = 'linn-help-btn';
		helpBtn.type = 'button';
		helpBtn.textContent = '?';
		helpBtn.title = 'Show the quick guide';
		helpBtn.addEventListener( 'click', showTour );
		end.insertBefore( helpBtn, end.firstChild );
	}

	// Show automatically on the first visit (short delay lets Vector finish its layout)
	if ( !mw.cookie.get( COOKIE ) ) {
		setTimeout( showTour, 800 );
	}
} );