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

편집 요약 없음
편집 요약 없음
1번째 줄: 1번째 줄:
/**
/**
  * =========================================================
  * MediaWiki 커스텀 펼치기/접기 애니메이션
* Animated Collapse / Expand
* MediaWiki:Common.js
* =========================================================
  *
  *
  * 사용 예:
  * 지원:
  *
  *
  * <div
  * .mw-customtoggle-이름
  *     class="animated-toggle"
  *         ↕
  *     data-collapse-target="example-1"
  * .mw-customcollapsible-이름
*    role="button"
*    tabindex="0"
* >
*    <b>[ 펼치기 · 접기 ]</b>
* </div>
  *
  *
  * <div
  * 예:
*    id="example-1"
*    class="animated-content"
* >
*    <div class="animated-content-inner">
*        내용
*    </div>
* </div>
  *
  *
* .mw-customtoggle-KSC_LEADER
* .mw-customcollapsible-KSC_LEADER
  */
  */


( function () {
'use strict';


(function ($, mw) {
var DURATION = 300;
    'use strict';
var EASING = 'cubic-bezier(0.4, 0, 0.2, 1)';


var ANIMATING_CLASS =
'mw-custom-collapse-animating';


    /**
var INITIALIZED_CLASS =
    * -----------------------------------------------------
'mw-custom-collapse-initialized';
    * 펼치기 초기화
    * -----------------------------------------------------
    */


    function initAnimatedCollapse($content) {


        $content.find('.animated-toggle').each(function () {
/**
* 토글의 식별자 추출
*
* mw-customtoggle-KSC_LEADER
* → KSC_LEADER
*/
function getToggleName( toggle ) {
var prefix = 'mw-customtoggle-';
var classes = toggle.classList;
var i;


            var $toggle = $(this);
for ( i = 0; i < classes.length; i++ ) {


if (
classes[ i ].indexOf( prefix ) === 0
) {
return classes[ i ].substring(
prefix.length
);
}
}


            /*
return null;
            * 이미 초기화되었다면 중복 실행하지 않음
}
            */
            if ($toggle.data('animatedCollapseInitialized')) {
                return;
            }




            /*
/**
            * 대상 ID
* CSS selector escape
            */
*/
            var targetId = $toggle.attr('data-collapse-target');
function escapeSelector( value ) {


if (
window.CSS &&
typeof window.CSS.escape === 'function'
) {
return window.CSS.escape( value );
}


            /*
return value.replace(
            * target이 없는 경우 무시
/([!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~])/g,
            */
'\\$1'
            if (!targetId) {
);
                return;
}
            }




            /*
/**
            * 특수문자가 ID에 들어갈 가능성을 고려해서
* 토글 이름에 대응하는 대상 찾기
            * document.getElementById 사용
*/
            */
function getTarget( name ) {
            var targetElement = document.getElementById(targetId);


if ( !name ) {
return null;
}


            if (!targetElement) {
var selector =
                return;
'.mw-customcollapsible-' +
            }
escapeSelector( name );


var target =
document.querySelector( selector );


            var $target = $(targetElement);
if ( target ) {
return target;
}


return document.getElementById(
'mw-customcollapsible-' + name
);
}


            /*
            * 초기화 완료 표시
            */
            $toggle.data(
                'animatedCollapseInitialized',
                true
            );


/**
* 실제 접히는 콘텐츠 찾기
*/
function getContent( target ) {


            /**
var content = target.querySelector(
            * ---------------------------------------------
':scope > .mw-collapsible-content'
            * 접근성 속성 초기화
);
            * ---------------------------------------------
            */


            if (!$toggle.attr('role')) {
if ( content ) {
                $toggle.attr('role', 'button');
return content;
            }
}


content = target.querySelector(
'.mw-collapsible-content'
);


            if (!$toggle.attr('tabindex')) {
if ( content ) {
                $toggle.attr('tabindex', '0');
return content;
            }
}


return target;
}


            /*
            * aria-controls 연결
            */
            $toggle.attr(
                'aria-controls',
                targetId
            );


/**
* 요소의 자연스러운 display 값
*/
function getNaturalDisplay( element ) {


            /*
switch ( element.tagName ) {
            * 초기 열린 상태 확인
            *
            * is-open 클래스가 있으면 펼쳐진 상태로 시작
            */
            var isInitiallyOpen =
                $target.hasClass('is-open');


case 'TABLE':
return 'table';


            $toggle.attr(
case 'TBODY':
                'aria-expanded',
return 'table-row-group';
                isInitiallyOpen ? 'true' : 'false'
            );


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


            $target.attr(
case 'TFOOT':
                'aria-hidden',
return 'table-footer-group';
                isInitiallyOpen ? 'false' : 'true'
            );


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


            /**
case 'TD':
            * ---------------------------------------------
case 'TH':
            * 열기
return 'table-cell';
            * ---------------------------------------------
            */


            function openCollapse() {
case 'COLGROUP':
return 'table-column-group';


                $target.addClass('is-open');
case 'COL':
return 'table-column';


                $toggle.attr(
case 'CAPTION':
                    'aria-expanded',
return 'table-caption';
                    'true'
                );


                $target.attr(
case 'LI':
                    'aria-hidden',
return 'list-item';
                    'false'
                );


default:
return 'block';
}
}


                /*
                * 필요하면 사용자 정의 이벤트도 발생
                */
                $target.trigger(
                    'animatedcollapse:open'
                );
            }


/**
* 현재 접힌 상태인지 확인
*/
function isCollapsed( target, content ) {


            /**
if (
            * ---------------------------------------------
target.classList.contains(
            * 닫기
'mw-collapsed'
            * ---------------------------------------------
)
            */
) {
return true;
}


            function closeCollapse() {
var style =
window.getComputedStyle( content );


                $target.removeClass('is-open');
return (
style.display === 'none' ||
content.hidden === true
);
}


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


                $target.attr(
/**
                    'aria-hidden',
* 현재 실행 중인 애니메이션 취소
                    'true'
*/
                );
function cancelCurrentAnimation( content ) {


if ( content._mwCollapseAnimation ) {


                $target.trigger(
try {
                    'animatedcollapse:close'
content._mwCollapseAnimation.cancel();
                );
} catch ( e ) {
            }
// 무시
}


content._mwCollapseAnimation = null;
}
}


            /**
            * ---------------------------------------------
            * 토글
            * ---------------------------------------------
            */


            function toggleCollapse() {
/**
* 펼치기
*/
function expand(
target,
content,
toggle
) {


                if ($target.hasClass('is-open')) {
cancelCurrentAnimation( content );


                    closeCollapse();
content.classList.add(
ANIMATING_CLASS
);


                } else {
/*
* MediaWiki의 접힘 클래스 먼저 제거
*/
target.classList.remove(
'mw-collapsed'
);


                    openCollapse();
content.hidden = false;


                }
            }


/*
* display:none 해제
*/
content.style.display =
getNaturalDisplay( content );


            /**
content.style.overflow = 'hidden';
            * ---------------------------------------------
            * 클릭
            * ---------------------------------------------
            */


            $toggle.on(
content.style.height = 'auto';
                'click.animatedCollapse',
                function (event) {


                    event.preventDefault();


                    toggleCollapse();
/*
                }
* 실제 최종 높이 계산
            );
*/
var targetHeight =
content.scrollHeight;




            /**
/*
            * ---------------------------------------------
* 애니메이션 시작 상태
            * 키보드
*/
            *
content.style.height = '0px';
            * Enter
content.style.opacity = '0';
            * Space
            * ---------------------------------------------
            */


            $toggle.on(
                'keydown.animatedCollapse',
                function (event) {


                    if (
/*
                        event.key === 'Enter' ||
* 강제 reflow
                        event.key === ' '
*/
                    ) {
void content.offsetHeight;


                        event.preventDefault();


                        toggleCollapse();
var animation = content.animate(
                    }
[
                }
{
            );
height: '0px',
opacity: 0
},
{
height:
targetHeight + 'px',
opacity: 1
}
],
{
duration: DURATION,
easing: EASING,
fill: 'forwards'
}
);


        });


    }
content._mwCollapseAnimation =
animation;




    /**
animation.onfinish = function () {
    * -----------------------------------------------------
    * MediaWiki 페이지 최초 로드
    * -----------------------------------------------------
    */


    $(function () {
content._mwCollapseAnimation =
null;


        initAnimatedCollapse(
content.classList.remove(
            $(document)
ANIMATING_CLASS
        );
);


    });


/*
* 애니메이션 스타일 정리
*/
content.style.height = '';
content.style.overflow = '';
content.style.opacity = '';


    /**
/*
    * -----------------------------------------------------
* display는 제거해서
    * MediaWiki 동적 콘텐츠 대응
* 기존 위키 스타일에 맡김
    *
*/
    * VisualEditor,
content.style.display = '';
    * AJAX,
    * 확장기능,
    * Lua 등에서 콘텐츠가 나중에 생성돼도
    * 자동으로 초기화
    * -----------------------------------------------------
    */


    if (
        typeof mw !== 'undefined' &&
        mw.hook
    ) {


        mw.hook('wikipage.content').add(
toggle.setAttribute(
            function ($content) {
'aria-expanded',
'true'
);


                initAnimatedCollapse(
                    $content
                );


            }
target.setAttribute(
        );
'aria-expanded',
'true'
);
};
}


    }


/**
* 접기
*/
function collapse(
target,
content,
toggle
) {


})(jQuery, mediaWiki);
cancelCurrentAnimation( content );
 
content.classList.add(
ANIMATING_CLASS
);
 
 
/*
* 현재 화면상 실제 높이
*/
var startHeight =
content.getBoundingClientRect().height;
 
 
/*
* display 유지
*/
content.style.display =
getNaturalDisplay( content );
 
content.style.overflow =
'hidden';
 
content.style.height =
startHeight + 'px';
 
content.style.opacity = '1';
 
 
/*
* 강제 reflow
*/
void content.offsetHeight;
 
 
var animation = content.animate(
[
{
height:
startHeight + 'px',
opacity: 1
},
{
height: '0px',
opacity: 0
}
],
{
duration: DURATION,
easing: EASING,
fill: 'forwards'
}
);
 
 
content._mwCollapseAnimation =
animation;
 
 
animation.onfinish = function () {
 
content._mwCollapseAnimation =
null;
 
target.classList.add(
'mw-collapsed'
);
 
content.classList.remove(
ANIMATING_CLASS
);
 
 
/*
* MediaWiki 기본 접힘 상태와
* 동일하게 숨김
*/
content.style.display =
'none';
 
content.style.height = '';
content.style.overflow = '';
content.style.opacity = '';
 
 
toggle.setAttribute(
'aria-expanded',
'false'
);
 
 
target.setAttribute(
'aria-expanded',
'false'
);
};
}
 
 
/**
* 실제 토글
*/
function toggleCollapsible( toggle ) {
 
var name =
getToggleName( toggle );
 
if ( !name ) {
return false;
}
 
 
var target =
getTarget( name );
 
if ( !target ) {
return false;
}
 
 
var content =
getContent( target );
 
if ( !content ) {
return false;
}
 
 
if (
isCollapsed(
target,
content
)
) {
 
expand(
target,
content,
toggle
);
 
} else {
 
collapse(
target,
content,
toggle
);
}
 
 
return true;
}
 
 
/**
* 토글 초기화
*/
function initializeToggle( toggle ) {
 
if (
toggle.classList.contains(
INITIALIZED_CLASS
)
) {
return;
}
 
 
var name =
getToggleName( toggle );
 
if ( !name ) {
return;
}
 
 
var target =
getTarget( name );
 
if ( !target ) {
return;
}
 
 
var content =
getContent( target );
 
 
toggle.classList.add(
INITIALIZED_CLASS
);
 
 
/*
* 접근성
*/
if (
!toggle.hasAttribute( 'role' )
) {
toggle.setAttribute(
'role',
'button'
);
}
 
 
if (
!toggle.hasAttribute( 'tabindex' )
) {
toggle.setAttribute(
'tabindex',
'0'
);
}
 
 
var collapsed =
isCollapsed(
target,
content
);
 
 
toggle.setAttribute(
'aria-expanded',
collapsed ?
'false' :
'true'
);
}
 
 
/**
* 페이지 내 토글 초기화
*/
function initializeAll( root ) {
 
var toggles =
root.querySelectorAll(
'[class*="mw-customtoggle-"]'
);
 
var i;
 
for (
i = 0;
i < toggles.length;
i++
) {
initializeToggle(
toggles[ i ]
);
}
}
 
 
/**
* 클릭
*
* MediaWiki jquery.makeCollapsible보다
* 먼저 가로채기 위해 capture=true
*/
document.addEventListener(
'click',
function ( event ) {
 
var toggle =
event.target.closest(
'.mw-customtoggle, [class*="mw-customtoggle-"]'
);
 
if ( !toggle ) {
return;
}
 
 
if (
!getToggleName( toggle )
) {
return;
}
 
 
event.preventDefault();
 
event.stopPropagation();
 
event.stopImmediatePropagation();
 
 
toggleCollapsible(
toggle
);
},
true
);
 
 
/**
* Enter / Space
*/
document.addEventListener(
'keydown',
function ( event ) {
 
if (
event.key !== 'Enter' &&
event.key !== ' '
) {
return;
}
 
 
var toggle =
event.target.closest(
'.mw-customtoggle, [class*="mw-customtoggle-"]'
);
 
if ( !toggle ) {
return;
}
 
 
if (
!getToggleName( toggle )
) {
return;
}
 
 
event.preventDefault();
 
event.stopPropagation();
 
event.stopImmediatePropagation();
 
 
toggleCollapsible(
toggle
);
},
true
);
 
 
/**
* 최초 로드
*/
if (
document.readyState ===
'loading'
) {
 
document.addEventListener(
'DOMContentLoaded',
function () {
initializeAll(
document
);
}
);
 
} else {
 
initializeAll(
document
);
}
 
 
/**
* MediaWiki 동적 콘텐츠 대응
*/
if (
window.mw &&
mw.hook
) {
 
mw.hook(
'wikipage.content'
).add(
function ( $content ) {
 
if (
$content &&
$content[ 0 ]
) {
 
initializeAll(
$content[ 0 ]
);
}
}
);
}
 
}() );

