편집 요약 없음 |
편집 요약 없음 |
||
| 1번째 줄: | 1번째 줄: | ||
( function ( $, mw ) { | ( function ( $, mw ) { | ||
'use strict'; | 'use strict'; | ||
var DURATION = 180; | var DURATION = 180; | ||
var EASING = 'cubic-bezier(0.4, 0, 0.2, 1)'; | |||
var | var VIEWPORT_CLASS = 'mw-collapse-viewport'; | ||
var CONTENT_CLASS = 'mw-collapse-content'; | |||
function getToggleName( toggle ) { | function getToggleName( toggle ) { | ||
var prefix = | var prefix = 'mw-customtoggle-'; | ||
var classes = toggle.classList; | |||
var classes = | |||
var i; | var i; | ||
for ( | for ( i = 0; i < classes.length; i++ ) { | ||
if ( classes[ i ].indexOf( prefix ) === 0 ) { | |||
return classes[ i ].substring( prefix.length ); | |||
if ( | |||
return classes[ i ].substring( | |||
} | } | ||
} | } | ||
| 64번째 줄: | 24번째 줄: | ||
function escapeAttribute( value ) { | function escapeAttribute( value ) { | ||
return String( value ) | return String( value ) | ||
.replace( | .replace( /\\/g, '\\\\' ) | ||
.replace( /"/g, '\\"' ); | |||
.replace( | |||
} | } | ||
function getTargets( name ) { | function getTargets( name ) { | ||
var id = | var id = 'mw-customcollapsible-' + name; | ||
return document.querySelectorAll( | return document.querySelectorAll( | ||
'[id="' + | '[id="' + escapeAttribute( id ) + '"]' | ||
); | |||
' | } | ||
function isCollapsed( target ) { | |||
return ( | |||
target.classList.contains( 'mw-collapsed' ) || | |||
window.getComputedStyle( target ).display === 'none' | |||
); | ); | ||
} | } | ||
| 97번째 줄: | 49번째 줄: | ||
/** | /** | ||
* | * target 내부에 viewport/content 구조 생성 | ||
* | |||
* target | |||
* └─ viewport | |||
* └─ content | |||
* └─ 기존 내용 전체 | |||
*/ | */ | ||
function | function ensureViewport( target ) { | ||
var directChildren = target.children; | |||
var i; | var i; | ||
for ( | for ( i = 0; i < directChildren.length; i++ ) { | ||
if ( | if ( | ||
directChildren[ i ].classList && | |||
directChildren[ i ].classList.contains( | |||
VIEWPORT_CLASS | |||
) | ) | ||
) { | ) { | ||
return | return directChildren[ i ]; | ||
} | } | ||
} | } | ||
var viewport = document.createElement( 'div' ); | |||
var content = document.createElement( 'div' ); | |||
viewport.className = VIEWPORT_CLASS; | |||
content.className = CONTENT_CLASS; | |||
/* | |||
* 중요: | |||
* 기존 내용 전체를 content로 이동 | |||
*/ | |||
while ( target.firstChild ) { | |||
content.appendChild( | |||
target.firstChild | |||
target | |||
); | ); | ||
} | |||
viewport.appendChild( content ); | |||
target.appendChild( viewport ); | |||
viewport.style.overflow = 'hidden'; | |||
return | return viewport; | ||
} | } | ||
function getContent( viewport ) { | |||
var children = viewport.children; | |||
var i; | |||
function | |||
var | |||
for ( i = 0; i < children.length; i++ ) { | |||
if ( | |||
children[ i ].classList && | |||
children[ i ].classList.contains( | |||
CONTENT_CLASS | |||
) | |||
) { | |||
); | return children[ i ]; | ||
} | |||
} | } | ||
return null; | |||
} | } | ||
function getNaturalDisplay( target ) { | |||
function | |||
if ( | if ( | ||
target.dataset.mwCollapseDisplay | |||
) { | ) { | ||
return | return target.dataset.mwCollapseDisplay; | ||
} | } | ||
var style = | |||
window.getComputedStyle( target ); | |||
if ( | |||
style.display && | |||
style.display !== 'none' | |||
) { | |||
target.dataset.mwCollapseDisplay = | |||
style.display; | |||
return style.display; | |||
} | |||
if ( | |||
target.style.width === '100%' | |||
) { | |||
return 'block'; | |||
} | } | ||
return 'block'; | return 'block'; | ||
} | } | ||
function rememberDisplay( target ) { | |||
var display = | |||
function | window.getComputedStyle( | ||
var | target | ||
).display; | |||
); | |||
if ( | |||
display && | |||
display !== 'none' | |||
) { | |||
target.dataset.mwCollapseDisplay = | |||
display; | |||
} | } | ||
} | } | ||
function expand( target ) { | |||
function | |||
var | var viewport = | ||
ensureViewport( | |||
target | |||
); | ); | ||
var content = | |||
getContent( | |||
viewport | |||
var | |||
); | ); | ||
if ( !content ) { | |||
return; | |||
} | } | ||
/* | /* | ||
* | * target 바깥쪽만 표시 | ||
*/ | */ | ||
target.style.display = | |||
getNaturalDisplay( | |||
target | |||
); | |||
target.classList.remove( | target.classList.remove( | ||
| 375번째 줄: | 206번째 줄: | ||
/* | /* | ||
* content 자체는 항상 auto height. | |||
* content | |||
* | * | ||
* 내부 table에는 height를 절대 설정하지 않음. | |||
* 내부 | |||
*/ | */ | ||
content.style.height = 'auto'; | |||
viewport.style.height = 'auto'; | |||
viewport.style.overflow = 'hidden'; | |||
var finalHeight = | |||
content.getBoundingClientRect() | |||
.height; | |||
viewport.style.height = '0px'; | |||
void | void viewport.offsetHeight; | ||
var animation = | var animation = | ||
viewport.animate( | |||
[ | [ | ||
{ | { | ||
height: | height: '0px' | ||
}, | }, | ||
{ | { | ||
height: | height: | ||
finalHeight + | |||
'px | 'px' | ||
} | } | ||
], | ], | ||
| 470번째 줄: | 251번째 줄: | ||
} | } | ||
); | ); | ||
| 481번째 줄: | 256번째 줄: | ||
function () { | function () { | ||
viewport.style.height = | |||
''; | ''; | ||
viewport.style.overflow = | |||
''; | ''; | ||
animation.cancel(); | |||
}; | }; | ||
} | } | ||
function collapse( target ) { | |||
function collapse( | |||
rememberDisplay( | rememberDisplay( | ||
| 530번째 줄: | 273번째 줄: | ||
); | ); | ||
var viewport = | |||
); | ensureViewport( | ||
target | |||
); | |||
var content = | |||
getContent( | |||
viewport | |||
); | |||
if ( !content ) { | |||
return; | |||
} | |||
/* | |||
* 내부 table은 그대로 둠. | |||
* viewport 높이만 줄임. | |||
*/ | |||
var startHeight = | var startHeight = | ||
content.getBoundingClientRect() | |||
.height; | .height; | ||
viewport.style.height = | |||
startHeight + | startHeight + | ||
'px'; | 'px'; | ||
viewport.style.overflow = | |||
'hidden'; | 'hidden'; | ||
void viewport.offsetHeight; | |||
void | |||
var animation = | var animation = | ||
viewport.animate( | |||
[ | [ | ||
{ | { | ||
height: | height: | ||
startHeight + | startHeight + | ||
'px | 'px' | ||
}, | }, | ||
{ | { | ||
height: | height: | ||
'0px | '0px' | ||
} | } | ||
], | ], | ||
| 587번째 줄: | 334번째 줄: | ||
} | } | ||
); | ); | ||
animation.onfinish = | animation.onfinish = | ||
function () { | function () { | ||
target.classList.add( | target.classList.add( | ||
| 607번째 줄: | 343번째 줄: | ||
); | ); | ||
target.style.display = | |||
'none'; | |||
viewport.style.height = | |||
''; | ''; | ||
viewport.style.overflow = | |||
''; | ''; | ||
animation.cancel(); | |||
}; | }; | ||
} | } | ||
function toggleGroup( toggle ) { | function toggleGroup( toggle ) { | ||
var name = | var name = | ||
getToggleName( | getToggleName( | ||
| 658번째 줄: | 368번째 줄: | ||
if ( !name ) { | if ( !name ) { | ||
return | return; | ||
} | } | ||
| 667번째 줄: | 377번째 줄: | ||
); | ); | ||
var i; | var i; | ||
| 690번째 줄: | 386번째 줄: | ||
i++ | i++ | ||
) { | ) { | ||
var target = | var target = | ||
targets[ i ]; | targets[ i ]; | ||
if ( | |||
isCollapsed( | isCollapsed( | ||
target | target | ||
) | |||
) | ) { | ||
expand( | expand( | ||
target | target | ||
); | ); | ||
} else { | } else { | ||
collapse( | collapse( | ||
target | target | ||
); | ); | ||
} | } | ||
} | } | ||
} | } | ||
document.addEventListener( | document.addEventListener( | ||
'click', | 'click', | ||
function ( event ) { | function ( event ) { | ||
var toggle = | var toggle = | ||
event.target.closest( | event.target.closest( | ||
| 766번째 줄: | 434번째 줄: | ||
event.preventDefault(); | event.preventDefault(); | ||
event.stopPropagation(); | event.stopPropagation(); | ||
event.stopImmediatePropagation(); | event.stopImmediatePropagation(); | ||
| 780번째 줄: | 446번째 줄: | ||
document.addEventListener( | document.addEventListener( | ||
'keydown', | 'keydown', | ||
function ( event ) { | function ( event ) { | ||
if ( | if ( | ||
event.key !== 'Enter' && | event.key !== 'Enter' && | ||
| 801번째 줄: | 465번째 줄: | ||
if ( !toggle ) { | if ( !toggle ) { | ||
return; | return; | ||
} | } | ||
| 815번째 줄: | 470번째 줄: | ||
event.preventDefault(); | event.preventDefault(); | ||
event.stopPropagation(); | event.stopPropagation(); | ||
event.stopImmediatePropagation(); | event.stopImmediatePropagation(); | ||
| 827번째 줄: | 480번째 줄: | ||
true | true | ||
); | ); | ||
}( jQuery, mediaWiki ) ); | }( jQuery, mediaWiki ) ); | ||
2026년 8월 20일 (목) 14:24 판
( function ( $, mw ) {
'use strict';
var DURATION = 180;
var EASING = 'cubic-bezier(0.4, 0, 0.2, 1)';
var VIEWPORT_CLASS = 'mw-collapse-viewport';
var CONTENT_CLASS = 'mw-collapse-content';
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;
}
function escapeAttribute( value ) {
return String( value )
.replace( /\\/g, '\\\\' )
.replace( /"/g, '\\"' );
}
function getTargets( name ) {
var id = 'mw-customcollapsible-' + name;
return document.querySelectorAll(
'[id="' + escapeAttribute( id ) + '"]'
);
}
function isCollapsed( target ) {
return (
target.classList.contains( 'mw-collapsed' ) ||
window.getComputedStyle( target ).display === 'none'
);
}
/**
* target 내부에 viewport/content 구조 생성
*
* target
* └─ viewport
* └─ content
* └─ 기존 내용 전체
*/
function ensureViewport( target ) {
var directChildren = target.children;
var i;
for ( i = 0; i < directChildren.length; i++ ) {
if (
directChildren[ i ].classList &&
directChildren[ i ].classList.contains(
VIEWPORT_CLASS
)
) {
return directChildren[ i ];
}
}
var viewport = document.createElement( 'div' );
var content = document.createElement( 'div' );
viewport.className = VIEWPORT_CLASS;
content.className = CONTENT_CLASS;
/*
* 중요:
* 기존 내용 전체를 content로 이동
*/
while ( target.firstChild ) {
content.appendChild(
target.firstChild
);
}
viewport.appendChild( content );
target.appendChild( viewport );
viewport.style.overflow = 'hidden';
return viewport;
}
function getContent( viewport ) {
var children = viewport.children;
var i;
for ( i = 0; i < children.length; i++ ) {
if (
children[ i ].classList &&
children[ i ].classList.contains(
CONTENT_CLASS
)
) {
return children[ i ];
}
}
return null;
}
function getNaturalDisplay( target ) {
if (
target.dataset.mwCollapseDisplay
) {
return target.dataset.mwCollapseDisplay;
}
var style =
window.getComputedStyle( target );
if (
style.display &&
style.display !== 'none'
) {
target.dataset.mwCollapseDisplay =
style.display;
return style.display;
}
if (
target.style.width === '100%'
) {
return 'block';
}
return 'block';
}
function rememberDisplay( target ) {
var display =
window.getComputedStyle(
target
).display;
if (
display &&
display !== 'none'
) {
target.dataset.mwCollapseDisplay =
display;
}
}
function expand( target ) {
var viewport =
ensureViewport(
target
);
var content =
getContent(
viewport
);
if ( !content ) {
return;
}
/*
* target 바깥쪽만 표시
*/
target.style.display =
getNaturalDisplay(
target
);
target.classList.remove(
'mw-collapsed'
);
/*
* content 자체는 항상 auto height.
*
* 내부 table에는 height를 절대 설정하지 않음.
*/
content.style.height = 'auto';
viewport.style.height = 'auto';
viewport.style.overflow = 'hidden';
var finalHeight =
content.getBoundingClientRect()
.height;
viewport.style.height = '0px';
void viewport.offsetHeight;
var animation =
viewport.animate(
[
{
height: '0px'
},
{
height:
finalHeight +
'px'
}
],
{
duration:
DURATION,
easing:
EASING,
fill:
'forwards'
}
);
animation.onfinish =
function () {
viewport.style.height =
'';
viewport.style.overflow =
'';
animation.cancel();
};
}
function collapse( target ) {
rememberDisplay(
target
);
var viewport =
ensureViewport(
target
);
var content =
getContent(
viewport
);
if ( !content ) {
return;
}
/*
* 내부 table은 그대로 둠.
* viewport 높이만 줄임.
*/
var startHeight =
content.getBoundingClientRect()
.height;
viewport.style.height =
startHeight +
'px';
viewport.style.overflow =
'hidden';
void viewport.offsetHeight;
var animation =
viewport.animate(
[
{
height:
startHeight +
'px'
},
{
height:
'0px'
}
],
{
duration:
DURATION,
easing:
EASING,
fill:
'forwards'
}
);
animation.onfinish =
function () {
target.classList.add(
'mw-collapsed'
);
target.style.display =
'none';
viewport.style.height =
'';
viewport.style.overflow =
'';
animation.cancel();
};
}
function toggleGroup( toggle ) {
var name =
getToggleName(
toggle
);
if ( !name ) {
return;
}
var targets =
getTargets(
name
);
var i;
for (
i = 0;
i < targets.length;
i++
) {
var target =
targets[ i ];
if (
isCollapsed(
target
)
) {
expand(
target
);
} else {
collapse(
target
);
}
}
}
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
);
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();
toggleGroup(
toggle
);
},
true
);
}( jQuery, mediaWiki ) );