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

편집 요약 없음
태그: 되돌려진 기여
편집 요약 없음
태그: 되돌려진 기여
1번째 줄: 1번째 줄:
/**
/**
  * MediaWiki 커스텀 펼치기/접기 애니메이션
  * MediaWiki customtoggle 펼치기/접기 애니메이션
  *
  *
  * 식별자 이름과 관계없이 자동으로 연결합니다.
  * 예:
* .mw-customtoggle-KSC_LEADER
*      ↓
* #mw-customcollapsible-KSC_LEADER
  */
  */


9번째 줄: 12번째 줄:


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


/**
/**
* 토글 요소에서 식별자 추출
* customtoggle에서 식별자 추출
*
*
* mw-customtoggle-KSC_LEADER
* mw-customtoggle-KSC_LEADER
* → KSC_LEADER
* → KSC_LEADER
*/
*/
function getToggleName( toggle ) {
function getToggleId( toggle ) {
var classes = toggle.classList;
var classes = toggle.className.split( /\\s+/ );
var i;
var i;
var prefix = 'mw-customtoggle-';


for ( i = 0; i < classes.length; i++ ) {
for ( i = 0; i < classes.length; i++ ) {
if ( classes[ i ].indexOf( prefix ) === 0 ) {
if (
return classes[ i ].substring( prefix.length );
classes[ i ].indexOf( 'mw-customtoggle-' ) === 0
) {
return classes[ i ].substring(
'mw-customtoggle-'.length
);
}
}
}
}
32번째 줄: 37번째 줄:


/**
/**
* CSS 선택자에서 사용할 문자열 이스케이프
* 연결된 collapsible 찾기
*/
function escapeSelector( value ) {
if ( window.CSS && CSS.escape ) {
return CSS.escape( value );
}
 
return value.replace(
/([!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~])/g,
'\\\\$1'
);
}
 
/**
* 식별자와 연결된 펼침 대상 찾기
*/
*/
function getTarget( name ) {
function getTarget( id ) {
if ( !name ) {
if ( !id ) {
return null;
return null;
}
}


/*
/*
* class 방식
* 가장 일반적인 MediaWiki 형식
*
* class="
*    mw-collapsible
*    mw-customcollapsible-KSC_LEADER
* "
*/
*/
var target = document.querySelector(
var target = document.getElementById(
'.mw-customcollapsible-' +
'mw-customcollapsible-' + id
escapeSelector( name )
);
);


71번째 줄: 56번째 줄:


/*
/*
* id 방식도 대응
* 일부 틀에서 class 방식으로 만들어진 경우 대응
*/
*/
target = document.getElementById(
target = document.querySelector(
'mw-customcollapsible-' + name
'.mw-customcollapsible-' +
CSS.escape( id )
);
);


81번째 줄: 67번째 줄:


/**
/**
* 실제 애니메이션을 적용할 콘텐츠
* 실제 접히고 펼쳐지는 내용 찾기
*/
*/
function getContent( target ) {
function getContent( target ) {
92번째 줄: 78번째 줄:
}
}


/*
* 별도의 mw-collapsible-content가 없으면
* target 자체를 애니메이션
*/
return target;
return target;
}
}


/**
* 현재 접혀있는지 확인
*/
function isCollapsed( target, content ) {
function isCollapsed( target, content ) {
if ( target.classList.contains( 'mw-collapsed' ) ) {
return (
return true;
target.classList.contains( 'mw-collapsed' ) ||
}
window.getComputedStyle( content ).display === 'none'
 
);
return window.getComputedStyle( content ).display === 'none';
}
}


/**
* 펼치기
*/
function expand( target, content, toggle ) {
function expand( target, content, toggle ) {
if ( content.classList.contains( ANIMATING_CLASS ) ) {
if (
content.classList.contains(
'mw-custom-collapse-animating'
)
) {
return;
return;
}
}
content.classList.add( ANIMATING_CLASS );


target.classList.remove( 'mw-collapsed' );
target.classList.remove( 'mw-collapsed' );
120번째 줄: 105번째 줄:
content.style.display = 'block';
content.style.display = 'block';
content.style.height = 'auto';
content.style.height = 'auto';
content.style.overflow = 'hidden';


var targetHeight = content.scrollHeight;
var targetHeight = content.scrollHeight;


content.style.height = '0px';
content.style.height = '0px';
content.style.opacity = '';
content.style.opacity = '0';
 
content.classList.add(
'mw-custom-collapse-animating'
);


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


136번째 줄: 122번째 줄:
{
{
height: '0px',
height: '0px',
opacity: 0
},
},
{
{
height: targetHeight + 'px',
height: targetHeight + 'px',
opacity: 1
}
}
],
],
149번째 줄: 137번째 줄:


animation.onfinish = function () {
animation.onfinish = function () {
content.classList.remove( ANIMATING_CLASS );
content.classList.remove(
'mw-custom-collapse-animating'
);


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


toggle.setAttribute(
toggle.setAttribute(
165번째 줄: 154번째 줄:
}
}


/**
* 접기
*/
function collapse( target, content, toggle ) {
function collapse( target, content, toggle ) {
if ( content.classList.contains( ANIMATING_CLASS ) ) {
if (
content.classList.contains(
'mw-custom-collapse-animating'
)
) {
return;
return;
}
}
content.classList.add( ANIMATING_CLASS );


var startHeight =
var startHeight =
179번째 줄: 167번째 줄:


content.style.display = 'block';
content.style.display = 'block';
content.style.overflow = 'hidden';
 
content.classList.add(
'mw-custom-collapse-animating'
);


var animation = content.animate(
var animation = content.animate(
202번째 줄: 193번째 줄:
target.classList.add( 'mw-collapsed' );
target.classList.add( 'mw-collapsed' );


content.classList.remove( ANIMATING_CLASS );
content.classList.remove(
'mw-custom-collapse-animating'
);


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


toggle.setAttribute(
toggle.setAttribute(
218번째 줄: 210번째 줄:
}
}


/**
function handleToggle( toggle ) {
* 토글 실행
var id = getToggleId( toggle );
*/
function toggleCollapsible( toggle ) {
var name = getToggleName( toggle );


if ( !name ) {
if ( !id ) {
return false;
return;
}
}


var target = getTarget( name );
var target = getTarget( id );


if ( !target ) {
if ( !target ) {
return false;
return;
}
}


237번째 줄: 226번째 줄:


if ( !content ) {
if ( !content ) {
return false;
return;
}
}


253번째 줄: 242번째 줄:
);
);
}
}
return true;
}
}


/**
/*
* 클릭
* MediaWiki 기본 customtoggle 동작보다 먼저 실행
*
* capture=true를 사용해서
* MediaWiki 기본 토글보다 먼저 처리
*/
*/
document.addEventListener(
document.addEventListener(
274번째 줄: 258번째 줄:
}
}


if ( !getToggleName( toggle ) ) {
if ( !getToggleId( toggle ) ) {
return;
return;
}
}
282번째 줄: 266번째 줄:
event.stopImmediatePropagation();
event.stopImmediatePropagation();


toggleCollapsible( toggle );
handleToggle( toggle );
},
},
true
true
);
);


