미디어위키:Common.js: 두 판 사이의 차이

편집 요약 없음
편집 요약 없음
 
(같은 사용자의 중간 판 18개는 보이지 않습니다)
1번째 줄: 1번째 줄:
document.addEventListener(
/* MediaWiki:Common.js */
'click',
mw.hook('wikipage.content').add(function ($content) {
function ( event ) {
var toggle = event.target.closest(
'.mw-customtoggle'
);


if ( toggle ) {
    var initialized = {};
console.log(
'custom toggle clicked:',
toggle
);
}
},
true
);


/**
    $content.find('[class*="mw-customtoggle-"]').each(function () {
* MediaWiki 커스텀 펼치기 / 접기 애니메이션
        var classList = (this.className || '').split(/\s+/);
*
* 지원:
* - mw-customtoggle-XXX
* - mw-customcollapsible-XXX
* - 일반 mw-collapsible-content
* - 동일 ID 2개 교대형 구조
* - flex 내부 여러 토글
* - 중첩 표
*
* 효과:
* - 높이 애니메이션만 적용
* - opacity 없음
*/


( function ( $ ) {
        classList.forEach(function (cls) {
'use strict';
            if (cls.indexOf('mw-customtoggle-') !== 0) {
                return;
            }


var DURATION = 180;
            var suffix = cls.substring('mw-customtoggle-'.length);
var EASING = 'swing';


var VIEWPORT_CLASS =
            if (initialized[suffix]) {
'mw-custom-collapse-viewport';
                return;
            }


var INNER_CLASS =
            initialized[suffix] = true;
'mw-custom-collapse-viewport-inner';


var DATA_DISPLAY =
            var targetId = 'mw-customcollapsible-' + suffix;
'mwCustomCollapseOriginalDisplay';


            /*
            * 중복 ID가 있을 수 있으므로
            * #id 대신 attribute selector 사용
            */
            var $targets = $content.find('[id="' + targetId + '"]');


/**
            if (!$targets.length) {
* mw-customtoggle-XXX → XXX
                return;
*/
            }
function getToggleName( toggle ) {
var prefix =
'mw-customtoggle-';


var classes =
toggle.classList;


var i;
            /*
            * =====================================================
            * 특수형
            *
            * 아나운서 표처럼:
            *
            * [상단 버튼 wrapper]
            * [실제 내용 패널]
            *
            * 두 요소가 같은 ID를 공유하고,
            * 실제 패널 안에 wikitable이 있는 구조
            * =====================================================
            */
            var $specialPanel = $targets.filter(function () {
                return $(this).find('table.wikitable').length > 0;
            });


for (
            if ($targets.length > 1 && $specialPanel.length) {
i = 0;
                initStackedCollapsible(
i < classes.length;
                    $content,
i++
                    cls,
) {
                    $targets
if (
                );
classes[ i ].indexOf(
prefix
) === 0
) {
return classes[ i ].substring(
prefix.length
);
}
}


return null;
                return;
}
            }




/**
            /*
* attribute selector escape
            * =====================================================
*/
            * 일반 MediaWiki collapsible
function escapeAttribute( value ) {
            * =====================================================
return String( value )
            */
.replace(
            initNormalCollapsible(
/\\/g,
                $content,
'\\\\'
                cls,
)
                $targets.first()
.replace(
            );
/"/g,
        });
'\\"'
    });
);
}




/**
* 동일한 이름의 target 모두 검색
*/
function getTargets( name ) {
var id =
'mw-customcollapsible-' +
name;


return document.querySelectorAll(
    /*
'[id="' +
    * =============================================================
escapeAttribute( id ) +
    * 일반 펼치기 · 접기
'"]'
    * =============================================================
);
    */
}
    function initNormalCollapsible($content, cls, $target) {


        var $toggles = $content.find('.' + cls);


/**
        /*
* 직계 mw-collapsible-content 찾기
        * 일반 MediaWiki 구조:
*/
        *
function getDirectContent( target ) {
        * .mw-collapsible
var children =
        *   └ .mw-collapsible-content
target.children;
        *
        * content가 있으면 content만 애니메이션
        */
        var $contentTarget = $target.children('.mw-collapsible-content');


var i;
        if (!$contentTarget.length) {
            $contentTarget = $target.find('.mw-collapsible-content').first();
        }


for (
        var $animTarget = $contentTarget.length
i = 0;
            ? $contentTarget
i < children.length;
            : $target;
i++
) {
if (
children[ i ].classList &&
children[ i ].classList.contains(
'mw-collapsible-content'
)
) {
return children[ i ];
}
}


return null;
}


        /*
        * 현재 상태를 기준으로 초기 aria 설정
        */
        var initiallyOpen = $animTarget.is(':visible');


/**
        $toggles.attr(
* 현재 접힘 상태
            'aria-expanded',
*/
            initiallyOpen ? 'true' : 'false'
function isCollapsed( target ) {
        );
if (
target.classList.contains(
'mw-collapsed'
)
) {
return true;
}


return (
window.getComputedStyle(
target
).display === 'none'
);
}


        $toggles
            .off('click.smoothSlide')
            .on('click.smoothSlide', function (e) {


/**
                e.preventDefault();
* display 상태 기억
                e.stopImmediatePropagation();
*/
function rememberDisplay( target ) {
var display =
window.getComputedStyle(
target
).display;


if (
display &&
display !== 'none'
) {
$( target ).data(
DATA_DISPLAY,
display
);
}
}


                /*
                * 애니메이션 중 재클릭 방지
                */
                if ($animTarget.is(':animated')) {
                    return false;
                }


/**
* 펼칠 때 target의 display 복원
*/
function getOpenDisplay( target ) {
var saved =
$( target ).data(
DATA_DISPLAY
);


if ( saved ) {
                var opening = !$animTarget.is(':visible');
return saved;
}




/*
                /*
* HTML에 원래 display 값이 남아 있다면
                * -------------------------------------------------
* 그 값을 우선 사용
                * 펼치기
*/
                * -------------------------------------------------
if (
                */
target.style.display &&
                if (opening) {
target.style.display !== 'none'
) {
return target.style.display;
}


                    /*
                    * MediaWiki collapsed 상태 제거
                    */
                    $target.removeClass('mw-collapsed');


/*
* TABLE 계열
*/
switch ( target.tagName ) {


case 'TABLE':
                    /*
return 'table';
                    * 내용만 펼침
                    */
                    $animTarget
                        .stop(true, true)
                        .hide()
                        .slideDown(250, function () {


case 'TBODY':
                            /*
return 'table-row-group';
                            * jQuery 애니메이션 inline style 정리
                            */
                            $(this).css({
                                height: '',
                                overflow: '',
                                paddingTop: '',
                                paddingBottom: '',
                                marginTop: '',
                                marginBottom: ''
                            });
                        });


case 'THEAD':
return 'table-header-group';


case 'TFOOT':
                    $toggles.attr('aria-expanded', 'true');
return 'table-footer-group';
                }


case 'TR':
return 'table-row';


case 'TD':
                /*
case 'TH':
                * -------------------------------------------------
return 'table-cell';
                * 접기
                * -------------------------------------------------
                */
                else {


case 'LI':
                    $animTarget
return 'list-item';
                        .stop(true, true)
}
                        .slideUp(250, function () {


                            /*
                            * 애니메이션 종료 후 collapsed 복원
                            */
                            $target.addClass('mw-collapsed');


/*
                            $(this).css({
* mw-collapsible은 대부분 DIV
                                display: 'none',
*/
                                height: '',
return 'block';
                                overflow: '',
}
                                paddingTop: '',
                                paddingBottom: '',
                                marginTop: '',
                                marginBottom: ''
                            });
                        });




/**
                    $toggles.attr('aria-expanded', 'false');
* 애니메이션용 viewport 검색
                }
*/
function getViewport( target ) {
var children =
target.children;


var i;


for (
                return false;
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 높이만 변화시킴.
    *
*/
    * DOM 예:
function ensureViewport( target ) {
    *
var existing =
    * [남성 버튼 wrapper]
getViewport(
    * [남성 실제 패널]
target
    * [여성 버튼 wrapper]
);
    * [여성 실제 패널]
    *
    * 패널을 이동시키지 않고
    * 원래 DOM 위치에서 펼친다.
    *
    * 중요한 점:
    *
    * wrapper와 panel을 둘 다 애니메이션하지 않는다.
    *
    * 버튼 wrapper는 즉시 교체하고,
    * 실제 패널 하나만 slide 애니메이션한다.
    *
    * 따라서 이중 움직임이나
    * 중간 겹침 현상이 최소화된다.
    * =============================================================
    */
    function initStackedCollapsible($content, cls, $targets) {


if ( existing ) {
        /*
return existing;
        * wikitable이 들어 있는 요소를 실제 패널로 사용
}
        */
        var $panel = $targets.filter(function () {
            return $(this).find('table.wikitable').length > 0;
        }).first();


        if (!$panel.length) {
            return;
        }


var viewport =
document.createElement(
'div'
);


var inner =
        /*
document.createElement(
        * 실제 패널이 아닌 동일 ID 요소 중
'div'
        * 해당 toggle을 포함하는 요소 = 상단 버튼 wrapper
);
        */
        var $topWrapper = $targets.not($panel).filter(function () {
            return $(this).find('.' + cls).length > 0;
        }).first();


        if (!$topWrapper.length) {
            return;
        }


viewport.className =
VIEWPORT_CLASS;


inner.className =
        var $topToggle = $topWrapper.find('.' + cls).first();
INNER_CLASS;
        var $panelToggle = $panel.find('.' + cls).first();




/*
        if (!$topToggle.length || !$panelToggle.length) {
* MediaWiki 표준 구조라면
            return;
* mw-collapsible-content의 내용만 이동
        }
*/
var content =
getDirectContent(
target
);




if ( content ) {


while (
        /*
content.firstChild
        * =========================================================
) {
        * 최초 상태
inner.appendChild(
        * =========================================================
content.firstChild
        */
);
        $panel
}
            .stop(true, true)
            .addClass('mw-collapsed')
            .hide()
            .css({
                height: '',
                overflow: '',
                paddingTop: '',
                paddingBottom: '',
                marginTop: '',
                marginBottom: ''
            });




viewport.appendChild(
        $topWrapper
inner
            .stop(true, true)
);
            .show()
            .css({
                height: '',
                overflow: '',
                paddingTop: '',
                paddingBottom: '',
                marginTop: '',
                marginBottom: ''
            });




content.appendChild(
        $topToggle.attr('aria-expanded', 'false');
viewport
        $panelToggle.attr('aria-expanded', 'false');
);




return viewport;
}


        /*
        * =========================================================
        * 위쪽 버튼 클릭 → 펼치기
        * =========================================================
        */
        $topToggle
            .off('click.smoothSlide')
            .on('click.smoothSlide', function (e) {


/*
                e.preventDefault();
* mw-collapsible-content가 없는 구조
                e.stopImmediatePropagation();
*
* target 내부 전체를 viewport 안으로 이동
*/
while (
target.firstChild
) {
inner.appendChild(
target.firstChild
);
}




viewport.appendChild(
                /*
inner
                * 애니메이션 중 재클릭 방지
);
                */
                if ($panel.is(':animated')) {
                    return false;
                }




target.appendChild(
                /*
viewport
                * 이미 열려 있으면 중단
);
                */
                if ($panel.is(':visible')) {
                    return false;
                }




return viewport;
                /*
}
                * -------------------------------------------------
                * 상단 버튼 wrapper는 즉시 숨김
                *
                * wrapper 자체를 애니메이션하지 않으므로
                * 이중 움직임이 발생하지 않는다.
                * -------------------------------------------------
                */
                $topWrapper
                    .stop(true, true)
                    .hide();




/**
                /*
* viewport inner
                * MediaWiki collapsed 상태 제거
*/
                */
function getViewportInner(
                $panel.removeClass('mw-collapsed');
viewport
) {
var children =
viewport.children;


var i;


for (
                /*
i = 0;
                * -------------------------------------------------
i < children.length;
                * 실제 패널 하나만 부드럽게 펼침
i++
                * -------------------------------------------------
) {
                */
if (
                $panel
children[ i ].classList &&
                    .stop(true, true)
children[ i ].classList.contains(
                    .hide()
INNER_CLASS
                    .slideDown(260, function () {
)
) {
return children[ i ];
}
}


return null;
                        /*
}
                        * animation 종료 후
                        * jQuery가 만든 inline style 정리
                        */
                        $(this).css({
                            height: '',
                            overflow: '',
                            paddingTop: '',
                            paddingBottom: '',
                            marginTop: '',
                            marginBottom: ''
                        });
                    });




/**
                /*
* target 안의 자기 토글 검색
                * ARIA
*
                */
* 동일 ID 2개 교대형에서 사용.
                $topToggle.attr('aria-expanded', 'true');
*/
                $panelToggle.attr('aria-expanded', 'true');
function getOwnToggle(
target,
name
) {
var className =
'mw-customtoggle-' +
name;


var elements =
target.getElementsByClassName(
className
);


if (
                return false;
elements.length
            });
) {
return elements[ 0 ];
}


return null;
}




/**
        /*
* 펼치기
        * =========================================================
*/
        * 패널 안쪽 버튼 클릭 → 접기
function expand(
        * =========================================================
target,
        */
name,
        $panelToggle
multipleTargets
            .off('click.smoothSlide')
) {
            .on('click.smoothSlide', function (e) {
var $target =
$( target );


                e.preventDefault();
                e.stopImmediatePropagation();


/*
* 진행 중인 target animation 중단
*/
$target.stop(
true,
true
);


                /*
                * 애니메이션 중 재클릭 방지
                */
                if ($panel.is(':animated')) {
                    return false;
                }


target.classList.remove(
'mw-collapsed'
);


                /*
                * -------------------------------------------------
                * 실제 패널 하나만 접음
                * -------------------------------------------------
                */
                $panel
                    .stop(true, true)
                    .slideUp(260, function () {


/*
                        /*
* 바깥 target 먼저 표시
                        * 패널을 완전히 닫은 뒤
*/
                        * collapsed 상태 복원
target.style.display =
                        */
getOpenDisplay(
                        $(this)
target
                            .addClass('mw-collapsed')
);
                            .css({
                                display: 'none',
                                height: '',
                                overflow: '',
                                paddingTop: '',
                                paddingBottom: '',
                                marginTop: '',
                                marginBottom: ''
                            });




var viewport =
                        /*
ensureViewport(
                        * -------------------------------------------------
target
                        * 원래 상단 버튼 즉시 복귀
);
                        *
                        * 별도 애니메이션을 하지 않기 때문에
                        * 닫힘 동작도 한 번의 움직임으로 보인다.
                        * -------------------------------------------------
                        */
                        $topWrapper
                            .stop(true, true)
                            .show()
                            .css({
                                height: '',
                                overflow: '',
                                paddingTop: '',
                                paddingBottom: '',
                                marginTop: '',
                                marginBottom: ''
                            });
                    });




var inner =
                /*
getViewportInner(
                * ARIA
viewport
                */
);
                $topToggle.attr('aria-expanded', 'false');
                $panelToggle.attr('aria-expanded', 'false');




if ( !inner ) {
                return false;
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 ) );

2026년 8월 20일 (목) 15:13 기준 최신판

/* MediaWiki:Common.js */
mw.hook('wikipage.content').add(function ($content) {

    var initialized = {};

    $content.find('[class*="mw-customtoggle-"]').each(function () {
        var classList = (this.className || '').split(/\s+/);

        classList.forEach(function (cls) {
            if (cls.indexOf('mw-customtoggle-') !== 0) {
                return;
            }

            var suffix = cls.substring('mw-customtoggle-'.length);

            if (initialized[suffix]) {
                return;
            }

            initialized[suffix] = true;

            var targetId = 'mw-customcollapsible-' + suffix;

            /*
             * 중복 ID가 있을 수 있으므로
             * #id 대신 attribute selector 사용
             */
            var $targets = $content.find('[id="' + targetId + '"]');

            if (!$targets.length) {
                return;
            }


            /*
             * =====================================================
             * 특수형
             *
             * 아나운서 표처럼:
             *
             * [상단 버튼 wrapper]
             * [실제 내용 패널]
             *
             * 두 요소가 같은 ID를 공유하고,
             * 실제 패널 안에 wikitable이 있는 구조
             * =====================================================
             */
            var $specialPanel = $targets.filter(function () {
                return $(this).find('table.wikitable').length > 0;
            });

            if ($targets.length > 1 && $specialPanel.length) {
                initStackedCollapsible(
                    $content,
                    cls,
                    $targets
                );

                return;
            }


            /*
             * =====================================================
             * 일반 MediaWiki collapsible
             * =====================================================
             */
            initNormalCollapsible(
                $content,
                cls,
                $targets.first()
            );
        });
    });



    /*
     * =============================================================
     * 일반 펼치기 · 접기
     * =============================================================
     */
    function initNormalCollapsible($content, cls, $target) {

        var $toggles = $content.find('.' + cls);

        /*
         * 일반 MediaWiki 구조:
         *
         * .mw-collapsible
         *   └ .mw-collapsible-content
         *
         * content가 있으면 content만 애니메이션
         */
        var $contentTarget = $target.children('.mw-collapsible-content');

        if (!$contentTarget.length) {
            $contentTarget = $target.find('.mw-collapsible-content').first();
        }

        var $animTarget = $contentTarget.length
            ? $contentTarget
            : $target;


        /*
         * 현재 상태를 기준으로 초기 aria 설정
         */
        var initiallyOpen = $animTarget.is(':visible');

        $toggles.attr(
            'aria-expanded',
            initiallyOpen ? 'true' : 'false'
        );


        $toggles
            .off('click.smoothSlide')
            .on('click.smoothSlide', function (e) {

                e.preventDefault();
                e.stopImmediatePropagation();


                /*
                 * 애니메이션 중 재클릭 방지
                 */
                if ($animTarget.is(':animated')) {
                    return false;
                }


                var opening = !$animTarget.is(':visible');


                /*
                 * -------------------------------------------------
                 * 펼치기
                 * -------------------------------------------------
                 */
                if (opening) {

                    /*
                     * MediaWiki collapsed 상태 제거
                     */
                    $target.removeClass('mw-collapsed');


                    /*
                     * 내용만 펼침
                     */
                    $animTarget
                        .stop(true, true)
                        .hide()
                        .slideDown(250, function () {

                            /*
                             * jQuery 애니메이션 inline style 정리
                             */
                            $(this).css({
                                height: '',
                                overflow: '',
                                paddingTop: '',
                                paddingBottom: '',
                                marginTop: '',
                                marginBottom: ''
                            });
                        });


                    $toggles.attr('aria-expanded', 'true');
                }


                /*
                 * -------------------------------------------------
                 * 접기
                 * -------------------------------------------------
                 */
                else {

                    $animTarget
                        .stop(true, true)
                        .slideUp(250, function () {

                            /*
                             * 애니메이션 종료 후 collapsed 복원
                             */
                            $target.addClass('mw-collapsed');

                            $(this).css({
                                display: 'none',
                                height: '',
                                overflow: '',
                                paddingTop: '',
                                paddingBottom: '',
                                marginTop: '',
                                marginBottom: ''
                            });
                        });


                    $toggles.attr('aria-expanded', 'false');
                }


                return false;
            });
    }



    /*
     * =============================================================
     * 아나운서형 특수 펼치기 · 접기
     * =============================================================
     *
     * DOM 예:
     *
     * [남성 버튼 wrapper]
     * [남성 실제 패널]
     * [여성 버튼 wrapper]
     * [여성 실제 패널]
     *
     * 패널을 이동시키지 않고
     * 원래 DOM 위치에서 펼친다.
     *
     * 중요한 점:
     *
     * wrapper와 panel을 둘 다 애니메이션하지 않는다.
     *
     * 버튼 wrapper는 즉시 교체하고,
     * 실제 패널 하나만 slide 애니메이션한다.
     *
     * 따라서 이중 움직임이나
     * 중간 겹침 현상이 최소화된다.
     * =============================================================
     */
    function initStackedCollapsible($content, cls, $targets) {

        /*
         * wikitable이 들어 있는 요소를 실제 패널로 사용
         */
        var $panel = $targets.filter(function () {
            return $(this).find('table.wikitable').length > 0;
        }).first();

        if (!$panel.length) {
            return;
        }


        /*
         * 실제 패널이 아닌 동일 ID 요소 중
         * 해당 toggle을 포함하는 요소 = 상단 버튼 wrapper
         */
        var $topWrapper = $targets.not($panel).filter(function () {
            return $(this).find('.' + cls).length > 0;
        }).first();

        if (!$topWrapper.length) {
            return;
        }


        var $topToggle = $topWrapper.find('.' + cls).first();
        var $panelToggle = $panel.find('.' + cls).first();


        if (!$topToggle.length || !$panelToggle.length) {
            return;
        }



        /*
         * =========================================================
         * 최초 상태
         * =========================================================
         */
        $panel
            .stop(true, true)
            .addClass('mw-collapsed')
            .hide()
            .css({
                height: '',
                overflow: '',
                paddingTop: '',
                paddingBottom: '',
                marginTop: '',
                marginBottom: ''
            });


        $topWrapper
            .stop(true, true)
            .show()
            .css({
                height: '',
                overflow: '',
                paddingTop: '',
                paddingBottom: '',
                marginTop: '',
                marginBottom: ''
            });


        $topToggle.attr('aria-expanded', 'false');
        $panelToggle.attr('aria-expanded', 'false');



        /*
         * =========================================================
         * 위쪽 버튼 클릭 → 펼치기
         * =========================================================
         */
        $topToggle
            .off('click.smoothSlide')
            .on('click.smoothSlide', function (e) {

                e.preventDefault();
                e.stopImmediatePropagation();


                /*
                 * 애니메이션 중 재클릭 방지
                 */
                if ($panel.is(':animated')) {
                    return false;
                }


                /*
                 * 이미 열려 있으면 중단
                 */
                if ($panel.is(':visible')) {
                    return false;
                }


                /*
                 * -------------------------------------------------
                 * 상단 버튼 wrapper는 즉시 숨김
                 *
                 * wrapper 자체를 애니메이션하지 않으므로
                 * 이중 움직임이 발생하지 않는다.
                 * -------------------------------------------------
                 */
                $topWrapper
                    .stop(true, true)
                    .hide();


                /*
                 * MediaWiki collapsed 상태 제거
                 */
                $panel.removeClass('mw-collapsed');


                /*
                 * -------------------------------------------------
                 * 실제 패널 하나만 부드럽게 펼침
                 * -------------------------------------------------
                 */
                $panel
                    .stop(true, true)
                    .hide()
                    .slideDown(260, function () {

                        /*
                         * animation 종료 후
                         * jQuery가 만든 inline style 정리
                         */
                        $(this).css({
                            height: '',
                            overflow: '',
                            paddingTop: '',
                            paddingBottom: '',
                            marginTop: '',
                            marginBottom: ''
                        });
                    });


                /*
                 * ARIA
                 */
                $topToggle.attr('aria-expanded', 'true');
                $panelToggle.attr('aria-expanded', 'true');


                return false;
            });



        /*
         * =========================================================
         * 패널 안쪽 버튼 클릭 → 접기
         * =========================================================
         */
        $panelToggle
            .off('click.smoothSlide')
            .on('click.smoothSlide', function (e) {

                e.preventDefault();
                e.stopImmediatePropagation();


                /*
                 * 애니메이션 중 재클릭 방지
                 */
                if ($panel.is(':animated')) {
                    return false;
                }


                /*
                 * -------------------------------------------------
                 * 실제 패널 하나만 접음
                 * -------------------------------------------------
                 */
                $panel
                    .stop(true, true)
                    .slideUp(260, function () {

                        /*
                         * 패널을 완전히 닫은 뒤
                         * collapsed 상태 복원
                         */
                        $(this)
                            .addClass('mw-collapsed')
                            .css({
                                display: 'none',
                                height: '',
                                overflow: '',
                                paddingTop: '',
                                paddingBottom: '',
                                marginTop: '',
                                marginBottom: ''
                            });


                        /*
                         * -------------------------------------------------
                         * 원래 상단 버튼 즉시 복귀
                         *
                         * 별도 애니메이션을 하지 않기 때문에
                         * 닫힘 동작도 한 번의 움직임으로 보인다.
                         * -------------------------------------------------
                         */
                        $topWrapper
                            .stop(true, true)
                            .show()
                            .css({
                                height: '',
                                overflow: '',
                                paddingTop: '',
                                paddingBottom: '',
                                marginTop: '',
                                marginBottom: ''
                            });
                    });


                /*
                 * ARIA
                 */
                $topToggle.attr('aria-expanded', 'false');
                $panelToggle.attr('aria-expanded', 'false');


                return false;
            });
    }

});