|
|
| 1번째 줄: |
1번째 줄: |
| /**
| | $(document).ready(function() { |
| * MediaWiki 커스텀 펼치기/접기 슬라이드 애니메이션
| | var toggleBtns = $('.toggleBtn'); |
| *
| | toggleBtns.each(function() { |
| * 같은 mw-customcollapsible-XXX ID가 2개 존재하는 틀 지원
| | var toggleId = $(this).attr("id"); |
| *
| | var contentId = toggleId.replace("toggle", "collapsible"); |
| * 예:
| | var content = $("#" + contentId); |
| *
| | var toggleTextId = toggleId.replace("toggle", "toggleText"); |
| * [첫 번째]
| | var toggleText = $("#" + toggleTextId); |
| * <div id="mw-customcollapsible-XXX">
| | |
| * <div class="mw-customtoggle-XXX">[ 버튼 ]</div>
| | content.hide(); |
| * </div>
| | |
| *
| | $(this).click(function() { |
| * [두 번째]
| | content.slideToggle(300); |
| * <div id="mw-customcollapsible-XXX" class="mw-collapsed">
| | if (content.css("display") == "none") { |
| * <div class="mw-customtoggle-XXX">[ 버튼 ]</div>
| | toggleText.html("[ 펼치기 · 접기 ]"); |
| * <table>내용</table>
| | } else { |
| * </div>
| | toggleText.html("[ 펼치기 · 접기 ]"); |
| *
| | } |
| * 틀 원본 수정 필요 없음.
| | }); |
| */
| | }); |
| | | }); |
| ( function () { | |
| 'use strict';
| |
| | |
| var DURATION = 350;
| |
| | |
| var INITIALIZED = 'data-slide-collapse-ready';
| |
| var CONTENT_CLASS = 'mw-slide-collapse-content';
| |
| var ANIMATING_CLASS = 'mw-slide-collapse-animating';
| |
| | |
| | |
| /* --------------------------------
| |
| * mw-customtoggle-XXX 에서 XXX 추출
| |
| * -------------------------------- */
| |
| | |
| function getName( 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;
| |
| }
| |
| | |
| | |
| /* --------------------------------
| |
| * 같은 ID를 가진 collapsible 전부 찾기
| |
| *
| |
| * getElementById() 사용하지 않음!
| |
| * -------------------------------- */
| |
| | |
| function getTargets( name ) {
| |
| if ( !name ) {
| |
| return [];
| |
| }
| |
| | |
| var id =
| |
| 'mw-customcollapsible-' + name;
| |
| | |
| return Array.prototype.slice.call(
| |
| document.querySelectorAll(
| |
| '[id="' +
| |
| CSS.escape( id ) +
| |
| '"]'
| |
| )
| |
| );
| |
| }
| |
| | |
| | |
| /* --------------------------------
| |
| * 실제 내용이 들어있는 collapsible 찾기
| |
| *
| |
| * table 등이 들어있는 두 번째 div를 선택
| |
| * -------------------------------- */
| |
| | |
| function getContentTarget( name ) {
| |
| var targets = getTargets( name );
| |
| var i;
| |
| | |
| /*
| |
| * 버튼 외에 다른 요소가 들어있는 target을 찾는다.
| |
| */
| |
| for ( i = 0; i < targets.length; i++ ) {
| |
| var target = targets[ i ];
| |
| var toggle = target.querySelector(
| |
| '[class*="mw-customtoggle-"]'
| |
| );
| |
| | |
| if ( !toggle ) {
| |
| continue;
| |
| }
| |
| | |
| var node = toggle.nextElementSibling;
| |
| | |
| if ( node ) {
| |
| return target;
| |
| }
| |
| }
| |
| | |
| return null;
| |
| }
| |
| | |
| | |
| /* --------------------------------
| |
| * 실제 내용 wrapper 생성
| |
| *
| |
| * table 자체의 높이를 변경하지 않고
| |
| * 바깥 DIV 높이를 애니메이션
| |
| * -------------------------------- */
| |
| | |
| function prepareContent( target ) {
| |
| var existing =
| |
| target.querySelector(
| |
| ':scope > .' + CONTENT_CLASS
| |
| );
| |
| | |
| if ( existing ) {
| |
| return existing;
| |
| }
| |
| | |
| var toggle =
| |
| target.querySelector(
| |
| ':scope > [class*="mw-customtoggle-"]'
| |
| );
| |
| | |
| if ( !toggle ) {
| |
| return null;
| |
| }
| |
| | |
| var firstContent =
| |
| toggle.nextSibling;
| |
| | |
| if ( !firstContent ) {
| |
| return null;
| |
| }
| |
| | |
| var wrapper =
| |
| document.createElement( 'div' );
| |
| | |
| wrapper.className =
| |
| CONTENT_CLASS;
| |
| | |
| /*
| |
| * toggle 뒤의 모든 노드를 wrapper로 이동
| |
| */
| |
| var node = firstContent;
| |
| var nodes = [];
| |
| | |
| while ( node ) {
| |
| nodes.push( node );
| |
| node = node.nextSibling;
| |
| }
| |
| | |
| nodes.forEach( function ( child ) {
| |
| wrapper.appendChild( child );
| |
| } );
| |
| | |
| target.appendChild( wrapper );
| |
| | |
| return wrapper;
| |
| }
| |
| | |
| | |
| /* --------------------------------
| |
| * 같은 이름의 토글들 aria 상태 변경
| |
| * -------------------------------- */
| |
| | |
| function setToggleState( name, expanded ) {
| |
| var selector =
| |
| '.mw-customtoggle-' +
| |
| CSS.escape( name );
| |
| | |
| var toggles =
| |
| document.querySelectorAll( selector );
| |
| | |
| toggles.forEach( function ( toggle ) {
| |
| toggle.setAttribute(
| |
| 'aria-expanded',
| |
| expanded ? 'true' : 'false'
| |
| );
| |
| } );
| |
| }
| |
| | |
| | |
| /* --------------------------------
| |
| * 펼치기
| |
| * -------------------------------- */
| |
| | |
| function expand( name, target, content ) {
| |
| if (
| |
| content.classList.contains(
| |
| ANIMATING_CLASS
| |
| )
| |
| ) {
| |
| return;
| |
| }
| |
| | |
| content.classList.add(
| |
| ANIMATING_CLASS
| |
| );
| |
| | |
| /*
| |
| * MediaWiki 접힌 상태 제거
| |
| */
| |
| target.classList.remove(
| |
| 'mw-collapsed'
| |
| );
| |
| | |
| /*
| |
| * 부모 자체가 display:none 된 경우 복구
| |
| */
| |
| target.style.display =
| |
| target.dataset.originalDisplay ||
| |
| 'inline-block';
| |
| | |
| /*
| |
| * 내용 표시
| |
| */
| |
| content.style.display = 'block';
| |
| content.style.overflow = 'hidden';
| |
| | |
| /*
| |
| * 자연 높이를 먼저 계산
| |
| */
| |
| content.style.height = 'auto';
| |
| | |
| var endHeight =
| |
| content.scrollHeight;
| |
| | |
| /*
| |
| * 시작 높이
| |
| */
| |
| content.style.height = '0px';
| |
| | |
| /*
| |
| * 0px 상태를 브라우저에 확정
| |
| */
| |
| content.offsetHeight;
| |
| | |
| content.style.transition =
| |
| 'height ' +
| |
| DURATION +
| |
| 'ms cubic-bezier(0.4, 0, 0.2, 1)';
| |
| | |
| requestAnimationFrame(
| |
| function () {
| |
| content.style.height =
| |
| endHeight + 'px';
| |
| }
| |
| );
| |
| | |
| setToggleState(
| |
| name,
| |
| true
| |
| );
| |
| | |
| window.setTimeout(
| |
| function () {
| |
| content.style.transition = '';
| |
| content.style.height = '';
| |
| content.style.overflow = '';
| |
| content.style.display = '';
| |
| | |
| content.classList.remove(
| |
| ANIMATING_CLASS
| |
| );
| |
| },
| |
| DURATION + 30
| |
| );
| |
| }
| |
| | |
| | |
| /* --------------------------------
| |
| * 접기
| |
| * -------------------------------- */
| |
| | |
| function collapse( name, target, content ) {
| |
| 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';
| |
| | |
| content.style.height =
| |
| startHeight + 'px';
| |
| | |
| /*
| |
| * 현재 높이 확정
| |
| */
| |
| content.offsetHeight;
| |
| | |
| content.style.transition =
| |
| 'height ' +
| |
| DURATION +
| |
| 'ms cubic-bezier(0.4, 0, 0.2, 1)';
| |
| | |
| requestAnimationFrame(
| |
| function () {
| |
| content.style.height =
| |
| '0px';
| |
| }
| |
| );
| |
| | |
| setToggleState(
| |
| name,
| |
| false
| |
| );
| |
| | |
| window.setTimeout(
| |
| function () {
| |
| content.style.transition = '';
| |
| content.style.height = '';
| |
| content.style.overflow = '';
| |
| content.style.display = 'none';
| |
| | |
| target.classList.add(
| |
| 'mw-collapsed'
| |
| );
| |
| | |
| content.classList.remove(
| |
| ANIMATING_CLASS
| |
| );
| |
| },
| |
| DURATION + 30
| |
| );
| |
| }
| |
| | |
| | |
| /* --------------------------------
| |
| * 현재 열린 상태인지 검사
| |
| * -------------------------------- */
| |
| | |
| function isOpen( target, content ) {
| |
| if (
| |
| target.classList.contains(
| |
| 'mw-collapsed'
| |
| )
| |
| ) {
| |
| return false;
| |
| }
| |
| | |
| if (
| |
| window.getComputedStyle(
| |
| content
| |
| ).display === 'none'
| |
| ) {
| |
| return false;
| |
| }
| |
| | |
| return true;
| |
| }
| |
| | |
| | |
| /* --------------------------------
| |
| * 버튼 클릭
| |
| * -------------------------------- */
| |
| | |
| function handleToggle( event ) {
| |
| var toggle =
| |
| event.target.closest(
| |
| '[class*="mw-customtoggle-"]'
| |
| );
| |
| | |
| if ( !toggle ) {
| |
| return;
| |
| }
| |
| | |
| var name =
| |
| getName( toggle );
| |
| | |
| if ( !name ) {
| |
| return;
| |
| }
| |
| | |
| var target =
| |
| getContentTarget( name );
| |
| | |
| /*
| |
| * 우리가 처리할 수 없는 구조면
| |
| * MediaWiki 기본 기능을 그대로 사용
| |
| */
| |
| if ( !target ) {
| |
| return;
| |
| }
| |
| | |
| var content =
| |
| prepareContent( target );
| |
| | |
| if ( !content ) {
| |
| return;
| |
| }
| |
| | |
| /*
| |
| * 여기까지 왔을 때만
| |
| * MediaWiki 기본 즉시 토글 차단
| |
| */
| |
| event.preventDefault();
| |
| event.stopPropagation();
| |
| event.stopImmediatePropagation();
| |
| | |
| if (
| |
| isOpen(
| |
| target,
| |
| content
| |
| )
| |
| ) {
| |
| collapse(
| |
| name,
| |
| target,
| |
| content
| |
| );
| |
| } else {
| |
| expand(
| |
| name,
| |
| target,
| |
| content
| |
| );
| |
| }
| |
| }
| |
| | |
| | |
| /* --------------------------------
| |
| * 초기 설정
| |
| * -------------------------------- */
| |
| | |
| function initialize() {
| |
| var toggles =
| |
| document.querySelectorAll(
| |
| '[class*="mw-customtoggle-"]'
| |
| );
| |
| | |
| toggles.forEach(
| |
| function ( toggle ) {
| |
| if (
| |
| toggle.hasAttribute(
| |
| INITIALIZED
| |
| )
| |
| ) {
| |
| return;
| |
| }
| |
| | |
| var name =
| |
| getName( toggle );
| |
| | |
| if ( !name ) {
| |
| return;
| |
| }
| |
| | |
| var target =
| |
| getContentTarget(
| |
| name
| |
| );
| |
| | |
| if ( !target ) {
| |
| return;
| |
| }
| |
| | |
| var content =
| |
| prepareContent(
| |
| target
| |
| );
| |
| | |
| if ( !content ) {
| |
| return;
| |
| }
| |
| | |
| /*
| |
| * 원래 display 기억
| |
| */
| |
| if (
| |
| !target.dataset.originalDisplay
| |
| ) {
| |
| target.dataset.originalDisplay =
| |
| target.style.display ||
| |
| 'inline-block';
| |
| }
| |
| | |
| /*
| |
| * 원래 mw-collapsed인 경우
| |
| * 내용만 숨긴다.
| |
| *
| |
| * 버튼 부모 전체를 숨기지 않음.
| |
| */
| |
| if (
| |
| target.classList.contains(
| |
| 'mw-collapsed'
| |
| )
| |
| ) {
| |
| content.style.display =
| |
| 'none';
| |
| | |
| setToggleState(
| |
| name,
| |
| false
| |
| );
| |
| }
| |
| | |
| toggle.setAttribute(
| |
| INITIALIZED,
| |
| '1'
| |
| );
| |
| }
| |
| );
| |
| }
| |
| | |
| | |
| /* --------------------------------
| |
| * 클릭 이벤트
| |
| *
| |
| * capture에서 처리하되,
| |
| * 실제 target을 찾은 경우에만
| |
| * MediaWiki 기본 이벤트 차단
| |
| * -------------------------------- */
| |
| | |
| document.addEventListener(
| |
| 'click',
| |
| handleToggle,
| |
| true
| |
| );
| |
| | |
| | |
| /* --------------------------------
| |
| * 키보드
| |
| * -------------------------------- */
| |
| | |
| document.addEventListener(
| |
| 'keydown',
| |
| function ( event ) {
| |
| if (
| |
| event.key !== 'Enter' &&
| |
| event.key !== ' '
| |
| ) {
| |
| return;
| |
| }
| |
| | |
| handleToggle( event );
| |
| },
| |
| true
| |
| );
| |
| | |
| | |
| /* --------------------------------
| |
| * 실행
| |
| * -------------------------------- */
| |
| | |
| if (
| |
| document.readyState === 'loading'
| |
| ) {
| |
| document.addEventListener(
| |
| 'DOMContentLoaded',
| |
| initialize
| |
| );
| |
| } else {
| |
| initialize();
| |
| }
| |
| | |
| | |
| /*
| |
| * MediaWiki 동적 로딩 대응
| |
| */
| |
| if (
| |
| window.mw &&
| |
| mw.hook
| |
| ) {
| |
| mw.hook(
| |
| 'wikipage.content'
| |
| ).add(
| |
| function () {
| |
| initialize();
| |
| }
| |
| );
| |
| }
| |
| | |
| }() );
| |