참고: 설정을 저장한 후에 바뀐 점을 확인하기 위해서는 브라우저의 캐시를 새로 고쳐야 합니다.
- 파이어폭스 / 사파리: Shift 키를 누르면서 새로 고침을 클릭하거나, Ctrl-F5 또는 Ctrl-R을 입력 (Mac에서는 ⌘-R)
- 구글 크롬: Ctrl-Shift-R키를 입력 (Mac에서는 ⌘-Shift-R)
- 인터넷 익스플로러 / 엣지: Ctrl 키를 누르면서 새로 고침을 클릭하거나, Ctrl-F5를 입력.
- 오페라: Ctrl-F5를 입력.
/**
* MediaWiki 커스텀 펼치기/접기 슬라이드 애니메이션
*
* 같은 mw-customcollapsible-XXX ID가 2개 존재하는 틀 지원
*
* 예:
*
* [첫 번째]
* <div id="mw-customcollapsible-XXX">
* <div class="mw-customtoggle-XXX">[ 버튼 ]</div>
* </div>
*
* [두 번째]
* <div id="mw-customcollapsible-XXX" class="mw-collapsed">
* <div class="mw-customtoggle-XXX">[ 버튼 ]</div>
* <table>내용</table>
* </div>
*
* 틀 원본 수정 필요 없음.
*/
( function () {
'use strict';
var DURATION = 350;
var INITIALIZED = 'data-slide-collapse-ready';
var CONTENT_CLASS = 'mw-slide-collapse-content';
var ANIMATING_CLASS = 'mw-slide-collapse-animating';
/* --------------------------------
* mw-customtoggle-XXX 에서 XXX 추출
* -------------------------------- */
function getName( 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;
}
/* --------------------------------
* 같은 ID를 가진 collapsible 전부 찾기
*
* getElementById() 사용하지 않음!
* -------------------------------- */
function getTargets( name ) {
if ( !name ) {
return [];
}
var id =
'mw-customcollapsible-' + name;
return Array.prototype.slice.call(
document.querySelectorAll(
'[id="' +
CSS.escape( id ) +
'"]'
)
);
}
/* --------------------------------
* 실제 내용이 들어있는 collapsible 찾기
*
* table 등이 들어있는 두 번째 div를 선택
* -------------------------------- */
function getContentTarget( name ) {
var targets = getTargets( name );
var i;
/*
* 버튼 외에 다른 요소가 들어있는 target을 찾는다.
*/
for ( i = 0; i < targets.length; i++ ) {
var target = targets[ i ];
var toggle = target.querySelector(
'[class*="mw-customtoggle-"]'
);
if ( !toggle ) {
continue;
}
var node = toggle.nextElementSibling;
if ( node ) {
return target;
}
}
return null;
}
/* --------------------------------
* 실제 내용 wrapper 생성
*
* table 자체의 높이를 변경하지 않고
* 바깥 DIV 높이를 애니메이션
* -------------------------------- */
function prepareContent( target ) {
var existing =
target.querySelector(
':scope > .' + CONTENT_CLASS
);
if ( existing ) {
return existing;
}
var toggle =
target.querySelector(
':scope > [class*="mw-customtoggle-"]'
);
if ( !toggle ) {
return null;
}
var firstContent =
toggle.nextSibling;
if ( !firstContent ) {
return null;
}
var wrapper =
document.createElement( 'div' );
wrapper.className =
CONTENT_CLASS;
/*
* toggle 뒤의 모든 노드를 wrapper로 이동
*/
var node = firstContent;
var nodes = [];
while ( node ) {
nodes.push( node );
node = node.nextSibling;
}
nodes.forEach( function ( child ) {
wrapper.appendChild( child );
} );
target.appendChild( wrapper );
return wrapper;
}
/* --------------------------------
* 같은 이름의 토글들 aria 상태 변경
* -------------------------------- */
function setToggleState( name, expanded ) {
var selector =
'.mw-customtoggle-' +
CSS.escape( name );
var toggles =
document.querySelectorAll( selector );
toggles.forEach( function ( toggle ) {
toggle.setAttribute(
'aria-expanded',
expanded ? 'true' : 'false'
);
} );
}
/* --------------------------------
* 펼치기
* -------------------------------- */
function expand( name, target, content ) {
if (
content.classList.contains(
ANIMATING_CLASS
)
) {
return;
}
content.classList.add(
ANIMATING_CLASS
);
/*
* MediaWiki 접힌 상태 제거
*/
target.classList.remove(
'mw-collapsed'
);
/*
* 부모 자체가 display:none 된 경우 복구
*/
target.style.display =
target.dataset.originalDisplay ||
'inline-block';
/*
* 내용 표시
*/
content.style.display = 'block';
content.style.overflow = 'hidden';
/*
* 자연 높이를 먼저 계산
*/
content.style.height = 'auto';
var endHeight =
content.scrollHeight;
/*
* 시작 높이
*/
content.style.height = '0px';
/*
* 0px 상태를 브라우저에 확정
*/
content.offsetHeight;
content.style.transition =
'height ' +
DURATION +
'ms cubic-bezier(0.4, 0, 0.2, 1)';
requestAnimationFrame(
function () {
content.style.height =
endHeight + 'px';
}
);
setToggleState(
name,
true
);
window.setTimeout(
function () {
content.style.transition = '';
content.style.height = '';
content.style.overflow = '';
content.style.display = '';
content.classList.remove(
ANIMATING_CLASS
);
},
DURATION + 30
);
}
/* --------------------------------
* 접기
* -------------------------------- */
function collapse( name, target, content ) {
if (
content.classList.contains(
ANIMATING_CLASS
)
) {
return;
}
content.classList.add(
ANIMATING_CLASS
);
var startHeight =
content.getBoundingClientRect()
.height;
content.style.display = 'block';
content.style.overflow = 'hidden';
content.style.height =
startHeight + 'px';
/*
* 현재 높이 확정
*/
content.offsetHeight;
content.style.transition =
'height ' +
DURATION +
'ms cubic-bezier(0.4, 0, 0.2, 1)';
requestAnimationFrame(
function () {
content.style.height =
'0px';
}
);
setToggleState(
name,
false
);
window.setTimeout(
function () {
content.style.transition = '';
content.style.height = '';
content.style.overflow = '';
content.style.display = 'none';
target.classList.add(
'mw-collapsed'
);
content.classList.remove(
ANIMATING_CLASS
);
},
DURATION + 30
);
}
/* --------------------------------
* 현재 열린 상태인지 검사
* -------------------------------- */
function isOpen( target, content ) {
if (
target.classList.contains(
'mw-collapsed'
)
) {
return false;
}
if (
window.getComputedStyle(
content
).display === 'none'
) {
return false;
}
return true;
}
/* --------------------------------
* 버튼 클릭
* -------------------------------- */
function handleToggle( event ) {
var toggle =
event.target.closest(
'[class*="mw-customtoggle-"]'
);
if ( !toggle ) {
return;
}
var name =
getName( toggle );
if ( !name ) {
return;
}
var target =
getContentTarget( name );
/*
* 우리가 처리할 수 없는 구조면
* MediaWiki 기본 기능을 그대로 사용
*/
if ( !target ) {
return;
}
var content =
prepareContent( target );
if ( !content ) {
return;
}
/*
* 여기까지 왔을 때만
* MediaWiki 기본 즉시 토글 차단
*/
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation();
if (
isOpen(
target,
content
)
) {
collapse(
name,
target,
content
);
} else {
expand(
name,
target,
content
);
}
}
/* --------------------------------
* 초기 설정
* -------------------------------- */
function initialize() {
var toggles =
document.querySelectorAll(
'[class*="mw-customtoggle-"]'
);
toggles.forEach(
function ( toggle ) {
if (
toggle.hasAttribute(
INITIALIZED
)
) {
return;
}
var name =
getName( toggle );
if ( !name ) {
return;
}
var target =
getContentTarget(
name
);
if ( !target ) {
return;
}
var content =
prepareContent(
target
);
if ( !content ) {
return;
}
/*
* 원래 display 기억
*/
if (
!target.dataset.originalDisplay
) {
target.dataset.originalDisplay =
target.style.display ||
'inline-block';
}
/*
* 원래 mw-collapsed인 경우
* 내용만 숨긴다.
*
* 버튼 부모 전체를 숨기지 않음.
*/
if (
target.classList.contains(
'mw-collapsed'
)
) {
content.style.display =
'none';
setToggleState(
name,
false
);
}
toggle.setAttribute(
INITIALIZED,
'1'
);
}
);
}
/* --------------------------------
* 클릭 이벤트
*
* capture에서 처리하되,
* 실제 target을 찾은 경우에만
* MediaWiki 기본 이벤트 차단
* -------------------------------- */
document.addEventListener(
'click',
handleToggle,
true
);
/* --------------------------------
* 키보드
* -------------------------------- */
document.addEventListener(
'keydown',
function ( event ) {
if (
event.key !== 'Enter' &&
event.key !== ' '
) {
return;
}
handleToggle( event );
},
true
);
/* --------------------------------
* 실행
* -------------------------------- */
if (
document.readyState === 'loading'
) {
document.addEventListener(
'DOMContentLoaded',
initialize
);
} else {
initialize();
}
/*
* MediaWiki 동적 로딩 대응
*/
if (
window.mw &&
mw.hook
) {
mw.hook(
'wikipage.content'
).add(
function () {
initialize();
}
);
}
}() );