미디어위키:Common.js: 두 판 사이의 차이

편집 요약 없음
태그: 되돌려진 기여
편집 요약 없음
태그: 되돌려진 기여
1번째 줄: 1번째 줄:
/**
/**
  * MediaWiki 커스텀 펼치기/접기 애니메이션
  * MediaWiki 커스텀 펼치기/접기 슬라이드 애니메이션
  *
  *
  * 틀 구조를 수정하지 않고 작동합니다.
  * 지원 구조:
  *
  *
  * 구조:
  * 1)
*
  * <div id="mw-customcollapsible-XXX">
  * <div id="mw-customcollapsible-XXX" class="mw-collapsible">
  *    <div class="mw-customtoggle-XXX mw-customtoggle">버튼</div>
*
  *     <table>내용</table>
  *    <div class="mw-customtoggle-XXX mw-customtoggle">
  * </div>
  *         [ 펼치기 / 접기 ]
  *     </div>
*
*    실제 내용...
  *
  *
* 2)
* <div id="mw-customcollapsible-XXX">
*    <div class="mw-customtoggle-XXX mw-customtoggle">버튼</div>
  * </div>
  * </div>
* <table>내용</table>
  *
  *
  * 토글 버튼은 항상 남겨두고
  * 둘 다 자동 처리
* 토글 버튼 뒤의 내용만 애니메이션합니다.
  */
  */


25번째 줄: 24번째 줄:
var DURATION = 300;
var DURATION = 300;


var BODY_CLASS = 'mw-customcollapse-animation-body';
var READY_CLASS = 'custom-collapse-ready';
var READY_CLASS = 'mw-customcollapse-animation-ready';
var OPEN_CLASS = 'custom-collapse-open';
var OPEN_CLASS = 'mw-customcollapse-animation-open';
var ANIMATING_CLASS = 'custom-collapse-animating';
var ANIMATING_CLASS = 'mw-customcollapse-animation-running';
var CONTENT_CLASS = 'custom-collapse-content';




/**
/* ==============================
* 토글 이름 추출
  toggle 이름 추출
*
  ============================== */
* mw-customtoggle-aurann_01
 
* →
* aurann_01
*/
function getToggleName( toggle ) {
function getToggleName( toggle ) {
var prefix = 'mw-customtoggle-';
var prefix = 'mw-customtoggle-';
55번째 줄: 51번째 줄:




/**
/* ==============================
* 토글과 연결된 부모 찾기
  연결된 collapsible 찾기
*/
  ============================== */
 
function getTarget( toggle ) {
function getTarget( toggle ) {
var name = getToggleName( toggle );
var name = getToggleName( toggle );
65번째 줄: 62번째 줄:
}
}


/*
return document.getElementById(
* 현재 구조에서는 toggle 자체가
'mw-customcollapsible-' + name
* collapsible 안에 있으므로 우선 closest 사용
*/
var target = toggle.closest(
'.mw-collapsible'
);
);
}
/* ==============================
  다음 custom collapsible인지 검사
  ============================== */


if (
function isNextCollapsible( element ) {
target &&
if ( !element || !element.id ) {
target.id ===
return false;
'mw-customcollapsible-' + name
) {
return target;
}
}


/*
return (
* 혹시 구조가 다를 경우 ID로 검색
element.id.indexOf(
*/
'mw-customcollapsible-'
return document.getElementById(
) === 0
'mw-customcollapsible-' + name
);
);
}
}




/**
/* ==============================
* 토글 버튼 이후의 모든 내용을
  실제 내용 wrapper 만들기
* 애니메이션용 DIV로 감싼다.
  ============================== */
*
 
