참고: 설정을 저장한 후에 바뀐 점을 확인하기 위해서는 브라우저의 캐시를 새로 고쳐야 합니다.
- 파이어폭스 / 사파리: Shift 키를 누르면서 새로 고침을 클릭하거나, Ctrl-F5 또는 Ctrl-R을 입력 (Mac에서는 ⌘-R)
- 구글 크롬: Ctrl-Shift-R키를 입력 (Mac에서는 ⌘-Shift-R)
- 인터넷 익스플로러 / 엣지: Ctrl 키를 누르면서 새로 고침을 클릭하거나, Ctrl-F5를 입력.
- 오페라: Ctrl-F5를 입력.
/**
* MediaWiki 커스텀 펼치기 / 접기 애니메이션
*
* 지원:
*
* 1. 일반 MediaWiki 구조
*
* .mw-customtoggle-XXX
* #mw-customcollapsible-XXX
* └─ .mw-collapsible-content
*
*
* 2. 동일 ID 2개를 사용하는 교대형 구조
*
* #mw-customcollapsible-XXX
* └─ [ 버튼 ]
*
* #mw-customcollapsible-XXX.mw-collapsed
* ├─ [ 버튼 ]
* └─ 실제 내용
*
*
* 3. 여러 독립 토글
*
* TWICE_KOR
* TWICE_OVS
* TWICE_ETC
* TWICE_REL
*
*
* 4. 정보상자 / table / rowspan 내부 지원
*
* mw-collapsible 자체의 height를 직접 변경하지 않고
* 내부 wrapper만 slide 처리하여
* table 레이아웃 재계산 문제를 줄임.
*
*
* 애니메이션:
* height만 사용
* opacity 사용하지 않음
*/
( function ( $, mw ) {
'use strict';
var DURATION = 200;
var EASING = 'swing';
var DATA_DISPLAY =
'mwCustomCollapseDisplay';
var INNER_CLASS =
'mw-custom-collapse-inner';
var DATA_WRAPPED =
'mwCustomCollapseWrapped';
/**
* 토글 클래스에서 식별자 추출
*
* mw-customtoggle-Flora_LACTEA_01
* →
* Flora_LACTEA_01
*/
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,
'\\"'
);
}
/**
* 같은 name을 가진 모든 target 검색
*/
function getTargets( name ) {
if ( !name ) {
return [];
}
var id =
'mw-customcollapsible-' +
name;
return document.querySelectorAll(
'[id="' +
escapeAttribute( id ) +
'"]'
);
}
/**
* target의 직계
* .mw-collapsible-content 검색
*
* 중첩 collapsible 오선택 방지.
*/
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;
}
/**
* 기존에 만든 내부 wrapper 검색
*/
function getDirectInnerWrapper( target ) {
var children =
target.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 내부에 애니메이션용 wrapper 생성
*
* .mw-collapsible-content가 없는 구조에서 사용.
*
* target 자체 height를 변경하지 않고
* 내부 wrapper만 slide 처리.
*/
function createInnerWrapper( target ) {
var existing =
getDirectInnerWrapper(
target
);
if ( existing ) {
return existing;
}
var wrapper =
document.createElement(
'div'
);
wrapper.className =
INNER_CLASS;
/*
* 기존 자식 전부 wrapper 안으로 이동
*/
while (
target.firstChild
) {
wrapper.appendChild(
target.firstChild
);
}
target.appendChild(
wrapper
);
$( target ).data(
DATA_WRAPPED,
true
);
return wrapper;
}
/**
* 실제 애니메이션 요소 결정
*
* 1순위:
* .mw-collapsible-content
*
* 2순위:
* 자동 생성 inner wrapper
*/
function getAnimatedElement( target ) {
var content =
getDirectContent(
target
);
if ( content ) {
return content;
}
return createInnerWrapper(
target
);
}
/**
* target 자신의 직계 토글 검색
*
* inner wrapper가 생성된 뒤에는
* 토글이 wrapper 안으로 들어가므로
* wrapper 내부도 확인.
*/
function getOwnToggle(
target,
name
) {
var className =
'mw-customtoggle-' +
name;
var children =
target.children;
var i;
for (
i = 0;
i < children.length;
i++
) {
if (
children[ i ].classList &&
children[ i ].classList.contains(
className
)
) {
return children[ i ];
}
}
var wrapper =
getDirectInnerWrapper(
target
);
if ( wrapper ) {
children =
wrapper.children;
for (
i = 0;
i < children.length;
i++
) {
if (
children[ i ].classList &&
children[ i ].classList.contains(
className
)
) {
return children[ i ];
}
}
}
return null;
}
/**
* 현재 접힌 상태인지 확인
*/
function isCollapsed(
target,
element
) {
if (
target.classList.contains(
'mw-collapsed'
)
) {
return true;
}
if (
window
.getComputedStyle(
target
)
.display === 'none'
) {
return true;
}
return (
window
.getComputedStyle(
element
)
.display === 'none'
);
}
/**
* display 값 기억
*/
function rememberDisplay( element ) {
var display =
window
.getComputedStyle(
element
)
.display;
if (
display &&
display !== 'none'
) {
$( element ).data(
DATA_DISPLAY,
display
);
}
}
/**
* target 자체 display 기억
*/
function rememberTargetDisplay(
target
) {
var display =
window
.getComputedStyle(
target
)
.display;
if (
display &&
display !== 'none'
) {
$( target ).data(
DATA_DISPLAY,
display
);
}
}
/**
* 펼칠 때 사용할 display 계산
*/
function getOpenDisplay( element ) {
var stored =
$( element ).data(
DATA_DISPLAY
);
if ( stored ) {
return stored;
}
if (
element.style.display &&
element.style.display !== 'none'
) {
return element.style.display;
}
var computed =
window
.getComputedStyle(
element
)
.display;
if (
computed &&
computed !== 'none'
) {
return computed;
}
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';
}
if (
element.classList.contains(
'mw-collapsible'
)
) {
if (
element.style.width === '100%' ||
element.style.width === '100.0%'
) {
return 'block';
}
return 'inline-block';
}
return 'block';
}
/**
* aria-expanded 갱신
*/
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 expand(
target,
element,
name,
hideOwnToggle
) {
var $target =
$( target );
var $element =
$( element );
var ownToggle =
null;
/*
* 교대형 구조:
* 새로 나타나는 쪽 토글 글자를
* 애니메이션이 끝날 때까지 숨김.
*/
if ( hideOwnToggle ) {
ownToggle =
getOwnToggle(
target,
name
);
if ( ownToggle ) {
ownToggle.style.visibility =
'hidden';
}
}
$element.stop(
true,
false
);
/*
* target 자체가 display:none 상태라면
* 먼저 바깥 컨테이너를 복원.
*/
if (
window
.getComputedStyle(
target
)
.display === 'none'
) {
$target.css(
'display',
getOpenDisplay(
target
)
);
}
target.classList.remove(
'mw-collapsed'
);
/*
* 내부 요소만 slideDown.
*
* target 자체 height는 건드리지 않음.
*/
$element
.stop(
true,
false
)
.slideDown(
{
duration:
DURATION,
easing:
EASING,
complete:
function () {
$element.css(
{
height:
'',
overflow:
''
}
);
if (
ownToggle
) {
ownToggle.style.visibility =
'';
}
}
}
);
}
/**
* 접기
*/
function collapse(
target,
element
) {
var $target =
$( target );
var $element =
$( element );
$element.stop(
true,
false
);
rememberTargetDisplay(
target
);
rememberDisplay(
element
);
/*
* 내부 요소만 slideUp.
*/
$element.slideUp(
{
duration:
DURATION,
easing:
EASING,
complete:
function () {
target.classList.add(
'mw-collapsed'
);
/*
* 자동 wrapper 구조에서는
* 바깥 target도 최종적으로 숨김.
*
* LACTEA 같은 display:none 교대형
* 구조 대응.
*/
if (
$( target ).data(
DATA_WRAPPED
)
) {
$target.css(
'display',
'none'
);
}
/*
* 다음에 다시 slideDown할 수 있도록
* 내부 wrapper의 display만 정리.
*/
$element.css(
{
height:
'',
overflow:
''
}
);
}
}
);
}
/**
* 그룹 전체 토글
*/
function toggleGroup(
toggle
) {
var name =
getToggleName(
toggle
);
if ( !name ) {
return false;
}
var targets =
getTargets(
name
);
if (
!targets ||
!targets.length
) {
return false;
}
var multipleTargets =
targets.length > 1;
var openedAny =
false;
var i;
for (
i = 0;
i < targets.length;
i++
) {
var target =
targets[ i ];
var element =
getAnimatedElement(
target
);
var collapsed =
isCollapsed(
target,
element
);
if ( collapsed ) {
expand(
target,
element,
name,
multipleTargets
);
openedAny =
true;
} else {
collapse(
target,
element
);
}
}
if (
targets.length === 1
) {
updateAria(
name,
openedAny
);
}
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
);
/**
* 초기 상태 저장
*/
function initializeDisplays(
root
) {
var collapsibles =
root.querySelectorAll(
'.mw-collapsible'
);
var i;
for (
i = 0;
i < collapsibles.length;
i++
) {
var element =
collapsibles[ i ];
var display =
window
.getComputedStyle(
element
)
.display;
if (
display &&
display !== 'none'
) {
$( element ).data(
DATA_DISPLAY,
display
);
}
/*
* 이미 mw-collapsible-content가 없는 구조는
* 초기화 시 wrapper를 미리 만들지 않음.
*
* 실제 클릭할 때 필요한 target만 생성해서
* 불필요한 DOM 변경을 줄임.
*/
}
}
/**
* 최초 로드
*/
$( function () {
initializeDisplays(
document
);
} );
/**
* MediaWiki 동적 콘텐츠 대응
*/
if (
mw &&
mw.hook
) {
mw.hook(
'wikipage.content'
).add(
function ( $content ) {
if (
$content &&
$content[ 0 ]
) {
initializeDisplays(
$content[ 0 ]
);
}
}
);
}
}( jQuery, mediaWiki ) );