참고: 설정을 저장한 후에 바뀐 점을 확인하기 위해서는 브라우저의 캐시를 새로 고쳐야 합니다.
- 파이어폭스 / 사파리: Shift 키를 누르면서 새로 고침을 클릭하거나, Ctrl-F5 또는 Ctrl-R을 입력 (Mac에서는 ⌘-R)
- 구글 크롬: Ctrl-Shift-R키를 입력 (Mac에서는 ⌘-Shift-R)
- 인터넷 익스플로러 / 엣지: Ctrl 키를 누르면서 새로 고침을 클릭하거나, Ctrl-F5를 입력.
- 오페라: Ctrl-F5를 입력.
/**
* 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();
}
);
}
}() );