Toggle menu
1
18
1
210
技术文档中心
Toggle preferences menu
Toggle personal menu
Administrator Login Only
Access is restricted to site administrators.

MediaWiki:Common.js

MediaWiki interface page

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.
( function () {
	'use strict';

	var ACTIVE_CLASS = 'citizen-toc-list-item--active';
	var ACTIVE_TOP_CLASS = 'citizen-toc-level-1--active';
	var LINK_SELECTOR = '.citizen-toc-link:not(.citizen-toc-top)';
	var NAVIGATION_KEYS = [
		'ArrowDown',
		'ArrowUp',
		'End',
		'Home',
		'PageDown',
		'PageUp',
		' '
	];

	function setupHomeCarousel() {
		var carousel = document.querySelector( '.lcd-home-carousel' );

		if ( !carousel || carousel.dataset.techdocsCarouselReady === '1' ) {
			return;
		}

		var slides = Array.prototype.slice.call(
			carousel.querySelectorAll( '.lcd-home-carousel__slide' )
		);
		if ( slides.length === 0 ) {
			return;
		}

		carousel.dataset.techdocsCarouselReady = '1';

		var interval = parseInt( carousel.dataset.interval, 10 );
		if ( !Number.isFinite( interval ) || interval < 1000 ) {
			interval = 4000;
		}

		var currentIndex = Math.max( 0, slides.findIndex( function ( slide ) {
			return slide.classList.contains( 'lcd-home-carousel__slide--active' );
		} ) );
		var timerId = 0;
		var dots = [];
		var dotsContainer = document.createElement( 'div' );

		dotsContainer.className = 'lcd-home-carousel__dots';
		dotsContainer.setAttribute( 'role', 'group' );
		dotsContainer.setAttribute( 'aria-label', '轮播进度' );

		function showSlide( index ) {
			currentIndex = ( index + slides.length ) % slides.length;

			slides.forEach( function ( slide, slideIndex ) {
				var isActive = slideIndex === currentIndex;
				slide.classList.toggle(
					'lcd-home-carousel__slide--active',
					isActive
				);
				slide.setAttribute( 'aria-hidden', isActive ? 'false' : 'true' );
			} );

			dots.forEach( function ( dot, dotIndex ) {
				if ( dotIndex === currentIndex ) {
					dot.setAttribute( 'aria-current', 'true' );
				} else {
					dot.removeAttribute( 'aria-current' );
				}
			} );
		}

		function stopTimer() {
			if ( timerId ) {
				window.clearInterval( timerId );
				timerId = 0;
			}
		}

		function startTimer() {
			stopTimer();
			if ( slides.length > 1 && !document.hidden ) {
				timerId = window.setInterval( function () {
					showSlide( currentIndex + 1 );
				}, interval );
			}
		}

		slides.forEach( function ( slide, slideIndex ) {
			var dot = document.createElement( 'button' );
			dot.type = 'button';
			dot.className = 'lcd-home-carousel__dot';
			dot.setAttribute(
				'aria-label',
				'显示第 ' + ( slideIndex + 1 ) + ' 张轮播图片'
			);
			dot.addEventListener( 'click', function () {
				showSlide( slideIndex );
				startTimer();
			} );
			dots.push( dot );
			dotsContainer.appendChild( dot );
		} );

		carousel.appendChild( dotsContainer );
		showSlide( currentIndex );
		startTimer();

		document.addEventListener( 'visibilitychange', function () {
			if ( document.hidden ) {
				stopTimer();
			} else {
				startTimer();
			}
		} );
		window.addEventListener( 'pagehide', stopTimer );
	}

	function decodeFragment( hash ) {
		if ( !hash || hash.charAt( 0 ) !== '#' ) {
			return '';
		}
		try {
			return decodeURIComponent( hash.slice( 1 ) );
		} catch ( error ) {
			return hash.slice( 1 );
		}
	}

	function setupSingleActiveToc() {
		var toc = document.querySelector( '.citizen-toc' );
		var indicator = toc && toc.querySelector( '.citizen-toc-indicator' );

		if ( !toc || !indicator || toc.dataset.techdocsSingleActiveToc === '1' ) {
			return;
		}

		var entries = Array.prototype.map.call(
			toc.querySelectorAll( LINK_SELECTOR ),
			function ( link ) {
				var target = document.getElementById( decodeFragment( link.hash ) );
				var item = link.closest( '.citizen-toc-list-item' );
				return target && item ? {
					item: item,
					link: link,
					target: target
				} : null;
			}
		).filter( Boolean );

		if ( entries.length === 0 ) {
			return;
		}

		toc.dataset.techdocsSingleActiveToc = '1';

		var applying = false;
		var frameId = 0;
		var manualEntry = null;

		function getActivationTop() {
			var value = parseFloat(
				window.getComputedStyle( document.documentElement ).scrollPaddingTop
			);
			return Number.isFinite( value ) ? value : 0;
		}

		function findEntryForHash() {
			var id = decodeFragment( window.location.hash );
			return entries.find( function ( entry ) {
				return entry.target.id === id;
			} ) || null;
		}

		function findEntryForScroll() {
			var activationTop = getActivationTop();
			var selected = entries[ 0 ];

			entries.some( function ( entry ) {
				if ( entry.target.getBoundingClientRect().top <= activationTop + 1 ) {
					selected = entry;
					return false;
				}
				return true;
			} );

			return selected;
		}

		function updateIndicator( entry ) {
			var positioningParent = indicator.offsetParent || toc;
			var parentRect = positioningParent.getBoundingClientRect();
			var linkRect = entry.link.getBoundingClientRect();
			var unitHeight = linkRect.height || entry.link.offsetHeight || 32;
			var top = linkRect.top - parentRect.top + positioningParent.scrollTop;

			indicator.style.setProperty( '--indicator-unit-height', unitHeight + 'px' );
			indicator.style.setProperty( '--indicator-top', top + 'px' );
			indicator.style.setProperty( '--indicator-scale', '1' );
		}

		function applyEntry( entry ) {
			if ( !entry ) {
				return;
			}

			applying = true;
			var topItem = entry.item.closest( '.citizen-toc-level-1' );

			entries.forEach( function ( candidate ) {
				var isActive = candidate.item === entry.item;
				candidate.item.classList.toggle( ACTIVE_CLASS, isActive );
				candidate.item.classList.toggle(
					ACTIVE_TOP_CLASS,
					candidate.item === topItem
				);
				if ( isActive ) {
					candidate.link.setAttribute( 'aria-current', 'location' );
				} else {
					candidate.link.removeAttribute( 'aria-current' );
				}
			} );

			if ( topItem && !entries.some( function ( candidate ) {
				return candidate.item === topItem;
			} ) ) {
				topItem.classList.add( ACTIVE_TOP_CLASS );
			}
			updateIndicator( entry );

			applying = false;
		}

		function update() {
			frameId = 0;
			applyEntry( manualEntry || findEntryForScroll() );
		}

		function scheduleUpdate() {
			if ( frameId === 0 ) {
				frameId = window.requestAnimationFrame( update );
			}
		}

		function releaseManualEntry() {
			if ( manualEntry ) {
				manualEntry = null;
				scheduleUpdate();
			}
		}

		toc.addEventListener( 'click', function ( event ) {
			if (
				event.defaultPrevented ||
				event.button !== 0 ||
				event.altKey ||
				event.ctrlKey ||
				event.metaKey ||
				event.shiftKey
			) {
				return;
			}

			var link = event.target.closest( LINK_SELECTOR );
			if ( !link || !toc.contains( link ) ) {
				return;
			}

			var entry = entries.find( function ( candidate ) {
				return candidate.link === link;
			} );
			if ( !entry ) {
				return;
			}

			event.preventDefault();
			event.stopImmediatePropagation();
			manualEntry = entry;
			applyEntry( entry );

			var hash = link.getAttribute( 'href' );
			if ( hash ) {
				window.history.pushState( null, '', hash );
			}

			window.requestAnimationFrame( function () {
				var targetTop = window.scrollY +
					entry.target.getBoundingClientRect().top -
					getActivationTop();
				window.scrollTo( 0, Math.max( 0, targetTop ) );
			} );
		}, true );

		window.addEventListener( 'scroll', scheduleUpdate, { passive: true } );
		window.addEventListener( 'resize', scheduleUpdate );
		window.addEventListener( 'wheel', releaseManualEntry, { passive: true } );
		window.addEventListener( 'touchstart', releaseManualEntry, { passive: true } );
		window.addEventListener( 'pointerdown', function ( event ) {
			if ( !toc.contains( event.target ) ) {
				releaseManualEntry();
			}
		}, true );
		window.addEventListener( 'keydown', function ( event ) {
			if ( NAVIGATION_KEYS.indexOf( event.key ) !== -1 ) {
				releaseManualEntry();
			}
		} );
		window.addEventListener( 'hashchange', function () {
			manualEntry = findEntryForHash();
			scheduleUpdate();
		} );

		var classObserver = new MutationObserver( function () {
			if ( !applying ) {
				scheduleUpdate();
			}
		} );
		classObserver.observe( toc, {
			attributes: true,
			attributeFilter: [ 'class' ],
			subtree: true
		} );

		scheduleUpdate();
	}

	mw.loader.using( 'skins.citizen.scripts' ).then( function () {
		window.requestAnimationFrame( function () {
			setupHomeCarousel();
			setupSingleActiveToc();
		} );
	} );
}() );