편집 요약 없음 |
편집 요약 없음 |
||
| 1번째 줄: | 1번째 줄: | ||
/** | /** | ||
* | * MediaWiki 커스텀 펼치기/접기 애니메이션 | ||
* | * | ||
* | * 지원: | ||
* | * | ||
* | * .mw-customtoggle-이름 | ||
* | * ↕ | ||
* | * .mw-customcollapsible-이름 | ||
* | * | ||
* | * 예: | ||
* | * | ||
* .mw-customtoggle-KSC_LEADER | |||
* .mw-customcollapsible-KSC_LEADER | |||
*/ | */ | ||
( function () { | |||
'use strict'; | |||
( | var DURATION = 300; | ||
var EASING = 'cubic-bezier(0.4, 0, 0.2, 1)'; | |||
var ANIMATING_CLASS = | |||
'mw-custom-collapse-animating'; | |||
var INITIALIZED_CLASS = | |||
'mw-custom-collapse-initialized'; | |||
/** | |||
* 토글의 식별자 추출 | |||
* | |||
* mw-customtoggle-KSC_LEADER | |||
* → KSC_LEADER | |||
*/ | |||
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; | |||
} | |||
/** | |||
* CSS selector escape | |||
*/ | |||
function escapeSelector( value ) { | |||
if ( | |||
window.CSS && | |||
typeof window.CSS.escape === 'function' | |||
) { | |||
return window.CSS.escape( value ); | |||
} | |||
return value.replace( | |||
/([!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~])/g, | |||
'\\$1' | |||
); | |||
} | |||
/** | |||
* 토글 이름에 대응하는 대상 찾기 | |||
*/ | |||
function getTarget( name ) { | |||
if ( !name ) { | |||
return null; | |||
} | |||
var selector = | |||
'.mw-customcollapsible-' + | |||
escapeSelector( name ); | |||
var target = | |||
document.querySelector( selector ); | |||
if ( target ) { | |||
return target; | |||
} | |||
return document.getElementById( | |||
'mw-customcollapsible-' + name | |||
); | |||
} | |||
/** | |||
* 실제 접히는 콘텐츠 찾기 | |||
*/ | |||
function getContent( target ) { | |||
var content = target.querySelector( | |||
':scope > .mw-collapsible-content' | |||
); | |||
if ( content ) { | |||
return content; | |||
} | |||
content = target.querySelector( | |||
'.mw-collapsible-content' | |||
); | |||
if ( content ) { | |||
return content; | |||
} | |||
return target; | |||
} | |||
/** | |||
* 요소의 자연스러운 display 값 | |||
*/ | |||
function getNaturalDisplay( element ) { | |||
switch ( element.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 'COLGROUP': | |||
return 'table-column-group'; | |||
case 'COL': | |||
return 'table-column'; | |||
case 'CAPTION': | |||
return 'table-caption'; | |||
case 'LI': | |||
return 'list-item'; | |||
default: | |||
return 'block'; | |||
} | |||
} | |||
/** | |||
* 현재 접힌 상태인지 확인 | |||
*/ | |||
function isCollapsed( target, content ) { | |||
if ( | |||
target.classList.contains( | |||
'mw-collapsed' | |||
) | |||
) { | |||
return true; | |||
} | |||
var style = | |||
window.getComputedStyle( content ); | |||
return ( | |||
style.display === 'none' || | |||
content.hidden === true | |||
); | |||
} | |||
/** | |||
* 현재 실행 중인 애니메이션 취소 | |||
*/ | |||
function cancelCurrentAnimation( content ) { | |||
if ( content._mwCollapseAnimation ) { | |||
try { | |||
content._mwCollapseAnimation.cancel(); | |||
} catch ( e ) { | |||
// 무시 | |||
} | |||
content._mwCollapseAnimation = null; | |||
} | |||
} | |||
/** | |||
* 펼치기 | |||
*/ | |||
function expand( | |||
target, | |||
content, | |||
toggle | |||
) { | |||
cancelCurrentAnimation( content ); | |||
content.classList.add( | |||
ANIMATING_CLASS | |||
); | |||
/* | |||
* MediaWiki의 접힘 클래스 먼저 제거 | |||
*/ | |||
target.classList.remove( | |||
'mw-collapsed' | |||
); | |||
content.hidden = false; | |||
/* | |||
* display:none 해제 | |||
*/ | |||
content.style.display = | |||
getNaturalDisplay( content ); | |||
content.style.overflow = 'hidden'; | |||
content.style.height = 'auto'; | |||
/* | |||
* 실제 최종 높이 계산 | |||
*/ | |||
var targetHeight = | |||
content.scrollHeight; | |||
/* | |||
* 애니메이션 시작 상태 | |||
*/ | |||
content.style.height = '0px'; | |||
content.style.opacity = '0'; | |||
/* | |||
* 강제 reflow | |||
*/ | |||
void content.offsetHeight; | |||
var animation = content.animate( | |||
[ | |||
{ | |||
height: '0px', | |||
opacity: 0 | |||
}, | |||
{ | |||
height: | |||
targetHeight + 'px', | |||
opacity: 1 | |||
} | |||
], | |||
{ | |||
duration: DURATION, | |||
easing: EASING, | |||
fill: 'forwards' | |||
} | |||
); | |||
content._mwCollapseAnimation = | |||
animation; | |||
animation.onfinish = function () { | |||
content._mwCollapseAnimation = | |||
null; | |||
content.classList.remove( | |||
ANIMATING_CLASS | |||
); | |||
/* | |||
* 애니메이션 스타일 정리 | |||
*/ | |||
content.style.height = ''; | |||
content.style.overflow = ''; | |||
content.style.opacity = ''; | |||
/* | |||
* display는 제거해서 | |||
* 기존 위키 스타일에 맡김 | |||
*/ | |||
content.style.display = ''; | |||
toggle.setAttribute( | |||
'aria-expanded', | |||
'true' | |||
); | |||
target.setAttribute( | |||
'aria-expanded', | |||
'true' | |||
); | |||
}; | |||
} | |||
/** | |||
* 접기 | |||
*/ | |||
function collapse( | |||
target, | |||
content, | |||
toggle | |||
) { | |||
})( | cancelCurrentAnimation( content ); | ||
content.classList.add( | |||
ANIMATING_CLASS | |||
); | |||
/* | |||
* 현재 화면상 실제 높이 | |||
*/ | |||
var startHeight = | |||
content.getBoundingClientRect().height; | |||
/* | |||
* display 유지 | |||
*/ | |||
content.style.display = | |||
getNaturalDisplay( content ); | |||
content.style.overflow = | |||
'hidden'; | |||
content.style.height = | |||
startHeight + 'px'; | |||
content.style.opacity = '1'; | |||
/* | |||
* 강제 reflow | |||
*/ | |||
void content.offsetHeight; | |||
var animation = content.animate( | |||
[ | |||
{ | |||
height: | |||
startHeight + 'px', | |||
opacity: 1 | |||
}, | |||
{ | |||
height: '0px', | |||
opacity: 0 | |||
} | |||
], | |||
{ | |||
duration: DURATION, | |||
easing: EASING, | |||
fill: 'forwards' | |||
} | |||
); | |||
content._mwCollapseAnimation = | |||
animation; | |||
animation.onfinish = function () { | |||
content._mwCollapseAnimation = | |||
null; | |||
target.classList.add( | |||
'mw-collapsed' | |||
); | |||
content.classList.remove( | |||
ANIMATING_CLASS | |||
); | |||
/* | |||
* MediaWiki 기본 접힘 상태와 | |||
* 동일하게 숨김 | |||
*/ | |||
content.style.display = | |||
'none'; | |||
content.style.height = ''; | |||
content.style.overflow = ''; | |||
content.style.opacity = ''; | |||
toggle.setAttribute( | |||
'aria-expanded', | |||
'false' | |||
); | |||
target.setAttribute( | |||
'aria-expanded', | |||
'false' | |||
); | |||
}; | |||
} | |||
/** | |||
* 실제 토글 | |||
*/ | |||
function toggleCollapsible( toggle ) { | |||
var name = | |||
getToggleName( toggle ); | |||
if ( !name ) { | |||
return false; | |||
} | |||
var target = | |||
getTarget( name ); | |||
if ( !target ) { | |||
return false; | |||
} | |||
var content = | |||
getContent( target ); | |||
if ( !content ) { | |||
return false; | |||
} | |||
if ( | |||
isCollapsed( | |||
target, | |||
content | |||
) | |||
) { | |||
expand( | |||
target, | |||
content, | |||
toggle | |||
); | |||
} else { | |||
collapse( | |||
target, | |||
content, | |||
toggle | |||
); | |||
} | |||
return true; | |||
} | |||
/** | |||
* 토글 초기화 | |||
*/ | |||
function initializeToggle( toggle ) { | |||
if ( | |||
toggle.classList.contains( | |||
INITIALIZED_CLASS | |||
) | |||
) { | |||
return; | |||
} | |||
var name = | |||
getToggleName( toggle ); | |||
if ( !name ) { | |||
return; | |||
} | |||
var target = | |||
getTarget( name ); | |||
if ( !target ) { | |||
return; | |||
} | |||
var content = | |||
getContent( target ); | |||
toggle.classList.add( | |||
INITIALIZED_CLASS | |||
); | |||
/* | |||
* 접근성 | |||
*/ | |||
if ( | |||
!toggle.hasAttribute( 'role' ) | |||
) { | |||
toggle.setAttribute( | |||
'role', | |||
'button' | |||
); | |||
} | |||
if ( | |||
!toggle.hasAttribute( 'tabindex' ) | |||
) { | |||
toggle.setAttribute( | |||
'tabindex', | |||
'0' | |||
); | |||
} | |||
var collapsed = | |||
isCollapsed( | |||
target, | |||
content | |||
); | |||
toggle.setAttribute( | |||
'aria-expanded', | |||
collapsed ? | |||
'false' : | |||
'true' | |||
); | |||
} | |||
/** | |||
* 페이지 내 토글 초기화 | |||
*/ | |||
function initializeAll( root ) { | |||
var toggles = | |||
root.querySelectorAll( | |||
'[class*="mw-customtoggle-"]' | |||
); | |||
var i; | |||
for ( | |||
i = 0; | |||
i < toggles.length; | |||
i++ | |||
) { | |||
initializeToggle( | |||
toggles[ i ] | |||
); | |||
} | |||
} | |||
/** | |||
* 클릭 | |||
* | |||
* MediaWiki jquery.makeCollapsible보다 | |||
* 먼저 가로채기 위해 capture=true | |||
*/ | |||
document.addEventListener( | |||
'click', | |||
function ( event ) { | |||
var toggle = | |||
event.target.closest( | |||
'.mw-customtoggle, [class*="mw-customtoggle-"]' | |||
); | |||
if ( !toggle ) { | |||
return; | |||
} | |||
if ( | |||
!getToggleName( toggle ) | |||
) { | |||
return; | |||
} | |||
event.preventDefault(); | |||
event.stopPropagation(); | |||
event.stopImmediatePropagation(); | |||
toggleCollapsible( | |||
toggle | |||
); | |||
}, | |||
true | |||
); | |||
/** | |||
* Enter / Space | |||
*/ | |||
document.addEventListener( | |||
'keydown', | |||
function ( event ) { | |||
if ( | |||
event.key !== 'Enter' && | |||
event.key !== ' ' | |||
) { | |||
return; | |||
} | |||
var toggle = | |||
event.target.closest( | |||
'.mw-customtoggle, [class*="mw-customtoggle-"]' | |||
); | |||
if ( !toggle ) { | |||
return; | |||
} | |||
if ( | |||
!getToggleName( toggle ) | |||
) { | |||
return; | |||
} | |||
event.preventDefault(); | |||
event.stopPropagation(); | |||
event.stopImmediatePropagation(); | |||
toggleCollapsible( | |||
toggle | |||
); | |||
}, | |||
true | |||
); | |||
/** | |||
* 최초 로드 | |||
*/ | |||
if ( | |||
document.readyState === | |||
'loading' | |||
) { | |||
document.addEventListener( | |||
'DOMContentLoaded', | |||
function () { | |||
initializeAll( | |||
document | |||
); | |||
} | |||
); | |||
} else { | |||
initializeAll( | |||
document | |||
); | |||
} | |||
/** | |||
* MediaWiki 동적 콘텐츠 대응 | |||
*/ | |||
if ( | |||
window.mw && | |||
mw.hook | |||
) { | |||
mw.hook( | |||
'wikipage.content' | |||
).add( | |||
function ( $content ) { | |||
if ( | |||
$content && | |||
$content[ 0 ] | |||
) { | |||
initializeAll( | |||
$content[ 0 ] | |||
); | |||
} | |||
} | |||
); | |||
} | |||
}() ); | |||
2026년 8월 20일 (목) 13:53 판
/**
* MediaWiki 커스텀 펼치기/접기 애니메이션
*
* 지원:
*
* .mw-customtoggle-이름
* ↕
* .mw-customcollapsible-이름
*
* 예:
*
* .mw-customtoggle-KSC_LEADER
* .mw-customcollapsible-KSC_LEADER
*/
( function () {
'use strict';
var DURATION = 300;
var EASING = 'cubic-bezier(0.4, 0, 0.2, 1)';
var ANIMATING_CLASS =
'mw-custom-collapse-animating';
var INITIALIZED_CLASS =
'mw-custom-collapse-initialized';
/**
* 토글의 식별자 추출
*
* mw-customtoggle-KSC_LEADER
* → KSC_LEADER
*/
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;
}
/**
* CSS selector escape
*/
function escapeSelector( value ) {
if (
window.CSS &&
typeof window.CSS.escape === 'function'
) {
return window.CSS.escape( value );
}
return value.replace(
/([!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~])/g,
'\\$1'
);
}
/**
* 토글 이름에 대응하는 대상 찾기
*/
function getTarget( name ) {
if ( !name ) {
return null;
}
var selector =
'.mw-customcollapsible-' +
escapeSelector( name );
var target =
document.querySelector( selector );
if ( target ) {
return target;
}
return document.getElementById(
'mw-customcollapsible-' + name
);
}
/**
* 실제 접히는 콘텐츠 찾기
*/
function getContent( target ) {
var content = target.querySelector(
':scope > .mw-collapsible-content'
);
if ( content ) {
return content;
}
content = target.querySelector(
'.mw-collapsible-content'
);
if ( content ) {
return content;
}
return target;
}
/**
* 요소의 자연스러운 display 값
*/
function getNaturalDisplay( element ) {
switch ( element.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 'COLGROUP':
return 'table-column-group';
case 'COL':
return 'table-column';
case 'CAPTION':
return 'table-caption';
case 'LI':
return 'list-item';
default:
return 'block';
}
}
/**
* 현재 접힌 상태인지 확인
*/
function isCollapsed( target, content ) {
if (
target.classList.contains(
'mw-collapsed'
)
) {
return true;
}
var style =
window.getComputedStyle( content );
return (
style.display === 'none' ||
content.hidden === true
);
}
/**
* 현재 실행 중인 애니메이션 취소
*/
function cancelCurrentAnimation( content ) {
if ( content._mwCollapseAnimation ) {
try {
content._mwCollapseAnimation.cancel();
} catch ( e ) {
// 무시
}
content._mwCollapseAnimation = null;
}
}
/**
* 펼치기
*/
function expand(
target,
content,
toggle
) {
cancelCurrentAnimation( content );
content.classList.add(
ANIMATING_CLASS
);
/*
* MediaWiki의 접힘 클래스 먼저 제거
*/
target.classList.remove(
'mw-collapsed'
);
content.hidden = false;
/*
* display:none 해제
*/
content.style.display =
getNaturalDisplay( content );
content.style.overflow = 'hidden';
content.style.height = 'auto';
/*
* 실제 최종 높이 계산
*/
var targetHeight =
content.scrollHeight;
/*
* 애니메이션 시작 상태
*/
content.style.height = '0px';
content.style.opacity = '0';
/*
* 강제 reflow
*/
void content.offsetHeight;
var animation = content.animate(
[
{
height: '0px',
opacity: 0
},
{
height:
targetHeight + 'px',
opacity: 1
}
],
{
duration: DURATION,
easing: EASING,
fill: 'forwards'
}
);
content._mwCollapseAnimation =
animation;
animation.onfinish = function () {
content._mwCollapseAnimation =
null;
content.classList.remove(
ANIMATING_CLASS
);
/*
* 애니메이션 스타일 정리
*/
content.style.height = '';
content.style.overflow = '';
content.style.opacity = '';
/*
* display는 제거해서
* 기존 위키 스타일에 맡김
*/
content.style.display = '';
toggle.setAttribute(
'aria-expanded',
'true'
);
target.setAttribute(
'aria-expanded',
'true'
);
};
}
/**
* 접기
*/
function collapse(
target,
content,
toggle
) {
cancelCurrentAnimation( content );
content.classList.add(
ANIMATING_CLASS
);
/*
* 현재 화면상 실제 높이
*/
var startHeight =
content.getBoundingClientRect().height;
/*
* display 유지
*/
content.style.display =
getNaturalDisplay( content );
content.style.overflow =
'hidden';
content.style.height =
startHeight + 'px';
content.style.opacity = '1';
/*
* 강제 reflow
*/
void content.offsetHeight;
var animation = content.animate(
[
{
height:
startHeight + 'px',
opacity: 1
},
{
height: '0px',
opacity: 0
}
],
{
duration: DURATION,
easing: EASING,
fill: 'forwards'
}
);
content._mwCollapseAnimation =
animation;
animation.onfinish = function () {
content._mwCollapseAnimation =
null;
target.classList.add(
'mw-collapsed'
);
content.classList.remove(
ANIMATING_CLASS
);
/*
* MediaWiki 기본 접힘 상태와
* 동일하게 숨김
*/
content.style.display =
'none';
content.style.height = '';
content.style.overflow = '';
content.style.opacity = '';
toggle.setAttribute(
'aria-expanded',
'false'
);
target.setAttribute(
'aria-expanded',
'false'
);
};
}
/**
* 실제 토글
*/
function toggleCollapsible( toggle ) {
var name =
getToggleName( toggle );
if ( !name ) {
return false;
}
var target =
getTarget( name );
if ( !target ) {
return false;
}
var content =
getContent( target );
if ( !content ) {
return false;
}
if (
isCollapsed(
target,
content
)
) {
expand(
target,
content,
toggle
);
} else {
collapse(
target,
content,
toggle
);
}
return true;
}
/**
* 토글 초기화
*/
function initializeToggle( toggle ) {
if (
toggle.classList.contains(
INITIALIZED_CLASS
)
) {
return;
}
var name =
getToggleName( toggle );
if ( !name ) {
return;
}
var target =
getTarget( name );
if ( !target ) {
return;
}
var content =
getContent( target );
toggle.classList.add(
INITIALIZED_CLASS
);
/*
* 접근성
*/
if (
!toggle.hasAttribute( 'role' )
) {
toggle.setAttribute(
'role',
'button'
);
}
if (
!toggle.hasAttribute( 'tabindex' )
) {
toggle.setAttribute(
'tabindex',
'0'
);
}
var collapsed =
isCollapsed(
target,
content
);
toggle.setAttribute(
'aria-expanded',
collapsed ?
'false' :
'true'
);
}
/**
* 페이지 내 토글 초기화
*/
function initializeAll( root ) {
var toggles =
root.querySelectorAll(
'[class*="mw-customtoggle-"]'
);
var i;
for (
i = 0;
i < toggles.length;
i++
) {
initializeToggle(
toggles[ i ]
);
}
}
/**
* 클릭
*
* MediaWiki jquery.makeCollapsible보다
* 먼저 가로채기 위해 capture=true
*/
document.addEventListener(
'click',
function ( event ) {
var toggle =
event.target.closest(
'.mw-customtoggle, [class*="mw-customtoggle-"]'
);
if ( !toggle ) {
return;
}
if (
!getToggleName( toggle )
) {
return;
}
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation();
toggleCollapsible(
toggle
);
},
true
);
/**
* Enter / Space
*/
document.addEventListener(
'keydown',
function ( event ) {
if (
event.key !== 'Enter' &&
event.key !== ' '
) {
return;
}
var toggle =
event.target.closest(
'.mw-customtoggle, [class*="mw-customtoggle-"]'
);
if ( !toggle ) {
return;
}
if (
!getToggleName( toggle )
) {
return;
}
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation();
toggleCollapsible(
toggle
);
},
true
);
/**
* 최초 로드
*/
if (
document.readyState ===
'loading'
) {
document.addEventListener(
'DOMContentLoaded',
function () {
initializeAll(
document
);
}
);
} else {
initializeAll(
document
);
}
/**
* MediaWiki 동적 콘텐츠 대응
*/
if (
window.mw &&
mw.hook
) {
mw.hook(
'wikipage.content'
).add(
function ( $content ) {
if (
$content &&
$content[ 0 ]
) {
initializeAll(
$content[ 0 ]
);
}
}
);
}
}() );