* 틀 원본 HTML은 수정할 필요 없음.
function createContentWrapper( target, toggle ) {
*/
 
function createAnimationBody( target, toggle ) {
/*
var existing = target.querySelector(
* 이미 만들었으면 재사용
':scope > .' + BODY_CLASS
*/
);
var existing =
target.querySelector(
':scope > .' + CONTENT_CLASS
);


if ( existing ) {
if ( existing ) {
105번째 줄: 103번째 줄:
}
}


var body = document.createElement( 'div' );


body.className = BODY_CLASS;
/* --------------------------------
  경우 1:
  내용이 target 내부에 있음
  -------------------------------- */


/*
var internalNodes = [];
* toggle 다음의 모든 노드를 body 안으로 이동
*
* table뿐만 아니라 div, 텍스트 등도 지원
*/
var node = toggle.nextSibling;
var node = toggle.nextSibling;
var nodes = [];


while ( node ) {
while ( node ) {
nodes.push( node );
internalNodes.push( node );
node = node.nextSibling;
node = node.nextSibling;
}
}


nodes.forEach( function ( child ) {
if ( internalNodes.length ) {
body.appendChild( child );
 
} );
var internalWrapper =
document.createElement( 'div' );
 
internalWrapper.className =
CONTENT_CLASS;
 
internalNodes.forEach(
function ( child ) {
internalWrapper.appendChild(
child
);
}
);
 
target.appendChild(
internalWrapper
);


target.appendChild( body );
return internalWrapper;
}


return body;
}


/* --------------------------------
  경우 2:
  내용이 target 다음 형제에 있음
  -------------------------------- */
var parent = target.parentNode;


/**
if ( !parent ) {
* 초기 상태 확인
return null;
*/
function getInitialOpenState( target, toggle, body ) {
/*
* MediaWiki가 mw-collapsed를 붙였다면 접힌 상태
*/
if ( target.classList.contains( 'mw-collapsed' ) ) {
return false;
}
}


/*
var externalWrapper =
* body가 display:none이면 접힌 상태
document.createElement( 'div' );
*/
 
if (
externalWrapper.className =
window.getComputedStyle( body ).display === 'none'
CONTENT_CLASS;
) {
 
return false;
var sibling =
target.nextSibling;
 
var foundContent = false;
 
while ( sibling ) {
 
var nextSibling =
sibling.nextSibling;
 
/*
* 다음 customcollapsible을 만나면
* 현재 영역 종료
*/
if (
sibling.nodeType === 1 &&
isNextCollapsible( sibling )
) {
break;
}
 
/*
* 공백 텍스트는 무시
*/
if (
sibling.nodeType === 3 &&
!sibling.textContent.trim()
) {
sibling = nextSibling;
continue;
}
 
/*
* script / style은 건드리지 않음
*/
if (
sibling.nodeType === 1 &&
(
sibling.tagName === 'SCRIPT' ||
sibling.tagName === 'STYLE'
)
) {
break;
}
 
externalWrapper.appendChild(
sibling
);
 
foundContent = true;
 
sibling = nextSibling;
}
}


/*
if ( !foundContent ) {
* aria-expanded가 명시적으로 false
return null;
*/
if (
toggle.getAttribute( 'aria-expanded' ) ===
'false'
) {
return false;
}
}


return true;
parent.insertBefore(
externalWrapper,
sibling
);
 
return externalWrapper;
}
 
 
/* ==============================
  즉시 펼쳐진 상태 설정
  ============================== */
 
function setOpenImmediately(
target,
toggle,
content
) {
target.classList.remove(
'mw-collapsed'
);
 
target.classList.add(
OPEN_CLASS
);
 
content.style.display = '';
content.style.height = '';
content.style.overflow = '';
 
toggle.setAttribute(
'aria-expanded',
'true'
);
}
 
 
/* ==============================
  즉시 접힌 상태 설정
  ============================== */
 
function setClosedImmediately(
target,
toggle,
content
) {
target.classList.add(
'mw-collapsed'
);
 
target.classList.remove(
OPEN_CLASS
);
 
content.style.display = 'none';
content.style.height = '';
content.style.overflow = '';
 
toggle.setAttribute(
'aria-expanded',
'false'
);
}
}




/**
/* ==============================
* 펼치기
  펼치기 애니메이션
*/
  ============================== */
function openTarget( target, toggle, body ) {
 
function openContent(
target,
toggle,
content
) {
if (
if (
target.classList.contains(
content.classList.contains(
ANIMATING_CLASS
ANIMATING_CLASS
)
)
178번째 줄: 298번째 줄:
}
}


target.classList.add(
content.classList.add(
ANIMATING_CLASS
ANIMATING_CLASS
);
);
186번째 줄: 306번째 줄:
);
);


