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

편집 요약 없음
편집 요약 없음
23번째 줄: 23번째 줄:


             /*
             /*
             * ID 중복 가능성을 고려해서 attribute selector 사용
             * 중복 ID가 있을 수 있으므로 #id 대신 attribute selector 사용
             */
             */
             var $targets = $content.find('[id="' + targetId + '"]');
             var $targets = $content.find('[id="' + targetId + '"]');
34번째 줄: 34번째 줄:
             /*
             /*
             * =====================================================
             * =====================================================
             * 1. 아나운서형 특수 구조
             * 특수형
             *
             *
             * 같은 ID가 여러 개 있고,
             * 아나운서 표처럼:
             * 그중 하나가 wikitable을 포함하는 경우
            *
            * 상단 버튼 wrapper
            * 실제 내용 패널
            *
            * 이 같은 ID를 공유하고,
             * 실제 패널 안에 wikitable이 있는 구조
             * =====================================================
             * =====================================================
             */
             */
             if (
             var $specialPanel = $targets.filter(function () {
                $targets.length > 1 &&
                return $(this).find('table.wikitable').length > 0;
                $targets.filter(function () {
            });
                    return $(this).find('table.wikitable').length > 0;
 
                }).length
            if ($targets.length > 1 && $specialPanel.length) {
            ) {
                 initStackedCollapsible(
                 initStackedCollapsible(
                     $content,
                     $content,
58번째 줄: 62번째 줄:
             /*
             /*
             * =====================================================
             * =====================================================
             * 2. 일반 MediaWiki collapsible
             * 일반 MediaWiki collapsible
             * =====================================================
             * =====================================================
             */
             */
68번째 줄: 72번째 줄:
         });
         });
     });
     });




     /*
     /*
     * -----------------------------------------------------
     * =============================================================
     * 일반 펼치기 · 접기
     * 일반 펼치기 · 접기
     * -----------------------------------------------------
     * =============================================================
     */
     */
     function initNormalCollapsible($content, cls, $target) {
     function initNormalCollapsible($content, cls, $target) {
80번째 줄: 85번째 줄:


         /*
         /*
         * 실제로 애니메이션할 부분
         * 일반 MediaWiki 구조:
        *
        * .mw-collapsible
        *  └ .mw-collapsible-content
        *
        * content가 있으면 content만 애니메이션
         */
         */
         var $contentTarget = $target.children('.mw-collapsible-content');
         var $contentTarget = $target.children('.mw-collapsible-content');
88번째 줄: 98번째 줄:
         }
         }


        /*
        * mw-collapsible-content가 없다면
        * target 자체를 애니메이션
        */
         var $animTarget = $contentTarget.length
         var $animTarget = $contentTarget.length
             ? $contentTarget
             ? $contentTarget
             : $target;
             : $target;
        /*
        * 현재 상태를 기준으로 초기 aria 설정
        */
        var initiallyOpen = $animTarget.is(':visible');
        $toggles.attr(
            'aria-expanded',
            initiallyOpen ? 'true' : 'false'
        );




