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

편집 요약 없음
(내용을 "$(function () { $(document).on('click', '.smooth-toggle', function (e) { e.preventDefault(); var target = $(this).data('target'); var $content = $('#' + target); $content.toggleClass('is-collapsed'); if ($content.hasClass('is-collapsed')) { $(this).text('[펼치기]'); } else { $(this).text('[접기]'); } }); });"(으)로 바꿈)
태그: 대체됨
1번째 줄: 1번째 줄:
console.log( 'Common.js loaded' );
$(function () {
/**
    $(document).on('click', '.smooth-toggle', function (e) {
* MediaWiki 커스텀 펼치기 / 접기 애니메이션
        e.preventDefault();
*
* 지원:
* - mw-customtoggle-XXX
* - mw-customcollapsible-XXX
* - 일반 mw-collapsible-content
* - 동일 ID 2개 교대형 구조
* - flex 내부 여러 토글
* - 중첩 표
*
* 효과:
* - 높이 애니메이션만 적용
* - opacity 없음
*/


( function ( $ ) {
        var target = $(this).data('target');
'use strict';
        var $content = $('#' + target);


var DURATION = 180;
        $content.toggleClass('is-collapsed');
var EASING = 'swing';


var VIEWPORT_CLASS =
        if ($content.hasClass('is-collapsed')) {
'mw-custom-collapse-viewport';
            $(this).text('[펼치기]');
 
        } else {
var INNER_CLASS =
            $(this).text('[접기]');
'mw-custom-collapse-viewport-inner';
        }
 
    });
var DATA_DISPLAY =
});
'mwCustomCollapseOriginalDisplay';
 
 
/**
* mw-customtoggle-XXX → XXX
*/
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;
}
 
 
/**
* attribute selector escape
*/
function escapeAttribute( value ) {
return String( value )
.replace(
/\\/g,
'\\\\'
)
.replace(
/"/g,
'\\"'
);
}
 
 
/**
* 동일한 이름의 target 모두 검색
*/
function getTargets( name ) {
var id =
'mw-customcollapsible-' +
name;
 
return document.querySelectorAll(
'[id="' +
escapeAttribute( id ) +
'"]'
);
}
 
 
/**
* 직계 mw-collapsible-content 찾기
*/
function getDirectContent( target ) {
var children =
target.children;
 
var i;
 
for (
i = 0;
i < children.length;
i++
) {
if (
children[ i ].classList &&
children[ i ].classList.contains(
'mw-collapsible-content'
)
) {
return children[ i ];
}
}
 
return null;
}
 
 
/**
* 현재 접힘 상태
*/
function isCollapsed( target ) {
if (
target.classList.contains(
'mw-collapsed'
)
) {
return true;
}
 
return (
window.getComputedStyle(
target
).display === 'none'
);
}
 
 
/**
* display 상태 기억
*/
function rememberDisplay( target ) {
var display =
window.getComputedStyle(
target
).display;
 
if (
display &&
display !== 'none'
) {
$( target ).data(
DATA_DISPLAY,
display
);
}
}
 
 
/**
* 펼칠 때 target의 display 복원
*/
function getOpenDisplay( target ) {
var saved =
$( target ).data(
DATA_DISPLAY
);
 
if ( saved ) {
return saved;
}
 
 
/*
* HTML에 원래 display 값이 남아 있다면
* 그 값을 우선 사용
*/
if (
target.style.display &&
target.style.display !== 'none'
) {
return target.style.display;
}
 
 
/*
* TABLE 계열
*/
switch ( target.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 'LI':
return 'list-item';
}
 
 
/*
* mw-collapsible은 대부분 DIV
*/
return 'block';
}
 
 
/**
* 애니메이션용 viewport 검색
*/
function getViewport( target ) {
var children =
target.children;
 
var i;
 
for (
i = 0;
i < children.length;
i++
) {
if (
children[ i ].classList &&
children[ i ].classList.contains(
VIEWPORT_CLASS
)
) {
return children[ i ];
}
}
 
return null;
}
 
 
/**
* 애니메이션용 viewport 생성
*
* 내부 TABLE 자체에는 height를 적용하지 않고
* viewport 높이만 변화시킴.
*/
function ensureViewport( target ) {
var existing =
getViewport(
target
);
 
if ( existing ) {
return existing;
}
 
 
var viewport =
document.createElement(
'div'
);
 
var inner =
document.createElement(
'div'
);
 
 
viewport.className =
VIEWPORT_CLASS;
 
inner.className =
INNER_CLASS;
 
 
/*
* MediaWiki 표준 구조라면
* mw-collapsible-content의 내용만 이동
*/
var content =
getDirectContent(
target
);
 
 
if ( content ) {
 
while (
content.firstChild
) {
inner.appendChild(
content.firstChild
);
}
 
 
viewport.appendChild(
inner
);
 
 
content.appendChild(
viewport
);
 
 
return viewport;
}
 
 
/*
* mw-collapsible-content가 없는 구조
*
* target 내부 전체를 viewport 안으로 이동
*/
while (
target.firstChild
) {
inner.appendChild(
target.firstChild
);
}
 
 
viewport.appendChild(
inner
);
 
 
target.appendChild(
viewport
);
 
 
return viewport;
}
 
 
/**
* viewport inner
*/
function getViewportInner(
viewport
) {
var children =
viewport.children;
 
var i;
 
for (
i = 0;
i < children.length;
i++
) {
if (
children[ i ].classList &&
children[ i ].classList.contains(
INNER_CLASS
)
) {
return children[ i ];
}
}
 
return null;
}
 
 
/**
* target 안의 자기 토글 검색
*
* 동일 ID 2개 교대형에서 사용.
*/
function getOwnToggle(
target,
name
) {
var className =
'mw-customtoggle-' +
name;
 
var elements =
target.getElementsByClassName(
className
);
 
if (
elements.length
) {
return elements[ 0 ];
}
 
return null;
}
 
 
/**
* 펼치기
*/
function expand(
target,
name,
multipleTargets
) {
var $target =
$( target );
 
 
/*
* 진행 중인 target animation 중단
*/
$target.stop(
true,
true
);
 
 
target.classList.remove(
'mw-collapsed'
);
 
 
/*
* 바깥 target 먼저 표시
*/
target.style.display =
getOpenDisplay(
target
);
 
 
var viewport =
ensureViewport(
target
);
 
 
var inner =
getViewportInner(
viewport
);
 
 
if ( !inner ) {
return;
}
 
 
var ownToggle =
null;
 
 
/*
* 동일 ID가 2개인 교대형 구조:
*
* 접히는 버튼과 펼쳐지는 버튼이
* 애니메이션 중 겹치지 않도록 처리.
*/
if ( multipleTargets ) {
ownToggle =
getOwnToggle(
target,
name
);
 
if ( ownToggle ) {
ownToggle.style.visibility =
'hidden';
}
}
 
 
var $viewport =
$( viewport );
 
 
$viewport.stop(
true,
true
);
 
 
viewport.style.display =
'block';
 
viewport.style.overflow =
'hidden';
 
viewport.style.height =
'auto';
 
 
/*
* 내부 table은 건드리지 않고
* inner의 실제 높이를 측정
*/
var targetHeight =
inner.getBoundingClientRect()
.height;
 
 
viewport.style.height =
'0px';
 
 
/*
* 강제 reflow
*/
void viewport.offsetHeight;
 
 
$viewport.animate(
{
height:
targetHeight
},
{
duration:
DURATION,
 
easing:
EASING,
 
complete:
function () {
 
viewport.style.height =
'';
 
viewport.style.overflow =
'';
 
 
if (
ownToggle
) {
ownToggle.style.visibility =
'';
}
}
}
);
}
 
 
/**
* 접기
*/
function collapse(
target
) {
rememberDisplay(
target
);
 
 
var viewport =
ensureViewport(
target
);
 
 
var inner =
getViewportInner(
viewport
);
 
 
if ( !inner ) {
return;
}
 
 
var $viewport =
$( viewport );
 
 
$viewport.stop(
true,
true
);
 
 
var currentHeight =
viewport.getBoundingClientRect()
.height;
 
 
/*
* 혹시 viewport 높이가 0으로 잡히는 경우
* inner 실제 높이 사용
*/
if (
currentHeight <= 0
) {
currentHeight =
inner.getBoundingClientRect()
.height;
}
 
 
viewport.style.height =
currentHeight +
'px';
 
viewport.style.overflow =
'hidden';
 
 
void viewport.offsetHeight;
 
 
$viewport.animate(
{
height:
0
},
{
duration:
DURATION,
 
easing:
EASING,
 
complete:
function () {
 
target.classList.add(
'mw-collapsed'
);
 
 
target.style.display =
'none';
 
 
viewport.style.height =
'';
 
viewport.style.overflow =
'';
}
}
);
}
 
 
/**
* ARIA 갱신
*/
function updateAria(
name,
expanded
) {
var className =
'mw-customtoggle-' +
name;
 
 
var toggles =
document.getElementsByClassName(
className
);
 
 
var i;
 
 
for (
i = 0;
i < toggles.length;
i++
) {
toggles[ i ].setAttribute(
'aria-expanded',
expanded ?
'true' :
'false'
);
}
}
 
 
/**
* 그룹 토글
*/
function toggleGroup(
toggle
) {
var name =
getToggleName(
toggle
);
 
 
if ( !name ) {
return false;
}
 
 
var targets =
getTargets(
name
);
 
 
if (
!targets.length
) {
return false;
}
 
 
var multipleTargets =
targets.length > 1;
 
 
var i;
 
var opened =
false;
 
 
/*
* 클릭 직전의 상태를 먼저 기록
*
* 각 요소를 순차 수정하다가
* 다음 요소의 상태 판별이 꼬이지 않도록 함.
*/
var states =
[];
 
 
for (
i = 0;
i < targets.length;
i++
) {
states.push(
isCollapsed(
targets[ i ]
)
);
}
 
 
/*
* 상태 변경
*/
for (
i = 0;
i < targets.length;
i++
) {
 
if (
states[ i ]
) {
expand(
targets[ i ],
name,
multipleTargets
);
 
opened =
true;
 
} else {
 
collapse(
targets[ i ]
);
}
}
 
 
/*
* 일반 target 하나짜리만
* aria-expanded 갱신.
*/
if (
targets.length === 1
) {
updateAria(
name,
opened
);
}
 
 
return true;
}
 
 
/**
* 클릭
*/
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();
 
 
toggleGroup(
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();
 
 
toggleGroup(
toggle
);
},
true
);
 
 
/**
* 페이지 최초 display 상태 기억
*/
function initialize(
root
) {
var elements =
root.querySelectorAll(
'.mw-collapsible'
);
 
 
var i;
 
 
for (
i = 0;
i < elements.length;
i++
) {
 
if (
window.getComputedStyle(
elements[ i ]
).display !== 'none'
) {
rememberDisplay(
elements[ i ]
);
}
}
}
 
 
$( function () {
initialize(
document
);
} );
 
 
/*
* MediaWiki AJAX / 동적 콘텐츠
*/
if (
window.mw &&
mw.hook
) {
mw.hook(
'wikipage.content'
).add(
function ( $content ) {
 
if (
$content &&
$content[ 0 ]
) {
initialize(
$content[ 0 ]
);
}
}
);
}
 
 
}( jQuery ) );

2026년 8월 20일 (목) 14:39 판

$(function () {
    $(document).on('click', '.smooth-toggle', function (e) {
        e.preventDefault();

        var target = $(this).data('target');
        var $content = $('#' + target);

        $content.toggleClass('is-collapsed');

        if ($content.hasClass('is-collapsed')) {
            $(this).text('[펼치기]');
        } else {
            $(this).text('[접기]');
        }
    });
});