참고: 설정을 저장한 후에 바뀐 점을 확인하기 위해서는 브라우저의 캐시를 새로 고쳐야 합니다.
- 파이어폭스 / 사파리: Shift 키를 누르면서 새로 고침을 클릭하거나, Ctrl-F5 또는 Ctrl-R을 입력 (Mac에서는 ⌘-R)
- 구글 크롬: Ctrl-Shift-R키를 입력 (Mac에서는 ⌘-Shift-R)
- 인터넷 익스플로러 / 엣지: Ctrl 키를 누르면서 새로 고침을 클릭하거나, Ctrl-F5를 입력.
- 오페라: Ctrl-F5를 입력.
/**
* MediaWiki 커스텀 펼치기 / 접기 애니메이션
*
* 지원:
*
* 1. 일반 구조
*
* .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
*
*
* 애니메이션:
* height만 사용
* opacity 사용하지 않음
*/
( function ( $, mw ) {
'use strict';
var DURATION = 300;
var EASING = 'swing';
var DATA_DISPLAY =
'mwCustomCollapseDisplay';
/**
* 토글 클래스에서 식별자 추출
*
* 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 검색
*
* 동일 ID가 여러 개 있어도 전부 가져옴.
*/
function getTargets( name ) {
if ( !name ) {
return [];
}
var id =
'mw-customcollapsible-' +
name;
return document.querySelectorAll(
'[id="' +
escapeAttribute( id ) +
'"]'
);
}
/**
* target의 직계
* .mw-collapsible-content 검색
*
* 중첩된 collapsible의 content를
* 잘못 잡지 않도록 children만 검사.
*/
function getDirectContent( target ) {
var children =
target.children;
var i;
for (
i = 0;
i < children.length;
i++
) {
if (
children[ i ]
.classList
.contains(
'mw-collapsible-content'
)
) {
return children[ i ];
}
}
return null;
}
/**
* 실제 애니메이션할 요소
*
* 일반 MediaWiki:
* .mw-collapsible-content
*
* 교대형:
* target 자체
*/
function getAnimatedElement( target ) {
var content =
getDirectContent(
target
);
if ( content ) {
return content;
}
return target;
}
/**
* target 자신의 직계 자식 중
* 해당 name의 토글 찾기
*
* 교대형 구조에서 펼쳐지는 쪽의
* 버튼 텍스트를 잠시 숨기기 위해 사용.
*/
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 ];
}
}
return null;
}
/**
* 현재 접힌 상태인지 확인
*/
function isCollapsed(
target,
element
) {
if (
target.classList.contains(
'mw-collapsed'
)
) {
return true;
}
return (
window
.getComputedStyle(
element
)
.display === 'none'
);
}
/**
* 현재 display값 기억
*
* inline-block 등을 다시 복원하기 위해 사용.
*/
function rememberDisplay( element ) {
var display =
window
.getComputedStyle(
element
)
.display;
if (
display &&
display !== 'none'
) {
$( element ).data(
DATA_DISPLAY,
display
);
}
}
/**
* 펼칠 때 사용할 display 계산
*/
function getOpenDisplay( element ) {
var stored =
$( element ).data(
DATA_DISPLAY
);
if ( stored ) {
return stored;
}
/*
* 유효한 인라인 display가 있으면 사용
*/
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';
}
/*
* mw-collapsible 자체인 경우
*
* 100% 폭이면 block,
* 그 외에는 inline-block 우선.
*/
if (
element.classList.contains(
'mw-collapsible'
)
) {
if (
element.style.width === '100%' ||
element.style.width === '100.0%'
) {
return 'block';
}
return 'inline-block';
}
return 'block';
}
/**
* 같은 name을 가진 모든 toggle의
* 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'
);
}
}
/**
* 펼치기
*
* hideOwnToggle이 true인 경우
* 펼쳐지는 쪽의 버튼 텍스트는
* 애니메이션 종료까지 visibility:hidden.
*
* 공간 자체는 유지되므로
* 레이아웃이 갑자기 변하지 않음.
*/
function expand(
target,
element,
name,
hideOwnToggle
) {
var $element =
$( element );
var ownToggle =
null;
if (
hideOwnToggle &&
element === target
) {
ownToggle =
getOwnToggle(
target,
name
);
if ( ownToggle ) {
ownToggle.style.visibility =
'hidden';
}
}
$element.stop(
true,
false
);
target.classList.remove(
'mw-collapsed'
);
/**
* 일반 MediaWiki 구조
*
* target
* └─ .mw-collapsible-content
*/
if (
element !== target
) {
$element
.stop(
true,
false
)
.slideDown(
{
duration:
DURATION,
easing:
EASING,
complete:
function () {
$element.css(
{
height:
'',
overflow:
''
}
);
if (
ownToggle
) {
ownToggle.style.visibility =
'';
}
}
}
);
return;
}
/**
* target 자체가 display:none인 구조
*/
var display =
getOpenDisplay(
element
);
$element.css(
{
display:
display,
height:
'auto',
overflow:
'hidden'
}
);
var targetHeight =
element.scrollHeight;
$element.css(
{
height:
'0px'
}
);
/*
* 강제 reflow
*/
void element.offsetHeight;
$element.animate(
{
height:
targetHeight
},
{
duration:
DURATION,
easing:
EASING,
complete:
function () {
$element.css(
{
height:
'',
overflow:
''
}
);
/*
* 펼침이 완전히 끝난 뒤
* 내부 버튼 표시
*/
if (
ownToggle
) {
ownToggle.style.visibility =
'';
}
}
}
);
}
/**
* 접기
*/
function collapse(
target,
element
) {
var $element =
$( element );
$element.stop(
true,
false
);
rememberDisplay(
element
);
/**
* 일반 MediaWiki 구조
*/
if (
element !== target
) {
$element.slideUp(
{
duration:
DURATION,
easing:
EASING,
complete:
function () {
target.classList.add(
'mw-collapsed'
);
$element.css(
{
height:
'',
overflow:
''
}
);
}
}
);
return;
}
/**
* target 자체가 접히는 구조
*/
var startHeight =
element
.getBoundingClientRect()
.height;
$element.css(
{
height:
startHeight +
'px',
overflow:
'hidden'
}
);
void element.offsetHeight;
$element.animate(
{
height:
0
},
{
duration:
DURATION,
easing:
EASING,
complete:
function () {
target.classList.add(
'mw-collapsed'
);
$element.css(
{
display:
'none',
height:
'',
overflow:
''
}
);
}
}
);
}
/**
* 그룹 전체 토글
*
* target이 하나라면 일반 토글.
*
* 동일 ID target이 여러 개라면
* 각 요소를 현재 상태 반대로 변경.
*
* LACTEA:
*
* A = 상단 버튼
* B = 실제 내용
*
* 클릭 전:
*
* A 열림
* B 닫힘
*
* 클릭 후:
*
* A 닫힘
* B 열림
*/
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 i;
var openedAny =
false;
for (
i = 0;
i < targets.length;
i++
) {
var target =
targets[ i ];
var element =
getAnimatedElement(
target
);
var collapsed =
isCollapsed(
target,
element
);
if ( collapsed ) {
/*
* 동일 ID가 여러 개인 경우
* 새로 펼쳐지는 쪽 토글을
* 애니메이션 도중 숨김.
*
* 따라서
*
* [ 음반 목록 ]
* [ 음반 목록 ]
*
* 처럼 겹쳐 보이지 않음.
*/
expand(
target,
element,
name,
multipleTargets
);
openedAny =
true;
} else {
collapse(
target,
element
);
}
}
/*
* 일반적인 target 1개 구조만
* ARIA 상태를 일괄 갱신.
*
* 교대형은 두 target의 상태가
* 서로 반대이므로 제외.
*/
if (
targets.length === 1
) {
updateAria(
name,
openedAny
);
}
return true;
}
/**
* 클릭
*
* capture=true를 사용해서
* MediaWiki 기본 customtoggle보다
* 먼저 처리.
*/
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 상태 저장
*
* display:inline-block 같은 값을
* 나중에 정확히 복원하기 위한 처리.
*/
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
);
}
}
}
/**
* 최초 페이지 로드
*/
$( function () {
initializeDisplays(
document
);
} );
/**
* MediaWiki 동적 콘텐츠 대응
*
* AJAX / Lua / 확장기능 등으로
* 나중에 들어온 콘텐츠도 처리.
*/
if (
mw &&
mw.hook
) {
mw.hook(
'wikipage.content'
).add(
function ( $content ) {
if (
$content &&
$content[ 0 ]
) {
initializeDisplays(
$content[ 0 ]
);
}
}
);
}
}( jQuery, mediaWiki ) );