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

편집 요약 없음
편집 요약 없음
 
(같은 사용자의 중간 판 37개는 보이지 않습니다)
1번째 줄: 1번째 줄:
/**
/* MediaWiki:Common.js */
* MediaWiki 커스텀 펼치기/접기 슬라이드 애니메이션
mw.hook('wikipage.content').add(function ($content) {
*
* 같은 mw-customcollapsible-XXX ID가 2개 존재하는 틀 지원
*
* 예:
*
* [첫 번째]
* <div id="mw-customcollapsible-XXX">
*    <div class="mw-customtoggle-XXX">[ 버튼 ]</div>
* </div>
*
* [두 번째]
* <div id="mw-customcollapsible-XXX" class="mw-collapsed">
*    <div class="mw-customtoggle-XXX">[ 버튼 ]</div>
*    <table>내용</table>
* </div>
*
* 틀 원본 수정 필요 없음.
*/


( function () {
    var initialized = {};
'use strict';


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


var INITIALIZED = 'data-slide-collapse-ready';
        classList.forEach(function (cls) {
var CONTENT_CLASS = 'mw-slide-collapse-content';
            if (cls.indexOf('mw-customtoggle-') !== 0) {
var ANIMATING_CLASS = 'mw-slide-collapse-animating';
                return;
            }


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


/* --------------------------------
            if (initialized[suffix]) {
* mw-customtoggle-XXX 에서 XXX 추출
                return;
* -------------------------------- */
            }


function getName( toggle ) {
            initialized[suffix] = true;
var prefix = 'mw-customtoggle-';
var classes = toggle.classList;
var i;


for ( i = 0; i < classes.length; i++ ) {
            var targetId = 'mw-customcollapsible-' + suffix;
if ( classes[ i ].indexOf( prefix ) === 0 ) {
return classes[ i ].substring(
prefix.length
);
}
}


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


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


/* --------------------------------
* 같은 ID를 가진 collapsible 전부 찾기
*
* getElementById() 사용하지 않음!
* -------------------------------- */


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


var id =
            if ($targets.length > 1 && $specialPanel.length) {
'mw-customcollapsible-' + name;
                initStackedCollapsible(
                    $content,
                    cls,
                    $targets
                );


return Array.prototype.slice.call(
                return;
document.querySelectorAll(
            }
'[id="' +
CSS.escape( id ) +
'"]'
)
);
}




/* --------------------------------
            /*
* 실제 내용이 들어있는 collapsible 찾기
            * =====================================================
*
            * 일반 MediaWiki collapsible
* table 등이 들어있는 두 번째 div를 선택
            * =====================================================
* -------------------------------- */
            */
            initNormalCollapsible(
                $content,
                cls,
                $targets.first()
            );
        });
    });


function getContentTarget( name ) {
var targets = getTargets( name );
var i;


/*
* 버튼 외에 다른 요소가 들어있는 target을 찾는다.
*/
for ( i = 0; i < targets.length; i++ ) {
var target = targets[ i ];
var toggle = target.querySelector(
'[class*="mw-customtoggle-"]'
);


if ( !toggle ) {
    /*
continue;
    * =============================================================
}
    * 일반 펼치기 · 접기
    * =============================================================
    */
    function initNormalCollapsible($content, cls, $target) {


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


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


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


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


/* --------------------------------
* 실제 내용 wrapper 생성
*
* table 자체의 높이를 변경하지 않고
* 바깥 DIV 높이를 애니메이션
* -------------------------------- */


function prepareContent( target ) {
        /*
var existing =
        * 현재 상태를 기준으로 초기 aria 설정
target.querySelector(
        */
':scope > .' + CONTENT_CLASS
        var initiallyOpen = $animTarget.is(':visible');
);


if ( existing ) {
        $toggles.attr(
return existing;
            'aria-expanded',
}
            initiallyOpen ? 'true' : 'false'
        );


var toggle =
target.querySelector(
':scope > [class*="mw-customtoggle-"]'
);


if ( !toggle ) {
        $toggles
return null;
            .off('click.smoothSlide')
}
            .on('click.smoothSlide', function (e) {


var firstContent =
                e.preventDefault();
toggle.nextSibling;
                e.stopImmediatePropagation();


if ( !firstContent ) {
return null;
}


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


wrapper.className =
CONTENT_CLASS;


/*
                var opening = !$animTarget.is(':visible');
* toggle 뒤의 모든 노드를 wrapper로 이동
*/
var node = firstContent;
var nodes = [];


while ( node ) {
nodes.push( node );
node = node.nextSibling;
}


nodes.forEach( function ( child ) {
                /*
wrapper.appendChild( child );
                * -------------------------------------------------
} );
                * 펼치기
                * -------------------------------------------------
                */
                if (opening) {


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


return wrapper;
}


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


/* --------------------------------
                            /*
* 같은 이름의 토글들 aria 상태 변경
                            * jQuery 애니메이션 inline style 정리
* -------------------------------- */
                            */
                            $(this).css({
                                height: '',
                                overflow: '',
                                paddingTop: '',
                                paddingBottom: '',
                                marginTop: '',
                                marginBottom: ''
                            });
                        });


function setToggleState( name, expanded ) {
var selector =
'.mw-customtoggle-' +
CSS.escape( name );


var toggles =
                    $toggles.attr('aria-expanded', 'true');
document.querySelectorAll( selector );
                }


toggles.forEach( function ( toggle ) {
toggle.setAttribute(
'aria-expanded',
expanded ? 'true' : 'false'
);
} );
}


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


/* --------------------------------
                    $animTarget
* 펼치기
                        .stop(true, true)
* -------------------------------- */
                        .slideUp(250, function () {


function expand( name, target, content ) {
                            /*
if (
                            * 애니메이션 종료 후 collapsed 복원
content.classList.contains(
                            */
ANIMATING_CLASS
                            $target.addClass('mw-collapsed');
)
) {
return;
}


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


/*
* MediaWiki 접힌 상태 제거
*/
target.classList.remove(
'mw-collapsed'
);


/*
                    $toggles.attr('aria-expanded', 'false');
* 부모 자체가 display:none 된 경우 복구
                }
*/
target.style.display =
target.dataset.originalDisplay ||
'inline-block';


/*
* 내용 표시
*/
content.style.display = 'block';
content.style.overflow = 'hidden';


/*
                return false;
* 자연 높이를 먼저 계산
            });
*/
    }
content.style.height = 'auto';


var endHeight =
content.scrollHeight;


/*
* 시작 높이
*/
content.style.height = '0px';


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


content.style.transition =
        /*
'height ' +
        * wikitable이 들어 있는 요소를 실제 패널로 사용
DURATION +
        */
'ms cubic-bezier(0.4, 0, 0.2, 1)';
        var $panel = $targets.filter(function () {
            return $(this).find('table.wikitable').length > 0;
        }).first();


requestAnimationFrame(
        if (!$panel.length) {
function () {
            return;
content.style.height =
        }
endHeight + 'px';
}
);


setToggleState(
name,
true
);


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


content.classList.remove(
        if (!$topWrapper.length) {
ANIMATING_CLASS
            return;
);
        }
},
DURATION + 30
);
}




/* --------------------------------
        var $topToggle = $topWrapper.find('.' + cls).first();
* 접기
        var $panelToggle = $panel.find('.' + cls).first();
* -------------------------------- */


function collapse( name, target, content ) {
if (
content.classList.contains(
ANIMATING_CLASS
)
) {
return;
}


content.classList.add(
        if (!$topToggle.length || !$panelToggle.length) {
ANIMATING_CLASS
            return;
);
        }


var startHeight =
content.getBoundingClientRect()
.height;


content.style.display = 'block';
content.style.overflow = 'hidden';


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


/*
* 현재 높이 확정
*/
content.offsetHeight;


content.style.transition =
        $topWrapper
'height ' +
            .stop(true, true)
DURATION +
            .show()
'ms cubic-bezier(0.4, 0, 0.2, 1)';
            .css({
                height: '',
                overflow: '',
                paddingTop: '',
                paddingBottom: '',
                marginTop: '',
                marginBottom: ''
            });


requestAnimationFrame(
function () {
content.style.height =
'0px';
}
);


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


window.setTimeout(
function () {
content.style.transition = '';
content.style.height = '';
content.style.overflow = '';
content.style.display = 'none';


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


content.classList.remove(
        /*
ANIMATING_CLASS
        * =========================================================
);
        * 위쪽 버튼 클릭 → 펼치기
},
        * =========================================================
DURATION + 30
        */
);
        $topToggle
}
            .off('click.smoothSlide')
            .on('click.smoothSlide', function (e) {


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


/* --------------------------------
* 현재 열린 상태인지 검사
* -------------------------------- */


function isOpen( target, content ) {
                /*
if (
                * 애니메이션 중 재클릭 방지
target.classList.contains(
                */
'mw-collapsed'
                if ($panel.is(':animated')) {
)
                    return false;
) {
                }
return false;
}


if (
window.getComputedStyle(
content
).display === 'none'
) {
return false;
}


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




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


function handleToggle( event ) {
var toggle =
event.target.closest(
'[class*="mw-customtoggle-"]'
);


if ( !toggle ) {
                /*
return;
                * MediaWiki collapsed 상태 제거
}
                */
                $panel.removeClass('mw-collapsed');


var name =
getName( toggle );


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


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


/*
* 우리가 처리할 수 없는 구조면
* MediaWiki 기본 기능을 그대로 사용
*/
if ( !target ) {
return;
}


var content =
                /*
prepareContent( target );
                * ARIA
                */
                $topToggle.attr('aria-expanded', 'true');
                $panelToggle.attr('aria-expanded', 'true');


if ( !content ) {
return;
}


/*
                return false;
* 여기까지 왔을 때만
            });
* MediaWiki 기본 즉시 토글 차단
*/
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation();


if (
isOpen(
target,
content
)
) {
collapse(
name,
target,
content
);
} else {
expand(
name,
target,
content
);
}
}




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


function initialize() {
                e.preventDefault();
var toggles =
                e.stopImmediatePropagation();
document.querySelectorAll(
'[class*="mw-customtoggle-"]'
);


toggles.forEach(
function ( toggle ) {
if (
toggle.hasAttribute(
INITIALIZED
)
) {
return;
}


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


if ( !name ) {
return;
}


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


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


var content =
prepareContent(
target
);


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


/*
* 원래 display 기억
*/
if (
!target.dataset.originalDisplay
) {
target.dataset.originalDisplay =
target.style.display ||
'inline-block';
}


/*
                /*
* 원래 mw-collapsed인 경우
                * ARIA
* 내용만 숨긴다.
                */
*
                $topToggle.attr('aria-expanded', 'false');
* 버튼 부모 전체를 숨기지 않음.
                $panelToggle.attr('aria-expanded', 'false');
*/
if (
target.classList.contains(
'mw-collapsed'
)
) {
content.style.display =
'none';


setToggleState(
name,
false
);
}


toggle.setAttribute(
                return false;
INITIALIZED,
            });
'1'
    }
);
}
);
}


 
});
/* --------------------------------
* 클릭 이벤트
*
* capture에서 처리하되,
* 실제 target을 찾은 경우에만
* MediaWiki 기본 이벤트 차단
* -------------------------------- */
 
document.addEventListener(
'click',
handleToggle,
true
);
 
 
/* --------------------------------
* 키보드
* -------------------------------- */
 
document.addEventListener(
'keydown',
function ( event ) {
if (
event.key !== 'Enter' &&
event.key !== ' '
) {
return;
}
 
handleToggle( event );
},
true
);
 
 
/* --------------------------------
* 실행
* -------------------------------- */
 
if (
document.readyState === 'loading'
) {
document.addEventListener(
'DOMContentLoaded',
initialize
);
} else {
initialize();
}
 
 
/*
* MediaWiki 동적 로딩 대응
*/
if (
window.mw &&
mw.hook
) {
mw.hook(
'wikipage.content'
).add(
function () {
initialize();
}
);
}
 
}() );

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;
            });
    }

});