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

편집 요약 없음
편집 요약 없음
1번째 줄: 1번째 줄:
console.log( 'Common.js loaded' );
/**
/**
  * MediaWiki 사이트 전체 펼치기/접기 애니메이션
  * MediaWiki 커스텀 펼치기/접기 애니메이션
  *
  *
  * 대상:
  * 지원 예시:
  * .mw-collapsible
  *
  * .mw-collapsible-toggle
* mw-customtoggle-KSC_LEADER
  * .mw-collapsible-content
*        ↕
* mw-customcollapsible-KSC_LEADER
*
  * mw-customtoggle-COMPANY
*        ↕
  * mw-customcollapsible-COMPANY
*
* 식별자 이름과 관계없이 자동으로 연결합니다.
  */
  */


13번째 줄: 18번째 줄:
'use strict';
'use strict';


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


function getContent( collapsible ) {
/**
var children = collapsible.children;
* 토글 요소에서 식별자 추출
*
* mw-customtoggle-KSC_LEADER
* → KSC_LEADER
*/
function getToggleName( toggle ) {
var classes = toggle.classList;
var i;
var i;
var prefix = 'mw-customtoggle-';


for ( i = 0; i < children.length; i++ ) {
for ( i = 0; i < classes.length; i++ ) {
if (
if ( classes[ i ].indexOf( prefix ) === 0 ) {
children[ i ].classList &&
return classes[ i ].substring( prefix.length );
children[ i ].classList.contains( 'mw-collapsible-content' )
) {
return children[ i ];
}
}
}
}


/* 직접 자식이 아닌 구조도 대응 */
return null;
return collapsible.querySelector( '.mw-collapsible-content' );
}
 
/**
* CSS 선택자에서 사용할 문자열 이스케이프
*/
function escapeSelector( value ) {
if ( window.CSS && CSS.escape ) {
return CSS.escape( value );
}
 
return value.replace(
/([!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~])/g,
'\\\\$1'
);
}
}


function updateToggle( toggle, collapsed ) {
/**
if ( !toggle ) {
* 식별자와 연결된 펼침 대상 찾기
return;
*/
function getTarget( name ) {
if ( !name ) {
return null;
}
}


toggle.classList.toggle(
/*
'mw-collapsible-toggle-collapsed',
* class 방식
collapsed
*
* class="
*    mw-collapsible
*    mw-customcollapsible-KSC_LEADER
* "
*/
var target = document.querySelector(
'.mw-customcollapsible-' +
escapeSelector( name )
);
);


toggle.classList.toggle(
if ( target ) {
'mw-collapsible-toggle-expanded',
return target;
!collapsed
}
 
/*
* id 방식도 대응
*/
target = document.getElementById(
'mw-customcollapsible-' + name
);
);


toggle.setAttribute(
return target;
'aria-expanded',
}
collapsed ? 'false' : 'true'
 
/**
* 실제 애니메이션을 적용할 콘텐츠
*/
function getContent( target ) {
var content = target.querySelector(
'.mw-collapsible-content'
);
);


var text = toggle.querySelector( '.mw-collapsible-text' );
if ( content ) {
return content;
}


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


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


collapsible.classList.remove( 'mw-collapsed' );
content.classList.add( ANIMATING_CLASS );
 
target.classList.remove( 'mw-collapsed' );


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;
74번째 줄: 137번째 줄:
content.style.opacity = '0';
content.style.opacity = '0';


content.classList.add( 'mw-collapse-animating' );
/*
 
* 강제 reflow
/* 강제 reflow */
*/
content.offsetHeight;
content.offsetHeight;


98번째 줄: 161번째 줄:


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


content.style.display = '';
content.style.display = '';
content.style.height = '';
content.style.height = '';
content.style.opacity = '';
content.style.opacity = '';
content.style.overflow = '';
toggle.setAttribute(
'aria-expanded',
'true'
);


animation.cancel();
animation.cancel();
updateToggle( toggle, false );
};
};
}
}


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


var startHeight = content.getBoundingClientRect().height;
content.classList.add( ANIMATING_CLASS );
 
var startHeight =
content.getBoundingClientRect().height;


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


var animation = content.animate(
var animation = content.animate(
139번째 줄: 212번째 줄:


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


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


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(
'aria-expanded',
'false'
);


animation.cancel();
animation.cancel();
};
}
/**
* 토글 실행
*/
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;
}


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