101번째 줄: 118번째 줄:
             .on('click.smoothSlide', function (e) {
             .on('click.smoothSlide', function (e) {


                /*
                * MediaWiki 기본 이벤트까지 완전히 죽이지 않고
                * 우리가 애니메이션만 담당
                */
                 e.preventDefault();
                 e.preventDefault();
                 e.stopImmediatePropagation();
                 e.stopImmediatePropagation();


                /*
                * 애니메이션 중 다시 눌렀을 때
                * queue가 꼬이는 것을 방지
                */
                if ($animTarget.is(':animated')) {
                    return false;
                }


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




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


                     /*
                     /*
                     * 먼저 collapsed 해제
                     * MediaWiki의 collapsed 상태를 먼저 제거
                     */
                     */
                     $target.removeClass('mw-collapsed');
                     $target.removeClass('mw-collapsed');


                    /*
                    * slideDown
                    */
                     $animTarget
                     $animTarget
                         .stop(true, false)
                         .stop(true, false)
                         .slideDown(200, function () {
                        .hide()
                         .slideDown(250, function () {
 
                            /*
                            * jQuery가 남기는 애니메이션용 inline style 정리
                            */
                             $(this).css({
                             $(this).css({
                                 height: '',
                                 height: '',
132번째 줄: 166번째 줄:
                         });
                         });


                 } else {
 
                    $toggles.attr('aria-expanded', 'true');
                 }
 
 
                /*
                * -------------------------------------------------
                * 접기
                * -------------------------------------------------
                */
                else {


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


                             /*
                             /*
                             * 다 접힌 뒤 collapsed 복원
                             * 애니메이션이 끝난 뒤 collapsed 복원
                             */
                             */
                             $target.addClass('mw-collapsed');
                             $target.addClass('mw-collapsed');
153번째 줄: 197번째 줄:
                             });
                             });
                         });
                         });
                    $toggles.attr('aria-expanded', 'false');
                 }
                 }


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


                 return false;
                 return false;
