미디어위키:Common.js

신사 (토론 | 기여)님의 2026년 8월 20일 (목) 14:24 판

참고: 설정을 저장한 후에 바뀐 점을 확인하기 위해서는 브라우저의 캐시를 새로 고쳐야 합니다.

  • 파이어폭스 / 사파리: Shift 키를 누르면서 새로 고침을 클릭하거나, Ctrl-F5 또는 Ctrl-R을 입력 (Mac에서는 ⌘-R)
  • 구글 크롬: Ctrl-Shift-R키를 입력 (Mac에서는 ⌘-Shift-R)
  • 인터넷 익스플로러 / 엣지: Ctrl 키를 누르면서 새로 고침을 클릭하거나, Ctrl-F5를 입력.
  • 오페라: Ctrl-F5를 입력.
( function ( $, mw ) {
	'use strict';

	var DURATION = 180;
	var EASING = 'cubic-bezier(0.4, 0, 0.2, 1)';

	var VIEWPORT_CLASS = 'mw-collapse-viewport';
	var CONTENT_CLASS = 'mw-collapse-content';


	function getToggleName( toggle ) {
		var prefix = 'mw-customtoggle-';
		var classes = toggle.classList;
		var i;

		for ( i = 0; i < classes.length; i++ ) {
			if ( classes[ i ].indexOf( prefix ) === 0 ) {
				return classes[ i ].substring( prefix.length );
			}
		}

		return null;
	}


	function escapeAttribute( value ) {
		return String( value )
			.replace( /\\/g, '\\\\' )
			.replace( /"/g, '\\"' );
	}


	function getTargets( name ) {
		var id = 'mw-customcollapsible-' + name;

		return document.querySelectorAll(
			'[id="' + escapeAttribute( id ) + '"]'
		);
	}


	function isCollapsed( target ) {
		return (
			target.classList.contains( 'mw-collapsed' ) ||
			window.getComputedStyle( target ).display === 'none'
		);
	}


	/**
	 * target 내부에 viewport/content 구조 생성
	 *
	 * target
	 * └─ viewport
	 *    └─ content
	 *       └─ 기존 내용 전체
	 */
	function ensureViewport( target ) {

		var directChildren = target.children;
		var i;

		for ( i = 0; i < directChildren.length; i++ ) {
			if (
				directChildren[ i ].classList &&
				directChildren[ i ].classList.contains(
					VIEWPORT_CLASS
				)
			) {
				return directChildren[ i ];
			}
		}


		var viewport = document.createElement( 'div' );
		var content = document.createElement( 'div' );

		viewport.className = VIEWPORT_CLASS;
		content.className = CONTENT_CLASS;


		/*
		 * 중요:
		 * 기존 내용 전체를 content로 이동
		 */
		while ( target.firstChild ) {
			content.appendChild(
				target.firstChild
			);
		}


		viewport.appendChild( content );
		target.appendChild( viewport );


		viewport.style.overflow = 'hidden';


		return viewport;
	}


	function getContent( viewport ) {
		var children = viewport.children;
		var i;

		for ( i = 0; i < children.length; i++ ) {
			if (
				children[ i ].classList &&
				children[ i ].classList.contains(
					CONTENT_CLASS
				)
			) {
				return children[ i ];
			}
		}

		return null;
	}


	function getNaturalDisplay( target ) {

		if (
			target.dataset.mwCollapseDisplay
		) {
			return target.dataset.mwCollapseDisplay;
		}


		var style =
			window.getComputedStyle( target );


		if (
			style.display &&
			style.display !== 'none'
		) {
			target.dataset.mwCollapseDisplay =
				style.display;

			return style.display;
		}


		if (
			target.style.width === '100%'
		) {
			return 'block';
		}


		return 'block';
	}


	function rememberDisplay( target ) {

		var display =
			window.getComputedStyle(
				target
			).display;


		if (
			display &&
			display !== 'none'
		) {
			target.dataset.mwCollapseDisplay =
				display;
		}
	}


	function expand( target ) {

		var viewport =
			ensureViewport(
				target
			);

		var content =
			getContent(
				viewport
			);


		if ( !content ) {
			return;
		}


		/*
		 * target 바깥쪽만 표시
		 */
		target.style.display =
			getNaturalDisplay(
				target
			);

		target.classList.remove(
			'mw-collapsed'
		);


		/*
		 * content 자체는 항상 auto height.
		 *
		 * 내부 table에는 height를 절대 설정하지 않음.
		 */
		content.style.height = 'auto';


		viewport.style.height = 'auto';
		viewport.style.overflow = 'hidden';


		var finalHeight =
			content.getBoundingClientRect()
				.height;


		viewport.style.height = '0px';


		void viewport.offsetHeight;


		var animation =
			viewport.animate(
				[
					{
						height: '0px'
					},
					{
						height:
							finalHeight +
							'px'
					}
				],
				{
					duration:
						DURATION,

					easing:
						EASING,

					fill:
						'forwards'
				}
			);


		animation.onfinish =
			function () {

				viewport.style.height =
					'';

				viewport.style.overflow =
					'';

				animation.cancel();
			};
	}


	function collapse( target ) {

		rememberDisplay(
			target
		);


		var viewport =
			ensureViewport(
				target
			);

		var content =
			getContent(
				viewport
			);


		if ( !content ) {
			return;
		}


		/*
		 * 내부 table은 그대로 둠.
		 * viewport 높이만 줄임.
		 */
		var startHeight =
			content.getBoundingClientRect()
				.height;


		viewport.style.height =
			startHeight +
			'px';

		viewport.style.overflow =
			'hidden';


		void viewport.offsetHeight;


		var animation =
			viewport.animate(
				[
					{
						height:
							startHeight +
							'px'
					},
					{
						height:
							'0px'
					}
				],
				{
					duration:
						DURATION,

					easing:
						EASING,

					fill:
						'forwards'
				}
			);


		animation.onfinish =
			function () {

				target.classList.add(
					'mw-collapsed'
				);

				target.style.display =
					'none';


				viewport.style.height =
					'';

				viewport.style.overflow =
					'';


				animation.cancel();
			};
	}


	function toggleGroup( toggle ) {

		var name =
			getToggleName(
				toggle
			);


		if ( !name ) {
			return;
		}


		var targets =
			getTargets(
				name
			);


		var i;


		for (
			i = 0;
			i < targets.length;
			i++
		) {

			var target =
				targets[ i ];


			if (
				isCollapsed(
					target
				)
			) {
				expand(
					target
				);

			} else {
				collapse(
					target
				);
			}
		}
	}


	document.addEventListener(
		'click',
		function ( event ) {

			var toggle =
				event.target.closest(
					'.mw-customtoggle'
				);


			if ( !toggle ) {
				return;
			}


			if (
				!getToggleName(
					toggle
				)
			) {
				return;
			}


			event.preventDefault();
			event.stopPropagation();
			event.stopImmediatePropagation();


			toggleGroup(
				toggle
			);
		},
		true
	);


	document.addEventListener(
		'keydown',
		function ( event ) {

			if (
				event.key !== 'Enter' &&
				event.key !== ' '
			) {
				return;
			}


			var toggle =
				event.target.closest(
					'.mw-customtoggle'
				);


			if ( !toggle ) {
				return;
			}


			event.preventDefault();
			event.stopPropagation();
			event.stopImmediatePropagation();


			toggleGroup(
				toggle
			);
		},
		true
	);


}( jQuery, mediaWiki ) );