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

편집 요약 없음
편집 요약 없음
1번째 줄: 1번째 줄:
/**
/**
  * MediaWiki 커스텀 펼치기/접기 애니메이션
  * 식별자에 대응하는 실제 collapsible 대상 찾기
  *
  *
  * 지원:
  * 동일한 id가 여러 번 사용되는 나무위키식/리브레위키식
* 둘러보기 틀 구조도 지원.
  *
  *
  * .mw-customtoggle-이름
  * 예:
*          ↕
* .mw-customcollapsible-이름
  *
  *
  * 예:
  * 1. 상단 버튼용
* <div class="mw-collapsible"
*      id="mw-customcollapsible-XXX">
*    <div class="mw-customtoggle-XXX">...</div>
* </div>
*
* 2. 실제 펼침 영역
* <div class="mw-collapsible mw-collapsed"
*      id="mw-customcollapsible-XXX">
*    <div class="mw-customtoggle-XXX">...</div>
*    <table>...</table>
* </div>
  *
  *
  * .mw-customtoggle-KSC_LEADER
  * → 2번을 선택
* .mw-customcollapsible-KSC_LEADER
  */
  */
 
function getTarget( name ) {
( function () {
if ( !name ) {
'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;
return null;
}
}


var id =
'mw-customcollapsible-' + name;


/**
/*
* CSS selector escape
* getElementById()를 사용하지 않는다.
*
* 같은 ID가 여러 개 존재하는 위키 문법을
* 지원하기 위해 attribute selector로 전부 가져온다.
*/
*/
function escapeSelector( value ) {
var candidates =
 
document.querySelectorAll(
if (
'[id="' +
window.CSS &&
escapeAttribute( id ) +
typeof window.CSS.escape === 'function'
'"], .mw-customcollapsible-' +
) {
escapeSelector( name )
return window.CSS.escape( value );
}
 
return value.replace(
/([!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~])/g,
'\\$1'
);
);
}


 
if ( !candidates.length ) {
/**
return null;
* 토글 이름에 대응하는 대상 찾기
*/
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
);
}
}


 
if ( candidates.length === 1 ) {
/**
return candidates[ 0 ];
* 실제 접히는 콘텐츠 찾기
*/
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;
}
}


var bestTarget = null;
var bestScore = -1;
var i;


/**
for ( i = 0; i < candidates.length; i++ ) {
* 요소의 자연스러운 display 값
var candidate = candidates[ i ];
*/
var score = 0;
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 ) {


/*
* 실제 펼침 영역은 보통 mw-collapsed를 가지고 시작
*/
if (
if (
target.classList.contains(
candidate.classList.contains(
'mw-collapsed'
'mw-collapsed'
)
)
) {
) {
return true;
score += 1000;
}
}
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
* MediaWiki가 생성한 실제 콘텐츠
*/
*/
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 (
if (
isCollapsed(
candidate.querySelector(
target,
'.mw-collapsible-content'
content
)
)
) {
) {
 
score += 500;
expand(
target,
content,
toggle
);
 
} else {
 
collapse(
target,
content,
toggle
);
}
}


 
/*
return true;
* 실제 내용에 표가 존재하는 경우
}
*/
 
 
/**
* 토글 초기화
*/
function initializeToggle( toggle ) {
 
if (
if (
toggle.classList.contains(
candidate.querySelector(
INITIALIZED_CLASS
'table'
)
)
) {
) {
return;
score += 200;
}
 
 
var name =
getToggleName( toggle );
 
if ( !name ) {
return;
}
 
 
var target =
getTarget( name );
 
if ( !target ) {
return;
}
}
var content =
getContent( target );
toggle.classList.add(
INITIALIZED_CLASS
);


/*
/*
* 접근성
* wikitable
*/
*/
if (
if (
!toggle.hasAttribute( 'role' )
candidate.querySelector(
'.wikitable'
)
) {
) {
toggle.setAttribute(
score += 200;
'role',
'button'
);
}
}


/*
* 내부 요소가 많을수록 실제 내용 영역일 가능성이 높음
*/
score +=
candidate.querySelectorAll( '*' ).length;


if (
if ( score > bestScore ) {
!toggle.hasAttribute( 'tabindex' )
bestScore = score;
) {
bestTarget = candidate;
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 ]
);
}
}
);
}
}


}() );
return bestTarget;
}

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

/**
 * 식별자에 대응하는 실제 collapsible 대상 찾기
 *
 * 동일한 id가 여러 번 사용되는 나무위키식/리브레위키식
 * 둘러보기 틀 구조도 지원.
 *
 * 예:
 *
 * 1. 상단 버튼용
 * <div class="mw-collapsible"
 *      id="mw-customcollapsible-XXX">
 *     <div class="mw-customtoggle-XXX">...</div>
 * </div>
 *
 * 2. 실제 펼침 영역
 * <div class="mw-collapsible mw-collapsed"
 *      id="mw-customcollapsible-XXX">
 *     <div class="mw-customtoggle-XXX">...</div>
 *     <table>...</table>
 * </div>
 *
 * → 2번을 선택
 */
function getTarget( name ) {
	if ( !name ) {
		return null;
	}

	var id =
		'mw-customcollapsible-' + name;

	/*
	 * getElementById()를 사용하지 않는다.
	 *
	 * 같은 ID가 여러 개 존재하는 위키 문법을
	 * 지원하기 위해 attribute selector로 전부 가져온다.
	 */
	var candidates =
		document.querySelectorAll(
			'[id="' +
			escapeAttribute( id ) +
			'"], .mw-customcollapsible-' +
			escapeSelector( name )
		);

	if ( !candidates.length ) {
		return null;
	}

	if ( candidates.length === 1 ) {
		return candidates[ 0 ];
	}

	var bestTarget = null;
	var bestScore = -1;
	var i;

	for ( i = 0; i < candidates.length; i++ ) {
		var candidate = candidates[ i ];
		var score = 0;

		/*
		 * 실제 펼침 영역은 보통 mw-collapsed를 가지고 시작
		 */
		if (
			candidate.classList.contains(
				'mw-collapsed'
			)
		) {
			score += 1000;
		}

		/*
		 * MediaWiki가 생성한 실제 콘텐츠
		 */
		if (
			candidate.querySelector(
				'.mw-collapsible-content'
			)
		) {
			score += 500;
		}

		/*
		 * 실제 내용에 표가 존재하는 경우
		 */
		if (
			candidate.querySelector(
				'table'
			)
		) {
			score += 200;
		}

		/*
		 * wikitable
		 */
		if (
			candidate.querySelector(
				'.wikitable'
			)
		) {
			score += 200;
		}

		/*
		 * 내부 요소가 많을수록 실제 내용 영역일 가능성이 높음
		 */
		score +=
			candidate.querySelectorAll( '*' ).length;

		if ( score > bestScore ) {
			bestScore = score;
			bestTarget = candidate;
		}
	}

	return bestTarget;
}