/**
/*
* Enter / Space 키 지원
* 키보드 접근
*/
*/
document.addEventListener(
document.addEventListener(
305번째 줄: 289번째 줄:


if ( !toggle ) {
if ( !toggle ) {
return;
}
if ( !getToggleName( toggle ) ) {
return;
return;
}
}
316번째 줄: 296번째 줄:
event.stopImmediatePropagation();
event.stopImmediatePropagation();


toggleCollapsible( toggle );
handleToggle( toggle );
},
},
true
true

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

/**
 * MediaWiki customtoggle 펼치기/접기 애니메이션
 *
 * 예:
 * .mw-customtoggle-KSC_LEADER
 *      ↓
 * #mw-customcollapsible-KSC_LEADER
 */

( function () {
	'use strict';

	var DURATION = 300;

	/**
	 * customtoggle에서 식별자 추출
	 *
	 * mw-customtoggle-KSC_LEADER
	 * → KSC_LEADER
	 */
	function getToggleId( toggle ) {
		var classes = toggle.className.split( /\\s+/ );
		var i;

		for ( i = 0; i < classes.length; i++ ) {
			if (
				classes[ i ].indexOf( 'mw-customtoggle-' ) === 0
			) {
				return classes[ i ].substring(
					'mw-customtoggle-'.length
				);
			}
		}

		return null;
	}

	/**
	 * 연결된 collapsible 찾기
	 */
	function getTarget( id ) {
		if ( !id ) {
			return null;
		}

		/*
		 * 가장 일반적인 MediaWiki 형식
		 */
		var target = document.getElementById(
			'mw-customcollapsible-' + id
		);

		if ( target ) {
			return target;
		}

		/*
		 * 일부 틀에서 class 방식으로 만들어진 경우 대응
		 */
		target = document.querySelector(
			'.mw-customcollapsible-' +
			CSS.escape( id )
		);

		return target;
	}

	/**
	 * 실제 접히고 펼쳐지는 내용 찾기
	 */
	function getContent( target ) {
		var content = target.querySelector(
			'.mw-collapsible-content'
		);

		if ( content ) {
			return content;
		}

		/*
		 * 별도의 mw-collapsible-content가 없으면
		 * target 자체를 애니메이션
		 */
		return target;
	}

	function isCollapsed( target, content ) {
		return (
			target.classList.contains( 'mw-collapsed' ) ||
			window.getComputedStyle( content ).display === 'none'
		);
	}

	function expand( target, content, toggle ) {
		if (
			content.classList.contains(
				'mw-custom-collapse-animating'
			)
		) {
			return;
		}

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

		content.style.display = 'block';
		content.style.height = 'auto';

		var targetHeight = content.scrollHeight;

		content.style.height = '0px';
		content.style.opacity = '0';

		content.classList.add(
			'mw-custom-collapse-animating'
		);

		/* reflow */
		content.offsetHeight;

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

		animation.onfinish = function () {
			content.classList.remove(
				'mw-custom-collapse-animating'
			);

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

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

			animation.cancel();
		};
	}

	function collapse( target, content, toggle ) {
		if (
			content.classList.contains(
				'mw-custom-collapse-animating'
			)
		) {
			return;
		}

		var startHeight =
			content.getBoundingClientRect().height;

		content.style.display = 'block';

		content.classList.add(
			'mw-custom-collapse-animating'
		);

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

		animation.onfinish = function () {
			target.classList.add( 'mw-collapsed' );

			content.classList.remove(
				'mw-custom-collapse-animating'
			);

			content.style.display = 'none';
			content.style.height = '';
			content.style.opacity = '';

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

			animation.cancel();
		};
	}

	function handleToggle( toggle ) {
		var id = getToggleId( toggle );

		if ( !id ) {
			return;
		}

		var target = getTarget( id );

		if ( !target ) {
			return;
		}

		var content = getContent( target );

		if ( !content ) {
			return;
		}

		if ( isCollapsed( target, content ) ) {
			expand(
				target,
				content,
				toggle
			);
		} else {
			collapse(
				target,
				content,
				toggle
			);
		}
	}

	/*
	 * MediaWiki 기본 customtoggle 동작보다 먼저 실행
	 */
	document.addEventListener(
		'click',
		function ( event ) {
			var toggle = event.target.closest(
				'.mw-customtoggle'
			);

			if ( !toggle ) {
				return;
			}

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

			event.preventDefault();
			event.stopPropagation();
			event.stopImmediatePropagation();

			handleToggle( toggle );
		},
		true
	);

	/*
	 * 키보드 접근
	 */
	document.addEventListener(
		'keydown',
		function ( event ) {
			if (
				event.key !== 'Enter' &&
				event.key !== ' '
			) {
				return;
			}

			var toggle = event.target.closest(
				'.mw-customtoggle'
			);

			if ( !toggle ) {
				return;
			}

			event.preventDefault();
			event.stopPropagation();
			event.stopImmediatePropagation();

			handleToggle( toggle );
		},
		true
	);

}() );