168번째 줄: 210번째 줄:


     /*
     /*
     * -----------------------------------------------------
     * =============================================================
     * 아나운서처럼
     * 아나운서형 특수 펼치기 · 접기
    * =============================================================
    *
    * DOM 예:
    *
    * [남성 버튼 wrapper]
    * [남성 실제 패널]
    * [여성 버튼 wrapper]
    * [여성 실제 패널]
     *
     *
     * 상단 버튼
     * 패널을 다른 위치로 이동하지 않고
     * 실제 패널
     * 원래 DOM 위치에서 펼친다.
    * 다음 버튼
    * 실제 패널
     *
     *
     * 구조인 경우
     * 그래서 남성을 펼치면 여성 버튼이 자동으로 아래로 밀린다.
     * -----------------------------------------------------
     * =============================================================
     */
     */
     function initStackedCollapsible($content, cls, $targets) {
     function initStackedCollapsible($content, cls, $targets) {


         /*
         /*
         * wikitable이 들어 있는 것이 실제 패널
         * wikitable이 들어 있는 요소를 실제 패널로 사용
         */
         */
         var $panel = $targets.filter(function () {
         var $panel = $targets.filter(function () {
194번째 줄: 242번째 줄:


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


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


         if (!$topToggle.length || !$panelToggle.length) {
         if (!$topToggle.length || !$panelToggle.length) {
209번째 줄: 264번째 줄:


         /*
         /*
         * 초기에는 패널 숨김
         * 최초 상태
         */
         */
         $panel.hide();
         $panel
            .addClass('mw-collapsed')
            .hide();
 
        $topToggle.attr('aria-expanded', 'false');
        $panelToggle.attr('aria-expanded', 'false');
 




         /*
         /*
         * 위 버튼 클릭
         * =========================================================
        * 위쪽 버튼 클릭 → 펼치기
        * =========================================================
         */
         */
         $topToggle
         $topToggle
224번째 줄: 287번째 줄:
                 e.stopImmediatePropagation();
                 e.stopImmediatePropagation();


                 if ($panel.is(':animated') || $panel.is(':visible')) {
 
                /*
                * 중복 클릭 방지
                */
                 if (
                    $panel.is(':animated') ||
                    $topWrapper.is(':animated') ||
                    $panel.is(':visible')
                ) {
                     return false;
                     return false;
                 }
                 }
230번째 줄: 301번째 줄:


                 /*
                 /*
                 * 원래 상단 버튼 제거
                 * MediaWiki collapsed 상태 해제
                *
                * DOM 위치는 유지되므로
                * 뒤의 여성/다음 버튼이 아래로 내려감
                 */
                 */
                 $topWrapper.hide();
                 $panel.removeClass('mw-collapsed');




                 /*
                 /*
                 * 중요:
                 * 상단 버튼을 즉시 hide()하지 않고
                 * 패널 자체를 움직이지 않음
                * 짧게 slideUp하여 "툭" 끊기는 느낌 완화
                */
                $topWrapper
                    .stop(true, false)
                    .slideUp(140, function () {
 
                        $(this).css({
                            height: '',
                            overflow: '',
                            paddingTop: '',
                            paddingBottom: '',
                            marginTop: '',
                            marginBottom: ''
                        });
                    });
 
 
                /*
                 * 실제 패널은 원래 위치에서 부드럽게 펼침
                 */
                 */
                 $panel
                 $panel
                    .removeClass('mw-collapsed')
                     .stop(true, false)
                     .stop(true, false)
                     .hide()
                     .hide()
                     .slideDown(200, function () {
                     .slideDown(260, function () {
 
                         $(this).css({
                         $(this).css({
                             height: '',
                             height: '',
263번째 줄: 349번째 줄:
                 return false;
                 return false;
             });
             });




         /*
         /*
         * 펼쳐진 패널 안쪽 버튼 클릭
         * =========================================================
        * 패널 안쪽 버튼 클릭 → 접기
        * =========================================================
         */
         */
         $panelToggle
         $panelToggle
275번째 줄: 364번째 줄:
                 e.stopImmediatePropagation();
                 e.stopImmediatePropagation();


                 if ($panel.is(':animated')) {
 
                /*
                * 중복 클릭 방지
                */
                 if (
                    $panel.is(':animated') ||
                    $topWrapper.is(':animated')
                ) {
                     return false;
                     return false;
                 }
                 }




                /*
                * 실제 패널 접기
                */
                 $panel
                 $panel
                     .stop(true, false)
                     .stop(true, false)
                     .slideUp(200, function () {
                     .slideUp(260, function () {


                         $(this)
                         $(this)
295번째 줄: 394번째 줄:
                                 marginBottom: ''
                                 marginBottom: ''
                             });
                             });
                    });




                        /*
                /*
                        * 애니메이션이 끝난 뒤
                * 위쪽 원래 버튼은 동시에 복귀
                        * 원래 상단 버튼 복귀
                */
                        */
                $topWrapper
                         $topWrapper.show();
                    .stop(true, false)
                    .slideDown(140, function () {
 
                         $(this).css({
                            height: '',
                            overflow: '',
                            paddingTop: '',
                            paddingBottom: '',
                            marginTop: '',
                            marginBottom: ''
                        });
                     });
                     });



2026년 8월 20일 (목) 15:03 판

/* 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();

                /*
                 * 애니메이션 중 다시 눌렀을 때
                 * queue가 꼬이는 것을 방지
                 */
                if ($animTarget.is(':animated')) {
                    return false;
                }

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


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

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


                    /*
                     * slideDown
                     */
                    $animTarget
                        .stop(true, false)
                        .hide()
                        .slideDown(250, function () {

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


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


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

                    $animTarget
                        .stop(true, false)
                        .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 위치에서 펼친다.
     *
     * 그래서 남성을 펼치면 여성 버튼이 자동으로 아래로 밀린다.
     * =============================================================
     */
    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
            .addClass('mw-collapsed')
            .hide();

        $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') ||
                    $topWrapper.is(':animated') ||
                    $panel.is(':visible')
                ) {
                    return false;
                }


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


                /*
                 * 상단 버튼을 즉시 hide()하지 않고
                 * 짧게 slideUp하여 "툭" 끊기는 느낌 완화
                 */
                $topWrapper
                    .stop(true, false)
                    .slideUp(140, function () {

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


                /*
                 * 실제 패널은 원래 위치에서 부드럽게 펼침
                 */
                $panel
                    .stop(true, false)
                    .hide()
                    .slideDown(260, function () {

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


                $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') ||
                    $topWrapper.is(':animated')
                ) {
                    return false;
                }


                /*
                 * 실제 패널 접기
                 */
                $panel
                    .stop(true, false)
                    .slideUp(260, function () {

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


                /*
                 * 위쪽 원래 버튼은 동시에 복귀
                 */
                $topWrapper
                    .stop(true, false)
                    .slideDown(140, function () {

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


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

                return false;
            });
    }

});