body.style.display = 'block';
content.style.display = 'block';
body.style.overflow = 'hidden';
content.style.overflow = 'hidden';
content.style.height = '0px';


/*
/*
* 실제 최종 높이 계산
* 브라우저가 0px 상태를 먼저
* 그리도록 한 프레임 기다림
*/
*/
body.style.height = 'auto';
window.requestAnimationFrame(
function () {


var endHeight = body.scrollHeight;
var endHeight =
 
content.scrollHeight;
/*
* 0부터 시작
*/
body.style.height = '0px';


/*
content.style.transition =
* 강제 reflow
'height ' +
*/
DURATION +
body.offsetHeight;
'ms ease';


var animation = body.animate(
/*
[
* 한 프레임을 더 나누어
{
* height transition이 확실하게 실행되게 함
height: '0px'
*/
},
window.requestAnimationFrame(
{
function () {
height: endHeight + 'px'
content.style.height =
}
endHeight + 'px';
],
}
{
);
duration: DURATION,
easing: 'ease-out',
fill: 'forwards'
}
}
);
);


animation.onfinish = function () {
window.setTimeout(
target.classList.remove(
function () {
ANIMATING_CLASS
 
);
content.classList.remove(
ANIMATING_CLASS
);


target.classList.add(
target.classList.add(
OPEN_CLASS
OPEN_CLASS
);
);


body.style.height = '';
content.style.transition = '';
body.style.overflow = '';
content.style.height = '';
body.style.display = '';
content.style.overflow = '';
content.style.display = '';


toggle.setAttribute(
toggle.setAttribute(
'aria-expanded',
'aria-expanded',
'true'
'true'
);
);


animation.cancel();
},
};
DURATION + 40
);
}
}




/**
/* ==============================
* 접기
  접기 애니메이션
*/
  ============================== */
function closeTarget( target, toggle, body ) {
 
function closeContent(
target,
toggle,
content
) {
if (
if (
target.classList.contains(
content.classList.contains(
ANIMATING_CLASS
ANIMATING_CLASS
)
)
257번째 줄: 382번째 줄:
}
}


target.classList.add(
content.classList.add(
ANIMATING_CLASS
ANIMATING_CLASS
);
);


var startHeight =
var startHeight =
body.getBoundingClientRect().height;
content.getBoundingClientRect()
.height;


/*
* 이미 접혀있는 경우
*/
if ( startHeight <= 0 ) {
if ( startHeight <= 0 ) {
target.classList.remove(
content.classList.remove(
ANIMATING_CLASS
ANIMATING_CLASS
);
setClosedImmediately(
target,
toggle,
content
);
);


275번째 줄: 404번째 줄:
}
}


body.style.height =
content.style.overflow = 'hidden';
content.style.height =
startHeight + 'px';
startHeight + 'px';


body.style.overflow =
content.style.transition =
'hidden';
'height ' +
DURATION +
'ms ease';


/*
/*
* 강제 reflow
* 현재 높이를 브라우저에 먼저 반영
*/
*/
body.offsetHeight;
content.offsetHeight;


var animation = body.animate(
window.requestAnimationFrame(
[
function () {
{
content.style.height =
height:
'0px';
startHeight + 'px'
},
{
height: '0px'
}
],
{
duration: DURATION,
easing: 'ease-in',
fill: 'forwards'
}
}
);
);


animation.onfinish = function () {
window.setTimeout(
target.classList.remove(
function () {
ANIMATING_CLASS
 
);
content.classList.remove(
ANIMATING_CLASS
);
 
target.classList.remove(
OPEN_CLASS
);
 
target.classList.add(
'mw-collapsed'
);


target.classList.remove(
content.style.display =
OPEN_CLASS
'none';
);


target.classList.add(
content.style.transition =
'mw-collapsed'
'';
);


body.style.display = 'none';
content.style.height = '';
body.style.height = '';
content.style.overflow = '';
body.style.overflow = '';


toggle.setAttribute(
toggle.setAttribute(
'aria-expanded',
'aria-expanded',
'false'
'false'
);
);


animation.cancel();
},
};
DURATION + 40
);
}
}