2026년 8월 20일 (목) 13:53 판

/**
 * MediaWiki 커스텀 펼치기/접기 애니메이션
 *
 * 지원:
 *
 * .mw-customtoggle-이름
 *          ↕
 * .mw-customcollapsible-이름
 *
 * 예:
 *
 * .mw-customtoggle-KSC_LEADER
 * .mw-customcollapsible-KSC_LEADER
 */

( function () {
	'use strict';

	var DURATION = 300;
	var EASING = 'cubic-bezier(0.4, 0, 0.2, 1)';

	var ANIMATING_CLASS =
		'mw-custom-collapse-animating';

	var INITIALIZED_CLASS =
		'mw-custom-collapse-initialized';


	/**
	 * 토글의 식별자 추출
	 *
	 * mw-customtoggle-KSC_LEADER
	 * → KSC_LEADER
	 */
	function getToggleName( toggle ) {
		var prefix = 'mw-customtoggle-';
		var classes = toggle.classList;
		var i;

		for ( i = 0; i < classes.length; i++ ) {

			if (
				classes[ i ].indexOf( prefix ) === 0
			) {
				return classes[ i ].substring(
					prefix.length
				);
			}
		}

		return null;
	}


	/**
	 * CSS selector escape
	 */
	function escapeSelector( value ) {

		if (
			window.CSS &&
			typeof window.CSS.escape === 'function'
		) {
			return window.CSS.escape( value );
		}

		return value.replace(
			/([!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~])/g,
			'\\$1'
		);
	}


	/**
	 * 토글 이름에 대응하는 대상 찾기
	 */
	function getTarget( name ) {

		if ( !name ) {
			return null;
		}

		var selector =
			'.mw-customcollapsible-' +
			escapeSelector( name );

		var target =
			document.querySelector( selector );

		if ( target ) {
			return target;
		}

		return document.getElementById(
			'mw-customcollapsible-' + name
		);
	}


	/**
	 * 실제 접히는 콘텐츠 찾기
	 */
	function getContent( target ) {

		var content = target.querySelector(
			':scope > .mw-collapsible-content'
		);

		if ( content ) {
			return content;
		}

		content = target.querySelector(
			'.mw-collapsible-content'
		);

		if ( content ) {
			return content;
		}

		return target;
	}


	/**
	 * 요소의 자연스러운 display 값
	 */
	function getNaturalDisplay( element ) {

		switch ( element.tagName ) {

			case 'TABLE':
				return 'table';

			case 'TBODY':
				return 'table-row-group';

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

			case 'TFOOT':
				return 'table-footer-group';

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

			case 'TD':
			case 'TH':
				return 'table-cell';

			case 'COLGROUP':
				return 'table-column-group';

			case 'COL':
				return 'table-column';

			case 'CAPTION':
				return 'table-caption';

			case 'LI':
				return 'list-item';

			default:
				return 'block';
		}
	}


	/**
	 * 현재 접힌 상태인지 확인
	 */
	function isCollapsed( target, content ) {

		if (
			target.classList.contains(
				'mw-collapsed'
			)
		) {
			return true;
		}

		var style =
			window.getComputedStyle( content );

		return (
			style.display === 'none' ||
			content.hidden === true
		);
	}


	/**
	 * 현재 실행 중인 애니메이션 취소
	 */
	function cancelCurrentAnimation( content ) {

		if ( content._mwCollapseAnimation ) {

			try {
				content._mwCollapseAnimation.cancel();
			} catch ( e ) {
				// 무시
			}

			content._mwCollapseAnimation = null;
		}
	}


	/**
	 * 펼치기
	 */
	function expand(
		target,
		content,
		toggle
	) {

		cancelCurrentAnimation( content );

		content.classList.add(
			ANIMATING_CLASS
		);

		/*
		 * MediaWiki의 접힘 클래스 먼저 제거
		 */
		target.classList.remove(
			'mw-collapsed'
		);

		content.hidden = false;


		/*
		 * display:none 해제
		 */
		content.style.display =
			getNaturalDisplay( content );

		content.style.overflow = 'hidden';

		content.style.height = 'auto';


		/*
		 * 실제 최종 높이 계산
		 */
		var targetHeight =
			content.scrollHeight;


		/*
		 * 애니메이션 시작 상태
		 */
		content.style.height = '0px';
		content.style.opacity = '0';


		/*
		 * 강제 reflow
		 */
		void content.offsetHeight;


		var animation = content.animate(
			[
				{
					height: '0px',
					opacity: 0
				},
				{
					height:
						targetHeight + 'px',
					opacity: 1
				}
			],
			{
				duration: DURATION,
				easing: EASING,
				fill: 'forwards'
			}
		);


		content._mwCollapseAnimation =
			animation;


		animation.onfinish = function () {

			content._mwCollapseAnimation =
				null;

			content.classList.remove(
				ANIMATING_CLASS
			);


			/*
			 * 애니메이션 스타일 정리
			 */
			content.style.height = '';
			content.style.overflow = '';
			content.style.opacity = '';

			/*
			 * display는 제거해서
			 * 기존 위키 스타일에 맡김
			 */
			content.style.display = '';


			toggle.setAttribute(
				'aria-expanded',
				'true'
			);


			target.setAttribute(
				'aria-expanded',
				'true'
			);
		};
	}


	/**
	 * 접기
	 */
	function collapse(
		target,
		content,
		toggle
	) {

		cancelCurrentAnimation( content );

		content.classList.add(
			ANIMATING_CLASS
		);


		/*
		 * 현재 화면상 실제 높이
		 */
		var startHeight =
			content.getBoundingClientRect().height;


		/*
		 * display 유지
		 */
		content.style.display =
			getNaturalDisplay( content );

		content.style.overflow =
			'hidden';

		content.style.height =
			startHeight + 'px';

		content.style.opacity = '1';


		/*
		 * 강제 reflow
		 */
		void content.offsetHeight;


		var animation = content.animate(
			[
				{
					height:
						startHeight + 'px',
					opacity: 1
				},
				{
					height: '0px',
					opacity: 0
				}
			],
			{
				duration: DURATION,
				easing: EASING,
				fill: 'forwards'
			}
		);


		content._mwCollapseAnimation =
			animation;


		animation.onfinish = function () {

			content._mwCollapseAnimation =
				null;

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

			content.classList.remove(
				ANIMATING_CLASS
			);


			/*
			 * MediaWiki 기본 접힘 상태와
			 * 동일하게 숨김
			 */
			content.style.display =
				'none';

			content.style.height = '';
			content.style.overflow = '';
			content.style.opacity = '';


			toggle.setAttribute(
				'aria-expanded',
				'false'
			);


			target.setAttribute(
				'aria-expanded',
				'false'
			);
		};
	}


	/**
	 * 실제 토글
	 */
	function toggleCollapsible( toggle ) {

		var name =
			getToggleName( toggle );

		if ( !name ) {
			return false;
		}


		var target =
			getTarget( name );

		if ( !target ) {
			return false;
		}


		var content =
			getContent( target );

		if ( !content ) {
			return false;
		}


		if (
			isCollapsed(
				target,
				content
			)
		) {

			expand(
				target,
				content,
				toggle
			);

		} else {

			collapse(
				target,
				content,
				toggle
			);
		}


		return true;
	}


	/**
	 * 토글 초기화
	 */
	function initializeToggle( toggle ) {

		if (
			toggle.classList.contains(
				INITIALIZED_CLASS
			)
		) {
			return;
		}


		var name =
			getToggleName( toggle );

		if ( !name ) {
			return;
		}


		var target =
			getTarget( name );

		if ( !target ) {
			return;
		}


		var content =
			getContent( target );


		toggle.classList.add(
			INITIALIZED_CLASS
		);


		/*
		 * 접근성
		 */
		if (
			!toggle.hasAttribute( 'role' )
		) {
			toggle.setAttribute(
				'role',
				'button'
			);
		}


		if (
			!toggle.hasAttribute( 'tabindex' )
		) {
			toggle.setAttribute(
				'tabindex',
				'0'
			);
		}


		var collapsed =
			isCollapsed(
				target,
				content
			);


		toggle.setAttribute(
			'aria-expanded',
			collapsed ?
				'false' :
				'true'
		);
	}


	/**
	 * 페이지 내 토글 초기화
	 */
	function initializeAll( root ) {

		var toggles =
			root.querySelectorAll(
				'[class*="mw-customtoggle-"]'
			);

		var i;

		for (
			i = 0;
			i < toggles.length;
			i++
		) {
			initializeToggle(
				toggles[ i ]
			);
		}
	}


	/**
	 * 클릭
	 *
	 * MediaWiki jquery.makeCollapsible보다
	 * 먼저 가로채기 위해 capture=true
	 */
	document.addEventListener(
		'click',
		function ( event ) {

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

			if ( !toggle ) {
				return;
			}


			if (
				!getToggleName( toggle )
			) {
				return;
			}


			event.preventDefault();

			event.stopPropagation();

			event.stopImmediatePropagation();


			toggleCollapsible(
				toggle
			);
		},
		true
	);


	/**
	 * Enter / Space
	 */
	document.addEventListener(
		'keydown',
		function ( event ) {

			if (
				event.key !== 'Enter' &&
				event.key !== ' '
			) {
				return;
			}


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

			if ( !toggle ) {
				return;
			}


			if (
				!getToggleName( toggle )
			) {
				return;
			}


			event.preventDefault();

			event.stopPropagation();

			event.stopImmediatePropagation();


			toggleCollapsible(
				toggle
			);
		},
		true
	);


	/**
	 * 최초 로드
	 */
	if (
		document.readyState ===
		'loading'
	) {

		document.addEventListener(
			'DOMContentLoaded',
			function () {
				initializeAll(
					document
				);
			}
		);

	} else {

		initializeAll(
			document
		);
	}


	/**
	 * MediaWiki 동적 콘텐츠 대응
	 */
	if (
		window.mw &&
		mw.hook
	) {

		mw.hook(
			'wikipage.content'
		).add(
			function ( $content ) {

				if (
					$content &&
					$content[ 0 ]
				) {

					initializeAll(
						$content[ 0 ]
					);
				}
			}
		);
	}

}() );