/*
/**
* capture 단계에서 MediaWiki 기본 즉시 열기/닫기를 막고
* 클릭
* 우리가 애니메이션을 처리함.
*
* capture=true를 사용해서
* MediaWiki 기본 토글보다 먼저 처리
*/
*/
document.addEventListener(
document.addEventListener(
161번째 줄: 279번째 줄:
function ( event ) {
function ( event ) {
var toggle = event.target.closest(
var toggle = event.target.closest(
'.mw-collapsible-toggle'
'.mw-customtoggle'
);
);


168번째 줄: 286번째 줄:
}
}


var collapsible = toggle.closest(
if ( !getToggleName( toggle ) ) {
'.mw-collapsible'
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'
);
);


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


var content = getContent( collapsible );
if ( !getToggleName( toggle ) ) {
 
if ( !content ) {
return;
return;
}
}
184번째 줄: 326번째 줄:
event.preventDefault();
event.preventDefault();
event.stopPropagation();
event.stopPropagation();
event.stopImmediatePropagation();


var collapsed =
toggleCollapsible( toggle );
collapsible.classList.contains(
'mw-collapsed'
) ||
window.getComputedStyle( content ).display === 'none';
 
if ( collapsed ) {
openCollapsible(
collapsible,
content,
toggle
);
} else {
closeCollapsible(
collapsible,
content,
toggle
);
}
},
},
true
true

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

/**
 * MediaWiki 커스텀 펼치기/접기 애니메이션
 *
 * 지원 예시:
 *
 * mw-customtoggle-KSC_LEADER
 *        ↕
 * mw-customcollapsible-KSC_LEADER
 *
 * mw-customtoggle-COMPANY
 *        ↕
 * mw-customcollapsible-COMPANY
 *
 * 식별자 이름과 관계없이 자동으로 연결합니다.
 */

( function () {
	'use strict';

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

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

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

		return null;
	}

	/**
	 * CSS 선택자에서 사용할 문자열 이스케이프
	 */
	function escapeSelector( value ) {
		if ( window.CSS && CSS.escape ) {
			return CSS.escape( value );
		}

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

	/**
	 * 식별자와 연결된 펼침 대상 찾기
	 */
	function getTarget( name ) {
		if ( !name ) {
			return null;
		}

		/*
		 * class 방식
		 *
		 * class="
		 *     mw-collapsible
		 *     mw-customcollapsible-KSC_LEADER
		 * "
		 */
		var target = document.querySelector(
			'.mw-customcollapsible-' +
			escapeSelector( name )
		);

		if ( target ) {
			return target;
		}

		/*
		 * id 방식도 대응
		 */
		target = document.getElementById(
			'mw-customcollapsible-' + name
		);

		return target;
	}

	/**
	 * 실제 애니메이션을 적용할 콘텐츠
	 */
	function getContent( target ) {
		var content = target.querySelector(
			'.mw-collapsible-content'
		);

		if ( content ) {
			return content;
		}

		return target;
	}

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

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

	/**
	 * 펼치기
	 */
	function expand( target, content, toggle ) {
		if ( content.classList.contains( ANIMATING_CLASS ) ) {
			return;
		}

		content.classList.add( ANIMATING_CLASS );

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

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

		var targetHeight = content.scrollHeight;

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

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

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

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

			animation.cancel();
		};
	}

	/**
	 * 접기
	 */
	function collapse( target, content, toggle ) {
		if ( content.classList.contains( ANIMATING_CLASS ) ) {
			return;
		}

		content.classList.add( ANIMATING_CLASS );

		var startHeight =
			content.getBoundingClientRect().height;

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

		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( ANIMATING_CLASS );

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

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

			animation.cancel();
		};
	}

	/**
	 * 토글 실행
	 */
	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;
	}

	/**
	 * 클릭
	 *
	 * capture=true를 사용해서
	 * MediaWiki 기본 토글보다 먼저 처리
	 */
	document.addEventListener(
		'click',
		function ( event ) {
			var toggle = event.target.closest(
				'.mw-customtoggle'
			);

			if ( !toggle ) {
				return;
			}

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

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

			toggleCollapsible( toggle );
		},
		true
	);

	/**
	 * Enter / Space 키 지원
	 */
	document.addEventListener(
		'keydown',
		function ( event ) {
			if (
				event.key !== 'Enter' &&
				event.key !== ' '
			) {
				return;
			}

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

			if ( !toggle ) {
				return;
			}

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

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

			toggleCollapsible( toggle );
		},
		true
	);

}() );