/**
/* ==============================
* 실제 클릭 처리
  토글 실행
*/
  ============================== */
function toggleTarget( toggle ) {
 
var target = getTarget( toggle );
function toggleContent( toggle ) {
var target =
getTarget( toggle );


if ( !target ) {
if ( !target ) {
return false;
return;
}
}


var body = target.querySelector(
var content =
':scope > .' + BODY_CLASS
target._customCollapseContent;
);
 
if ( !content ) {
content =
createContentWrapper(
target,
toggle
);


if ( !body ) {
target._customCollapseContent =
body = createAnimationBody(
content;
target,
toggle
);
}
}


if ( !body ) {
if ( !content ) {
return false;
return;
}
}


358번째 줄: 493번째 줄:
toggle.getAttribute(
toggle.getAttribute(
'aria-expanded'
'aria-expanded'
) === 'true' &&
) !== 'false' &&
!target.classList.contains(
!target.classList.contains(
'mw-collapsed'
'mw-collapsed'
364번째 줄: 499번째 줄:


if ( isOpen ) {
if ( isOpen ) {
closeTarget(
closeContent(
target,
target,
toggle,
toggle,
body
content
);
);
} else {
} else {
openTarget(
openContent(
target,
target,
toggle,
toggle,
body
content
);
);
}
}
}


return true;
}


/* ==============================
  초기화
  ============================== */


/**
function initializeToggle( toggle ) {
* 각 custom collapsible 초기화
var target =
*/
getTarget( toggle );
function initializeTarget( toggle ) {
var target = getTarget( toggle );


if ( !target ) {
if ( !target ) {
399번째 줄: 534번째 줄:
}
}


var body = createAnimationBody(
var content =
target,
createContentWrapper(
toggle
target,
);
toggle
);


if ( !body ) {
if ( !content ) {
return;
return;
}
}
target._customCollapseContent =
content;


target.classList.add(
target.classList.add(
412번째 줄: 551번째 줄:
);
);


var isOpen = getInitialOpenState(
/*
target,
* 최초 상태 판별
toggle,
*/
body
var initiallyClosed =
);
target.classList.contains(
'mw-collapsed'
) ||
toggle.getAttribute(
'aria-expanded'
) === 'false';


if ( isOpen ) {
if ( initiallyClosed ) {
target.classList.add(
OPEN_CLASS
);


target.classList.remove(
setClosedImmediately(
'mw-collapsed'
target,
toggle,
content
);
);


body.style.display = '';
toggle.setAttribute(
'aria-expanded',
'true'
);
} else {
} else {
target.classList.remove(
OPEN_CLASS
);


target.classList.add(
setOpenImmediately(
'mw-collapsed'
target,
toggle,
content
);
);


body.style.display = 'none';
toggle.setAttribute(
'aria-expanded',
'false'
);
}
}
}
}




/**
* 페이지 전체 초기화
*/
function initializeAll() {
function initializeAll() {
var toggles = document.querySelectorAll(
var toggles =
'.mw-customtoggle'
document.querySelectorAll(
);
'.mw-customtoggle'
);


toggles.forEach(
toggles.forEach(
function ( toggle ) {
function ( toggle ) {
if ( getToggleName( toggle ) ) {
 
initializeTarget(
if (
getToggleName(
toggle
)
) {
initializeToggle(
toggle
toggle
);
);
}
}
}
}
);
);
472번째 줄: 606번째 줄:




/**
/* ==============================
* 클릭
  클릭 이벤트
*
  ============================== */
* MediaWiki 기본 토글보다 먼저 실행해서
 
* 기본 display:none 처리 방지.
*/
document.addEventListener(
document.addEventListener(
'click',
'click',
function ( event ) {
function ( event ) {
var toggle =
var toggle =
event.target.closest(
event.target.closest(
491번째 줄: 624번째 줄:


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


/*
* MediaWiki 기본 즉시 토글 차단
*/
event.preventDefault();
event.preventDefault();
event.stopPropagation();
event.stopPropagation();
event.stopImmediatePropagation();
event.stopImmediatePropagation();


toggleTarget(
toggleContent(
toggle
toggle
);
);
},
},
true
true
508번째 줄: 647번째 줄:




/**
/* ==============================
* Enter / Space
  키보드
*/
  ============================== */
 
document.addEventListener(
document.addEventListener(
'keydown',
'keydown',
function ( event ) {
function ( event ) {
if (
if (
event.key !== 'Enter' &&
event.key !== 'Enter' &&
531번째 줄: 672번째 줄:


if (
if (
!getToggleName( toggle )
!getToggleName(
toggle
)
) {
) {
return;
return;
540번째 줄: 683번째 줄:
event.stopImmediatePropagation();
event.stopImmediatePropagation();


toggleTarget(
toggleContent(
toggle
toggle
);
);
},
},
true
true
548번째 줄: 692번째 줄:




/**
/* ==============================
* 초기 실행
  페이지 로드
*/
  ============================== */
 
if (
if (
document.readyState ===
document.readyState ===
'loading'
'loading'
) {
) {
document.addEventListener(
document.addEventListener(
'DOMContentLoaded',
'DOMContentLoaded',
initializeAll
initializeAll
);
);
} else {
} else {
initializeAll();
initializeAll();
}
}




/**
/*
* MediaWiki AJAX/동적 콘텐츠 대응
* MediaWiki 동적 콘텐츠 대응
*/
*/
if (
if (
571번째 줄: 720번째 줄:
mw.hook
mw.hook
) {
) {
mw.hook(
mw.hook(
'wikipage.content'
'wikipage.content'
578번째 줄: 728번째 줄:
}
}
);
);
}
}


}() );
}() );

2026년 8월 20일 (목) 13:24 판

/**
 * MediaWiki 커스텀 펼치기/접기 슬라이드 애니메이션
 *
 * 지원 구조:
 *
 * 1)
 * <div id="mw-customcollapsible-XXX">
 *     <div class="mw-customtoggle-XXX mw-customtoggle">버튼</div>
 *     <table>내용</table>
 * </div>
 *
 * 2)
 * <div id="mw-customcollapsible-XXX">
 *     <div class="mw-customtoggle-XXX mw-customtoggle">버튼</div>
 * </div>
 * <table>내용</table>
 *
 * 둘 다 자동 처리
 */

( function () {
	'use strict';

	var DURATION = 300;

	var READY_CLASS = 'custom-collapse-ready';
	var OPEN_CLASS = 'custom-collapse-open';
	var ANIMATING_CLASS = 'custom-collapse-animating';
	var CONTENT_CLASS = 'custom-collapse-content';


	/* ==============================
	   toggle 이름 추출
	   ============================== */

	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;
	}


	/* ==============================
	   연결된 collapsible 찾기
	   ============================== */

	function getTarget( toggle ) {
		var name = getToggleName( toggle );

		if ( !name ) {
			return null;
		}

		return document.getElementById(
			'mw-customcollapsible-' + name
		);
	}


	/* ==============================
	   다음 custom collapsible인지 검사
	   ============================== */

	function isNextCollapsible( element ) {
		if ( !element || !element.id ) {
			return false;
		}

		return (
			element.id.indexOf(
				'mw-customcollapsible-'
			) === 0
		);
	}


	/* ==============================
	   실제 내용 wrapper 만들기
	   ============================== */

	function createContentWrapper( target, toggle ) {

		/*
		 * 이미 만들었으면 재사용
		 */
		var existing =
			target.querySelector(
				':scope > .' + CONTENT_CLASS
			);

		if ( existing ) {
			return existing;
		}


		/* --------------------------------
		   경우 1:
		   내용이 target 내부에 있음
		   -------------------------------- */

		var internalNodes = [];
		var node = toggle.nextSibling;

		while ( node ) {
			internalNodes.push( node );
			node = node.nextSibling;
		}

		if ( internalNodes.length ) {

			var internalWrapper =
				document.createElement( 'div' );

			internalWrapper.className =
				CONTENT_CLASS;

			internalNodes.forEach(
				function ( child ) {
					internalWrapper.appendChild(
						child
					);
				}
			);

			target.appendChild(
				internalWrapper
			);

			return internalWrapper;
		}


		/* --------------------------------
		   경우 2:
		   내용이 target 다음 형제에 있음
		   -------------------------------- */

		var parent = target.parentNode;

		if ( !parent ) {
			return null;
		}

		var externalWrapper =
			document.createElement( 'div' );

		externalWrapper.className =
			CONTENT_CLASS;

		var sibling =
			target.nextSibling;

		var foundContent = false;

		while ( sibling ) {

			var nextSibling =
				sibling.nextSibling;

			/*
			 * 다음 customcollapsible을 만나면
			 * 현재 영역 종료
			 */
			if (
				sibling.nodeType === 1 &&
				isNextCollapsible( sibling )
			) {
				break;
			}

			/*
			 * 공백 텍스트는 무시
			 */
			if (
				sibling.nodeType === 3 &&
				!sibling.textContent.trim()
			) {
				sibling = nextSibling;
				continue;
			}

			/*
			 * script / style은 건드리지 않음
			 */
			if (
				sibling.nodeType === 1 &&
				(
					sibling.tagName === 'SCRIPT' ||
					sibling.tagName === 'STYLE'
				)
			) {
				break;
			}

			externalWrapper.appendChild(
				sibling
			);

			foundContent = true;

			sibling = nextSibling;
		}

		if ( !foundContent ) {
			return null;
		}

		parent.insertBefore(
			externalWrapper,
			sibling
		);

		return externalWrapper;
	}


	/* ==============================
	   즉시 펼쳐진 상태 설정
	   ============================== */

	function setOpenImmediately(
		target,
		toggle,
		content
	) {
		target.classList.remove(
			'mw-collapsed'
		);

		target.classList.add(
			OPEN_CLASS
		);

		content.style.display = '';
		content.style.height = '';
		content.style.overflow = '';

		toggle.setAttribute(
			'aria-expanded',
			'true'
		);
	}


	/* ==============================
	   즉시 접힌 상태 설정
	   ============================== */

	function setClosedImmediately(
		target,
		toggle,
		content
	) {
		target.classList.add(
			'mw-collapsed'
		);

		target.classList.remove(
			OPEN_CLASS
		);

		content.style.display = 'none';
		content.style.height = '';
		content.style.overflow = '';

		toggle.setAttribute(
			'aria-expanded',
			'false'
		);
	}


	/* ==============================
	   펼치기 애니메이션
	   ============================== */

	function openContent(
		target,
		toggle,
		content
	) {
		if (
			content.classList.contains(
				ANIMATING_CLASS
			)
		) {
			return;
		}

		content.classList.add(
			ANIMATING_CLASS
		);

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

		content.style.display = 'block';
		content.style.overflow = 'hidden';
		content.style.height = '0px';

		/*
		 * 브라우저가 0px 상태를 먼저
		 * 그리도록 한 프레임 기다림
		 */
		window.requestAnimationFrame(
			function () {

				var endHeight =
					content.scrollHeight;

				content.style.transition =
					'height ' +
					DURATION +
					'ms ease';

				/*
				 * 한 프레임을 더 나누어
				 * height transition이 확실하게 실행되게 함
				 */
				window.requestAnimationFrame(
					function () {
						content.style.height =
							endHeight + 'px';
					}
				);
			}
		);

		window.setTimeout(
			function () {

				content.classList.remove(
					ANIMATING_CLASS
				);

				target.classList.add(
					OPEN_CLASS
				);

				content.style.transition = '';
				content.style.height = '';
				content.style.overflow = '';
				content.style.display = '';

				toggle.setAttribute(
					'aria-expanded',
					'true'
				);

			},
			DURATION + 40
		);
	}


	/* ==============================
	   접기 애니메이션
	   ============================== */

	function closeContent(
		target,
		toggle,
		content
	) {
		if (
			content.classList.contains(
				ANIMATING_CLASS
			)
		) {
			return;
		}

		content.classList.add(
			ANIMATING_CLASS
		);

		var startHeight =
			content.getBoundingClientRect()
				.height;

		if ( startHeight <= 0 ) {
			content.classList.remove(
				ANIMATING_CLASS
			);

			setClosedImmediately(
				target,
				toggle,
				content
			);

			return;
		}

		content.style.overflow = 'hidden';
		content.style.height =
			startHeight + 'px';

		content.style.transition =
			'height ' +
			DURATION +
			'ms ease';

		/*
		 * 현재 높이를 브라우저에 먼저 반영
		 */
		content.offsetHeight;

		window.requestAnimationFrame(
			function () {
				content.style.height =
					'0px';
			}
		);

		window.setTimeout(
			function () {

				content.classList.remove(
					ANIMATING_CLASS
				);

				target.classList.remove(
					OPEN_CLASS
				);

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

				content.style.display =
					'none';

				content.style.transition =
					'';

				content.style.height = '';
				content.style.overflow = '';

				toggle.setAttribute(
					'aria-expanded',
					'false'
				);

			},
			DURATION + 40
		);
	}


	/* ==============================
	   토글 실행
	   ============================== */

	function toggleContent( toggle ) {
		var target =
			getTarget( toggle );

		if ( !target ) {
			return;
		}

		var content =
			target._customCollapseContent;

		if ( !content ) {
			content =
				createContentWrapper(
					target,
					toggle
				);

			target._customCollapseContent =
				content;
		}

		if ( !content ) {
			return;
		}

		var isOpen =
			toggle.getAttribute(
				'aria-expanded'
			) !== 'false' &&
			!target.classList.contains(
				'mw-collapsed'
			);

		if ( isOpen ) {
			closeContent(
				target,
				toggle,
				content
			);
		} else {
			openContent(
				target,
				toggle,
				content
			);
		}
	}


	/* ==============================
	   초기화
	   ============================== */

	function initializeToggle( toggle ) {
		var target =
			getTarget( toggle );

		if ( !target ) {
			return;
		}

		if (
			target.classList.contains(
				READY_CLASS
			)
		) {
			return;
		}

		var content =
			createContentWrapper(
				target,
				toggle
			);

		if ( !content ) {
			return;
		}

		target._customCollapseContent =
			content;

		target.classList.add(
			READY_CLASS
		);

		/*
		 * 최초 상태 판별
		 */
		var initiallyClosed =
			target.classList.contains(
				'mw-collapsed'
			) ||
			toggle.getAttribute(
				'aria-expanded'
			) === 'false';

		if ( initiallyClosed ) {

			setClosedImmediately(
				target,
				toggle,
				content
			);

		} else {

			setOpenImmediately(
				target,
				toggle,
				content
			);

		}
	}


	function initializeAll() {
		var toggles =
			document.querySelectorAll(
				'.mw-customtoggle'
			);

		toggles.forEach(
			function ( toggle ) {

				if (
					getToggleName(
						toggle
					)
				) {
					initializeToggle(
						toggle
					);
				}

			}
		);
	}


	/* ==============================
	   클릭 이벤트
	   ============================== */

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

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

			if ( !toggle ) {
				return;
			}

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

			/*
			 * MediaWiki 기본 즉시 토글 차단
			 */
			event.preventDefault();
			event.stopPropagation();
			event.stopImmediatePropagation();

			toggleContent(
				toggle
			);

		},
		true
	);


	/* ==============================
	   키보드
	   ============================== */

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

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

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

			if ( !toggle ) {
				return;
			}

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

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

			toggleContent(
				toggle
			);

		},
		true
	);


	/* ==============================
	   페이지 로드
	   ============================== */

	if (
		document.readyState ===
		'loading'
	) {

		document.addEventListener(
			'DOMContentLoaded',
			initializeAll
		);

	} else {

		initializeAll();

	}


	/*
	 * MediaWiki 동적 콘텐츠 대응
	 */
	if (
		window.mw &&
		mw.hook
	) {

		mw.hook(
			'wikipage.content'
		).add(
			function () {
				initializeAll();
			}
		);

	}

}() );