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