편집 요약 없음 |
편집 요약 없음 |
||
| 1번째 줄: | 1번째 줄: | ||
( function ( $ | /** | ||
* MediaWiki 커스텀 펼치기 / 접기 애니메이션 | |||
* | |||
* 지원: | |||
* - mw-customtoggle-XXX | |||
* - mw-customcollapsible-XXX | |||
* - 일반 mw-collapsible-content | |||
* - 동일 ID 2개 교대형 구조 | |||
* - flex 내부 여러 토글 | |||
* - 중첩 표 | |||
* | |||
* 효과: | |||
* - 높이 애니메이션만 적용 | |||
* - opacity 없음 | |||
*/ | |||
( function ( $ ) { | |||
'use strict'; | 'use strict'; | ||
var DURATION = 180; | var DURATION = 180; | ||
var EASING = ' | var EASING = 'swing'; | ||
var VIEWPORT_CLASS = | |||
'mw-custom-collapse-viewport'; | |||
var INNER_CLASS = | |||
'mw-custom-collapse-viewport-inner'; | |||
var | var DATA_DISPLAY = | ||
'mwCustomCollapseOriginalDisplay'; | |||
/** | |||
* mw-customtoggle-XXX → XXX | |||
*/ | |||
function getToggleName( toggle ) { | function getToggleName( toggle ) { | ||
var prefix = 'mw-customtoggle-'; | var prefix = | ||
var classes = toggle.classList; | 'mw-customtoggle-'; | ||
var classes = | |||
toggle.classList; | |||
var i; | var i; | ||
for ( i = 0; i < classes.length; i++ ) { | for ( | ||
if ( classes[ i ].indexOf( prefix ) === 0 ) { | i = 0; | ||
return classes[ i ].substring( prefix.length ); | i < classes.length; | ||
i++ | |||
) { | |||
if ( | |||
classes[ i ].indexOf( | |||
prefix | |||
) === 0 | |||
) { | |||
return classes[ i ].substring( | |||
prefix.length | |||
); | |||
} | } | ||
} | } | ||
| 24번째 줄: | 63번째 줄: | ||
/** | |||
* attribute selector escape | |||
*/ | |||
function escapeAttribute( value ) { | function escapeAttribute( value ) { | ||
return String( value ) | return String( value ) | ||
.replace( /\\/g, '\\\\' ) | .replace( | ||
.replace( /"/g, '\\"' ); | /\\/g, | ||
'\\\\' | |||
) | |||
.replace( | |||
/"/g, | |||
'\\"' | |||
); | |||
} | } | ||
/** | |||
* 동일한 이름의 target 모두 검색 | |||
*/ | |||
function getTargets( name ) { | function getTargets( name ) { | ||
var id = 'mw-customcollapsible-' + name; | var id = | ||
'mw-customcollapsible-' + | |||
name; | |||
return document.querySelectorAll( | return document.querySelectorAll( | ||
'[id="' + escapeAttribute( id ) + '"]' | '[id="' + | ||
escapeAttribute( id ) + | |||
'"]' | |||
); | ); | ||
} | } | ||
/** | |||
* 직계 mw-collapsible-content 찾기 | |||
*/ | |||
function getDirectContent( target ) { | |||
var children = | |||
target.children; | |||
var i; | |||
for ( | |||
i = 0; | |||
i < children.length; | |||
i++ | |||
) { | |||
if ( | |||
children[ i ].classList && | |||
children[ i ].classList.contains( | |||
'mw-collapsible-content' | |||
) | |||
) { | |||
return children[ i ]; | |||
} | |||
} | |||
return null; | |||
} | |||
/** | |||
* 현재 접힘 상태 | |||
*/ | |||
function isCollapsed( target ) { | function isCollapsed( target ) { | ||
if ( | |||
target.classList.contains( | |||
'mw-collapsed' | |||
) | |||
) { | |||
return true; | |||
} | |||
return ( | return ( | ||
window.getComputedStyle( | |||
window.getComputedStyle( target ).display === 'none' | target | ||
).display === 'none' | |||
); | ); | ||
} | } | ||
| 49번째 줄: | 144번째 줄: | ||
/** | /** | ||
* target | * display 상태 기억 | ||
* | */ | ||
* target | function rememberDisplay( target ) { | ||
var display = | |||
window.getComputedStyle( | |||
* | target | ||
).display; | |||
if ( | |||
display && | |||
display !== 'none' | |||
) { | |||
$( target ).data( | |||
DATA_DISPLAY, | |||
display | |||
); | |||
} | |||
} | |||
/** | |||
* 펼칠 때 target의 display 복원 | |||
*/ | |||
function getOpenDisplay( target ) { | |||
var saved = | |||
$( target ).data( | |||
DATA_DISPLAY | |||
); | |||
if ( saved ) { | |||
return saved; | |||
} | |||
/* | |||
* HTML에 원래 display 값이 남아 있다면 | |||
* 그 값을 우선 사용 | |||
*/ | |||
if ( | |||
target.style.display && | |||
target.style.display !== 'none' | |||
) { | |||
return target.style.display; | |||
} | |||
/* | |||
* TABLE 계열 | |||
*/ | |||
switch ( target.tagName ) { | |||
case 'TABLE': | |||
return 'table'; | |||
case 'TBODY': | |||
return 'table-row-group'; | |||
case 'THEAD': | |||
return 'table-header-group'; | |||
case 'TFOOT': | |||
return 'table-footer-group'; | |||
case 'TR': | |||
return 'table-row'; | |||
case 'TD': | |||
case 'TH': | |||
return 'table-cell'; | |||
case 'LI': | |||
return 'list-item'; | |||
} | |||
/* | |||
* mw-collapsible은 대부분 DIV | |||
*/ | |||
return 'block'; | |||
} | |||
/** | |||
* 애니메이션용 viewport 검색 | |||
*/ | */ | ||
function | function getViewport( target ) { | ||
var children = | |||
target.children; | |||
var i; | var i; | ||
for ( i = 0; i < | for ( | ||
i = 0; | |||
i < children.length; | |||
i++ | |||
) { | |||
if ( | if ( | ||
children[ i ].classList && | |||
children[ i ].classList.contains( | |||
VIEWPORT_CLASS | VIEWPORT_CLASS | ||
) | ) | ||
) { | ) { | ||
return | return children[ i ]; | ||
} | } | ||
} | } | ||
return null; | |||
} | |||
/** | |||
* 애니메이션용 viewport 생성 | |||
* | |||
* 내부 TABLE 자체에는 height를 적용하지 않고 | |||
* viewport 높이만 변화시킴. | |||
*/ | |||
function ensureViewport( target ) { | |||
var existing = | |||
getViewport( | |||
target | |||
); | |||
if ( existing ) { | |||
return existing; | |||
} | |||
var viewport = | |||
document.createElement( | |||
'div' | |||
); | |||
var inner = | |||
document.createElement( | |||
'div' | |||
); | |||
viewport.className = | |||
VIEWPORT_CLASS; | |||
inner.className = | |||
INNER_CLASS; | |||
/* | /* | ||
* | * MediaWiki 표준 구조라면 | ||
* | * mw-collapsible-content의 내용만 이동 | ||
*/ | */ | ||
while ( | var content = | ||
getDirectContent( | |||
target | |||
); | |||
if ( content ) { | |||
while ( | |||
content.firstChild | |||
) { | |||
inner.appendChild( | |||
content.firstChild | |||
); | |||
} | |||
viewport.appendChild( | |||
inner | |||
); | |||
content.appendChild( | content.appendChild( | ||
viewport | |||
); | |||
return viewport; | |||
} | |||
/* | |||
* mw-collapsible-content가 없는 구조 | |||
* | |||
* target 내부 전체를 viewport 안으로 이동 | |||
*/ | |||
while ( | |||
target.firstChild | |||
) { | |||
inner.appendChild( | |||
target.firstChild | target.firstChild | ||
); | ); | ||
| 91번째 줄: | 338번째 줄: | ||
viewport.appendChild( | viewport.appendChild( | ||
inner | |||
); | |||
viewport | target.appendChild( | ||
viewport | |||
); | |||
| 102번째 줄: | 352번째 줄: | ||
function | /** | ||
var children = viewport.children; | * viewport inner | ||
*/ | |||
function getViewportInner( | |||
viewport | |||
) { | |||
var children = | |||
viewport.children; | |||
var i; | var i; | ||
for ( i = 0; i < children.length; i++ ) { | for ( | ||
i = 0; | |||
i < children.length; | |||
i++ | |||
) { | |||
if ( | if ( | ||
children[ i ].classList && | children[ i ].classList && | ||
children[ i ].classList.contains( | children[ i ].classList.contains( | ||
INNER_CLASS | |||
) | ) | ||
) { | ) { | ||
| 121번째 줄: | 382번째 줄: | ||
function | /** | ||
* target 안의 자기 토글 검색 | |||
* | |||
* 동일 ID 2개 교대형에서 사용. | |||
*/ | |||
function getOwnToggle( | |||
target, | |||
name | |||
) { | |||
var className = | |||
'mw-customtoggle-' + | |||
name; | |||
var elements = | |||
target.getElementsByClassName( | |||
className | |||
); | |||
if ( | if ( | ||
elements.length | |||
) { | ) { | ||
return | return elements[ 0 ]; | ||
} | } | ||
return null; | |||
} | |||
/** | |||
* 펼치기 | |||
*/ | |||
function expand( | |||
target, | |||
name, | |||
multipleTargets | |||
) { | |||
var $target = | |||
$( target ); | |||
/* | |||
* 진행 중인 target animation 중단 | |||
*/ | |||
$target.stop( | |||
true, | |||
true | |||
); | |||
target.classList.remove( | |||
'mw-collapsed' | |||
) | ); | ||
/* | |||
* 바깥 target 먼저 표시 | |||
*/ | |||
target.style.display = | |||
getOpenDisplay( | |||
target | target | ||
) | ); | ||
var viewport = | var viewport = | ||
| 181번째 줄: | 450번째 줄: | ||
); | ); | ||
var | |||
var inner = | |||
getViewportInner( | |||
viewport | viewport | ||
); | ); | ||
if ( ! | if ( !inner ) { | ||
return; | return; | ||
} | } | ||
var ownToggle = | |||
null; | |||
/* | /* | ||
* | * 동일 ID가 2개인 교대형 구조: | ||
* | |||
* 접히는 버튼과 펼쳐지는 버튼이 | |||
* 애니메이션 중 겹치지 않도록 처리. | |||
*/ | */ | ||
target.style. | if ( multipleTargets ) { | ||
ownToggle = | |||
getOwnToggle( | |||
); | target, | ||
name | |||
); | |||
if ( ownToggle ) { | |||
ownToggle.style.visibility = | |||
'hidden'; | |||
} | |||
} | |||
var $viewport = | |||
$( viewport ); | |||
$viewport.stop( | |||
true, | |||
true | |||
); | ); | ||
viewport.style.display = | |||
'block'; | |||
viewport.style.overflow = | |||
'hidden'; | |||
viewport.style.height = 'auto | viewport.style.height = | ||
'auto'; | |||
var | /* | ||
* 내부 table은 건드리지 않고 | |||
* inner의 실제 높이를 측정 | |||
*/ | |||
var targetHeight = | |||
inner.getBoundingClientRect() | |||
.height; | .height; | ||
viewport.style.height = '0px'; | viewport.style.height = | ||
'0px'; | |||
/* | |||
* 강제 reflow | |||
*/ | |||
void viewport.offsetHeight; | void viewport.offsetHeight; | ||
$viewport.animate( | |||
{ | |||
height: | |||
targetHeight | |||
}, | |||
}, | { | ||
duration: | |||
DURATION, | |||
easing: | |||
EASING, | |||
complete: | |||
function () { | |||
viewport.style.height = | |||
''; | |||
viewport.style.overflow = | |||
''; | |||
if ( | |||
ownToggle | |||
) { | |||
ownToggle.style.visibility = | |||
}; | ''; | ||
} | |||
} | |||
} | |||
); | |||
} | } | ||
function collapse( target ) { | /** | ||
* 접기 | |||
*/ | |||
function collapse( | |||
target | |||
) { | |||
rememberDisplay( | rememberDisplay( | ||
target | target | ||
| 279번째 줄: | 575번째 줄: | ||
); | ); | ||
var | |||
var inner = | |||
getViewportInner( | |||
viewport | viewport | ||
); | ); | ||
if ( ! | if ( !inner ) { | ||
return; | return; | ||
} | } | ||
var $viewport = | |||
$( viewport ); | |||
$viewport.stop( | |||
true, | |||
true | |||
); | |||
var currentHeight = | |||
viewport.getBoundingClientRect() | |||
.height; | |||
/* | /* | ||
* | * 혹시 viewport 높이가 0으로 잡히는 경우 | ||
* | * inner 실제 높이 사용 | ||
*/ | */ | ||
if ( | |||
currentHeight <= 0 | |||
) { | |||
currentHeight = | |||
inner.getBoundingClientRect() | |||
.height; | |||
} | |||
viewport.style.height = | viewport.style.height = | ||
currentHeight + | |||
'px'; | 'px'; | ||
| 310번째 줄: | 626번째 줄: | ||
$viewport.animate( | |||
{ | |||
height: | |||
0 | |||
}, | |||
{ | |||
duration: | |||
DURATION, | |||
easing: | |||
EASING, | |||
complete: | |||
{ | function () { | ||
target.classList.add( | |||
'mw-collapsed' | |||
); | |||
target.style.display = | |||
'none'; | |||
viewport.style.height = | |||
''; | |||
viewport.style.overflow = | |||
''; | |||
} | |||
} | |||
); | |||
} | |||
/** | |||
* ARIA 갱신 | |||
*/ | |||
function updateAria( | |||
name, | |||
expanded | |||
) { | |||
var className = | |||
'mw-customtoggle-' + | |||
name; | |||
var toggles = | |||
document.getElementsByClassName( | |||
className | |||
); | |||
var i; | |||
for ( | |||
i = 0; | |||
i < toggles.length; | |||
i++ | |||
) { | |||
toggles[ i ].setAttribute( | |||
'aria-expanded', | |||
expanded ? | |||
'true' : | |||
'false' | |||
); | |||
} | |||
} | } | ||
function toggleGroup( toggle ) { | /** | ||
* 그룹 토글 | |||
*/ | |||
function toggleGroup( | |||
toggle | |||
) { | |||
var name = | var name = | ||
getToggleName( | getToggleName( | ||
| 368번째 줄: | 710번째 줄: | ||
if ( !name ) { | if ( !name ) { | ||
return; | return false; | ||
} | } | ||
| 376번째 줄: | 718번째 줄: | ||
name | name | ||
); | ); | ||
if ( | |||
!targets.length | |||
) { | |||
return false; | |||
} | |||
var multipleTargets = | |||
targets.length > 1; | |||
var i; | var i; | ||
var opened = | |||
false; | |||
/* | |||
* 클릭 직전의 상태를 먼저 기록 | |||
* | |||
* 각 요소를 순차 수정하다가 | |||
* 다음 요소의 상태 판별이 꼬이지 않도록 함. | |||
*/ | |||
var states = | |||
[]; | |||
| 386번째 줄: | 752번째 줄: | ||
i++ | i++ | ||
) { | ) { | ||
states.push( | |||
isCollapsed( | |||
targets[ i ] | |||
) | |||
); | |||
} | |||
/* | |||
* 상태 변경 | |||
*/ | |||
for ( | |||
i = 0; | |||
i < targets.length; | |||
i++ | |||
) { | |||
if ( | if ( | ||
states[ i ] | |||
) { | ) { | ||
expand( | expand( | ||
targets[ i ], | |||
name, | |||
multipleTargets | |||
); | ); | ||
opened = | |||
true; | |||
} else { | } else { | ||
collapse( | collapse( | ||
targets[ i ] | |||
); | ); | ||
} | } | ||
} | } | ||
/* | |||
* 일반 target 하나짜리만 | |||
* aria-expanded 갱신. | |||
*/ | |||
if ( | |||
targets.length === 1 | |||
) { | |||
updateAria( | |||
name, | |||
opened | |||
); | |||
} | |||
return true; | |||
} | } | ||
/** | |||
* 클릭 | |||
*/ | |||
document.addEventListener( | document.addEventListener( | ||
'click', | 'click', | ||
| 434번째 줄: | 836번째 줄: | ||
event.preventDefault(); | event.preventDefault(); | ||
event.stopPropagation(); | event.stopPropagation(); | ||
event.stopImmediatePropagation(); | event.stopImmediatePropagation(); | ||
| 446번째 줄: | 850번째 줄: | ||
/** | |||
* Enter / Space | |||
*/ | |||
document.addEventListener( | document.addEventListener( | ||
'keydown', | 'keydown', | ||
| 465번째 줄: | 872번째 줄: | ||
if ( !toggle ) { | if ( !toggle ) { | ||
return; | |||
} | |||
if ( | |||
!getToggleName( | |||
toggle | |||
) | |||
) { | |||
return; | return; | ||
} | } | ||
| 470번째 줄: | 886번째 줄: | ||
event.preventDefault(); | event.preventDefault(); | ||
event.stopPropagation(); | event.stopPropagation(); | ||
event.stopImmediatePropagation(); | event.stopImmediatePropagation(); | ||
| 482번째 줄: | 900번째 줄: | ||
}( jQuery | /** | ||
* 페이지 최초 display 상태 기억 | |||
*/ | |||
function initialize( | |||
root | |||
) { | |||
var elements = | |||
root.querySelectorAll( | |||
'.mw-collapsible' | |||
); | |||
var i; | |||
for ( | |||
i = 0; | |||
i < elements.length; | |||
i++ | |||
) { | |||
if ( | |||
window.getComputedStyle( | |||
elements[ i ] | |||
).display !== 'none' | |||
) { | |||
rememberDisplay( | |||
elements[ i ] | |||
); | |||
} | |||
} | |||
} | |||
$( function () { | |||
initialize( | |||
document | |||
); | |||
} ); | |||
/* | |||
* MediaWiki AJAX / 동적 콘텐츠 | |||
*/ | |||
if ( | |||
window.mw && | |||
mw.hook | |||
) { | |||
mw.hook( | |||
'wikipage.content' | |||
).add( | |||
function ( $content ) { | |||
if ( | |||
$content && | |||
$content[ 0 ] | |||
) { | |||
initialize( | |||
$content[ 0 ] | |||
); | |||
} | |||
} | |||
); | |||
} | |||
}( jQuery ) ); | |||
2026년 8월 20일 (목) 14:26 판
/**
* MediaWiki 커스텀 펼치기 / 접기 애니메이션
*
* 지원:
* - mw-customtoggle-XXX
* - mw-customcollapsible-XXX
* - 일반 mw-collapsible-content
* - 동일 ID 2개 교대형 구조
* - flex 내부 여러 토글
* - 중첩 표
*
* 효과:
* - 높이 애니메이션만 적용
* - opacity 없음
*/
( function ( $ ) {
'use strict';
var DURATION = 180;
var EASING = 'swing';
var VIEWPORT_CLASS =
'mw-custom-collapse-viewport';
var INNER_CLASS =
'mw-custom-collapse-viewport-inner';
var DATA_DISPLAY =
'mwCustomCollapseOriginalDisplay';
/**
* mw-customtoggle-XXX → XXX
*/
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;
}
/**
* attribute selector escape
*/
function escapeAttribute( value ) {
return String( value )
.replace(
/\\/g,
'\\\\'
)
.replace(
/"/g,
'\\"'
);
}
/**
* 동일한 이름의 target 모두 검색
*/
function getTargets( name ) {
var id =
'mw-customcollapsible-' +
name;
return document.querySelectorAll(
'[id="' +
escapeAttribute( id ) +
'"]'
);
}
/**
* 직계 mw-collapsible-content 찾기
*/
function getDirectContent( target ) {
var children =
target.children;
var i;
for (
i = 0;
i < children.length;
i++
) {
if (
children[ i ].classList &&
children[ i ].classList.contains(
'mw-collapsible-content'
)
) {
return children[ i ];
}
}
return null;
}
/**
* 현재 접힘 상태
*/
function isCollapsed( target ) {
if (
target.classList.contains(
'mw-collapsed'
)
) {
return true;
}
return (
window.getComputedStyle(
target
).display === 'none'
);
}
/**
* display 상태 기억
*/
function rememberDisplay( target ) {
var display =
window.getComputedStyle(
target
).display;
if (
display &&
display !== 'none'
) {
$( target ).data(
DATA_DISPLAY,
display
);
}
}
/**
* 펼칠 때 target의 display 복원
*/
function getOpenDisplay( target ) {
var saved =
$( target ).data(
DATA_DISPLAY
);
if ( saved ) {
return saved;
}
/*
* HTML에 원래 display 값이 남아 있다면
* 그 값을 우선 사용
*/
if (
target.style.display &&
target.style.display !== 'none'
) {
return target.style.display;
}
/*
* TABLE 계열
*/
switch ( target.tagName ) {
case 'TABLE':
return 'table';
case 'TBODY':
return 'table-row-group';
case 'THEAD':
return 'table-header-group';
case 'TFOOT':
return 'table-footer-group';
case 'TR':
return 'table-row';
case 'TD':
case 'TH':
return 'table-cell';
case 'LI':
return 'list-item';
}
/*
* mw-collapsible은 대부분 DIV
*/
return 'block';
}
/**
* 애니메이션용 viewport 검색
*/
function getViewport( target ) {
var children =
target.children;
var i;
for (
i = 0;
i < children.length;
i++
) {
if (
children[ i ].classList &&
children[ i ].classList.contains(
VIEWPORT_CLASS
)
) {
return children[ i ];
}
}
return null;
}
/**
* 애니메이션용 viewport 생성
*
* 내부 TABLE 자체에는 height를 적용하지 않고
* viewport 높이만 변화시킴.
*/
function ensureViewport( target ) {
var existing =
getViewport(
target
);
if ( existing ) {
return existing;
}
var viewport =
document.createElement(
'div'
);
var inner =
document.createElement(
'div'
);
viewport.className =
VIEWPORT_CLASS;
inner.className =
INNER_CLASS;
/*
* MediaWiki 표준 구조라면
* mw-collapsible-content의 내용만 이동
*/
var content =
getDirectContent(
target
);
if ( content ) {
while (
content.firstChild
) {
inner.appendChild(
content.firstChild
);
}
viewport.appendChild(
inner
);
content.appendChild(
viewport
);
return viewport;
}
/*
* mw-collapsible-content가 없는 구조
*
* target 내부 전체를 viewport 안으로 이동
*/
while (
target.firstChild
) {
inner.appendChild(
target.firstChild
);
}
viewport.appendChild(
inner
);
target.appendChild(
viewport
);
return viewport;
}
/**
* viewport inner
*/
function getViewportInner(
viewport
) {
var children =
viewport.children;
var i;
for (
i = 0;
i < children.length;
i++
) {
if (
children[ i ].classList &&
children[ i ].classList.contains(
INNER_CLASS
)
) {
return children[ i ];
}
}
return null;
}
/**
* target 안의 자기 토글 검색
*
* 동일 ID 2개 교대형에서 사용.
*/
function getOwnToggle(
target,
name
) {
var className =
'mw-customtoggle-' +
name;
var elements =
target.getElementsByClassName(
className
);
if (
elements.length
) {
return elements[ 0 ];
}
return null;
}
/**
* 펼치기
*/
function expand(
target,
name,
multipleTargets
) {
var $target =
$( target );
/*
* 진행 중인 target animation 중단
*/
$target.stop(
true,
true
);
target.classList.remove(
'mw-collapsed'
);
/*
* 바깥 target 먼저 표시
*/
target.style.display =
getOpenDisplay(
target
);
var viewport =
ensureViewport(
target
);
var inner =
getViewportInner(
viewport
);
if ( !inner ) {
return;
}
var ownToggle =
null;
/*
* 동일 ID가 2개인 교대형 구조:
*
* 접히는 버튼과 펼쳐지는 버튼이
* 애니메이션 중 겹치지 않도록 처리.
*/
if ( multipleTargets ) {
ownToggle =
getOwnToggle(
target,
name
);
if ( ownToggle ) {
ownToggle.style.visibility =
'hidden';
}
}
var $viewport =
$( viewport );
$viewport.stop(
true,
true
);
viewport.style.display =
'block';
viewport.style.overflow =
'hidden';
viewport.style.height =
'auto';
/*
* 내부 table은 건드리지 않고
* inner의 실제 높이를 측정
*/
var targetHeight =
inner.getBoundingClientRect()
.height;
viewport.style.height =
'0px';
/*
* 강제 reflow
*/
void viewport.offsetHeight;
$viewport.animate(
{
height:
targetHeight
},
{
duration:
DURATION,
easing:
EASING,
complete:
function () {
viewport.style.height =
'';
viewport.style.overflow =
'';
if (
ownToggle
) {
ownToggle.style.visibility =
'';
}
}
}
);
}
/**
* 접기
*/
function collapse(
target
) {
rememberDisplay(
target
);
var viewport =
ensureViewport(
target
);
var inner =
getViewportInner(
viewport
);
if ( !inner ) {
return;
}
var $viewport =
$( viewport );
$viewport.stop(
true,
true
);
var currentHeight =
viewport.getBoundingClientRect()
.height;
/*
* 혹시 viewport 높이가 0으로 잡히는 경우
* inner 실제 높이 사용
*/
if (
currentHeight <= 0
) {
currentHeight =
inner.getBoundingClientRect()
.height;
}
viewport.style.height =
currentHeight +
'px';
viewport.style.overflow =
'hidden';
void viewport.offsetHeight;
$viewport.animate(
{
height:
0
},
{
duration:
DURATION,
easing:
EASING,
complete:
function () {
target.classList.add(
'mw-collapsed'
);
target.style.display =
'none';
viewport.style.height =
'';
viewport.style.overflow =
'';
}
}
);
}
/**
* ARIA 갱신
*/
function updateAria(
name,
expanded
) {
var className =
'mw-customtoggle-' +
name;
var toggles =
document.getElementsByClassName(
className
);
var i;
for (
i = 0;
i < toggles.length;
i++
) {
toggles[ i ].setAttribute(
'aria-expanded',
expanded ?
'true' :
'false'
);
}
}
/**
* 그룹 토글
*/
function toggleGroup(
toggle
) {
var name =
getToggleName(
toggle
);
if ( !name ) {
return false;
}
var targets =
getTargets(
name
);
if (
!targets.length
) {
return false;
}
var multipleTargets =
targets.length > 1;
var i;
var opened =
false;
/*
* 클릭 직전의 상태를 먼저 기록
*
* 각 요소를 순차 수정하다가
* 다음 요소의 상태 판별이 꼬이지 않도록 함.
*/
var states =
[];
for (
i = 0;
i < targets.length;
i++
) {
states.push(
isCollapsed(
targets[ i ]
)
);
}
/*
* 상태 변경
*/
for (
i = 0;
i < targets.length;
i++
) {
if (
states[ i ]
) {
expand(
targets[ i ],
name,
multipleTargets
);
opened =
true;
} else {
collapse(
targets[ i ]
);
}
}
/*
* 일반 target 하나짜리만
* aria-expanded 갱신.
*/
if (
targets.length === 1
) {
updateAria(
name,
opened
);
}
return true;
}
/**
* 클릭
*/
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
);
/**
* Enter / Space
*/
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();
toggleGroup(
toggle
);
},
true
);
/**
* 페이지 최초 display 상태 기억
*/
function initialize(
root
) {
var elements =
root.querySelectorAll(
'.mw-collapsible'
);
var i;
for (
i = 0;
i < elements.length;
i++
) {
if (
window.getComputedStyle(
elements[ i ]
).display !== 'none'
) {
rememberDisplay(
elements[ i ]
);
}
}
}
$( function () {
initialize(
document
);
} );
/*
* MediaWiki AJAX / 동적 콘텐츠
*/
if (
window.mw &&
mw.hook
) {
mw.hook(
'wikipage.content'
).add(
function ( $content ) {
if (
$content &&
$content[ 0 ]
) {
initialize(
$content[ 0 ]
);
}
}
);
}
}( jQuery ) );