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

(주석 툴팁 강화: 애니메이션 추가, 셀렉터 최적화)
 
96번째 줄: 96번째 줄:
  
 
/**
 
/**
* Live Reference
+
  * 주석 툴팁
  * 주석 링크에 마우스를 올렸을 때 툴팁을 띄움
+
  * @Author(s)  User:Cafeinlove
*
+
  * @License   MIT License
  * @Author   User:Cafeinlove
+
  * @License MIT License
+
 
  */
 
  */
void function( document ) {
+
void function( window, document, mw, undefined ) {
 
   
 
   
 
     "use strict";
 
     "use strict";
 
   
 
   
     var citations = document.querySelectorAll( "#mw-content-text .reference" );
+
    /**
     var tooltip;
+
    * Namespace numbers for pages that do not need this module
 +
    * -1: Special
 +
    *  8: MediaWiki
 +
    */
 +
     var excludedNS = [-1, 8];
 +
 +
    if ( excludedNS.indexOf( mw.config.get("wgNamespaceNumber") ) > 0 ) return;
 +
    if ( mw.config.get( "wgAction" ) != "view" ) return;
 +
 +
    // triggering elements (links like [1], [2]...)
 +
    var container = document.getElementById( "mw-content-text" );
 +
     var citations = container.getElementsByClassName( "reference" );
 
   
 
   
 
     if ( citations.length === 0 ) return;
 
     if ( citations.length === 0 ) return;
 
   
 
   
 +
    var tooltip; // dynamic tooltip
 +
    var animation; // animationFrame queue
 +
 +
    /**
 +
    * init
 +
    * @description  initialize the tooltip
 +
    */
 
     void function init() {
 
     void function init() {
 
         // create tooltip element and append it to body element
 
         // create tooltip element and append it to body element
 
         tooltip = document.createElement( "div" );
 
         tooltip = document.createElement( "div" );
 
         tooltip.id = "liveReference";
 
         tooltip.id = "liveReference";
 
 
         document.body.appendChild( tooltip );
 
         document.body.appendChild( tooltip );
 
   
 
   
         // initialize mouse events
+
         // delegate mouse events to "#mw-content-text"
         Array.from( citations ).forEach( function( citation ) {
+
         [ "mouseover", "mouseout" ].forEach( function( e ) {
             citation.addEventListener( "mouseover", function() { setTooltip( citation ); } );
+
             container.addEventListener( e, function( event ) {
             citation.addEventListener( "mouseout", resetTooltip );
+
                checkEvent( event );
 +
             });
 
         });
 
         });
 
     }();
 
     }();
 
   
 
   
     function setTooltip( citation ) {
+
    /**
         var sourceHash = citation.children[0].hash.replace( /\./g, "\\." );
+
    * checkTarget
 +
    * @description    check detected mouse event and call tooltip functions if necessary
 +
    * @param{Object}  detected mouse event object
 +
    */
 +
     function checkEvent( event ) {
 +
        var target = event.target;
 +
 +
        if ( target.tagName != "A" ) return;
 +
        if ( !target.parentNode.classList.contains( "reference" ) ) return;
 +
 +
        if ( event.type == "mouseover" ) {
 +
            setTooltip( target );
 +
        } else {
 +
            resetTooltip();
 +
        }
 +
    }
 +
 +
    /**
 +
    * setTooltip
 +
    * @description        set content and position of the tooltip
 +
    * @param{HTMLElement}  the citation link element which user has mouse-overed
 +
    */
 +
    function setTooltip( trigger ) {
 +
        // replace() escapes all dots within the anchor's hash
 +
        // preventing error when <ref/>'s name attribute has unicode string as its value
 +
         var sourceHash = trigger.hash.replace( /\./g, "\\." );
 
         var sourceEl = document.querySelector( sourceHash + " .reference-text" );
 
         var sourceEl = document.querySelector( sourceHash + " .reference-text" );
 
         var scroll = {
 
         var scroll = {
             top: document.body.scrollTop || document.documentElement.scrollLeft,
+
             top: document.body.scrollTop || document.documentElement.scrollTop,
 
             left: document.body.scrollLeft || document.documentElement.scrollLeft
 
             left: document.body.scrollLeft || document.documentElement.scrollLeft
 
         };
 
         };
         var rect = citation.getBoundingClientRect();
+
         var rect = trigger.getBoundingClientRect();
 
         var isInViewport = ( rect.top - tooltip.offsetHeight ) > 100;
 
         var isInViewport = ( rect.top - tooltip.offsetHeight ) > 100;
 
   
 
   
         // position tooltip and fill it with source content
+
         // position the tooltip
 
         tooltip.style.top = ( scroll.top + rect.top ) + "px";
 
         tooltip.style.top = ( scroll.top + rect.top ) + "px";
 
         tooltip.style.left = ( scroll.left + rect.left ) + "px";
 
         tooltip.style.left = ( scroll.left + rect.left ) + "px";
 +
 +
        // copy content from target element to the tooltip
 
         tooltip.innerHTML = sourceEl.innerHTML;
 
         tooltip.innerHTML = sourceEl.innerHTML;
 +
 +
        // flip the tooltip if it is going off the viewport
 
         if ( !isInViewport ) tooltip.classList.add( "is-flipped" );
 
         if ( !isInViewport ) tooltip.classList.add( "is-flipped" );
 
   
 
   
         tooltip.classList.add( "is-ready" );
+
         // fade in the tooltip
 +
        fadeIn( tooltip );
 
     }
 
     }
 
   
 
   
 +
    /**
 +
    * resetTooltip
 +
    * @description  reset content and position of the tooltip
 +
    */
 
     function resetTooltip() {
 
     function resetTooltip() {
         tooltip.className = "";
+
         fadeOut( tooltip, function() {
        tooltip.removeAttribute( "style" );
+
            // reset flip
 +
            tooltip.className = "";
 
   
 
   
        while ( tooltip.lastChild ) {
+
            // remove content
            tooltip.removeChild( tooltip.lastChild );
+
            while ( tooltip.lastChild ) {
 +
                tooltip.removeChild( tooltip.lastChild );
 +
            }
 +
        });
 +
    }
 +
 +
    /**
 +
    * fadeIn
 +
    * @description        fade in the tooltip
 +
    * @param{HTMLElement}  element to fade in
 +
    */
 +
    function fadeIn( el ) {
 +
        var opacity = 0;
 +
        var interval = 1 / 8;
 +
 +
        el.style.opacity = opacity;
 +
        el.style.visibility = "visible";
 +
 +
        function animate() {
 +
            opacity += interval;
 +
 +
            if ( opacity < 1 ) {
 +
                el.style.opacity = opacity;
 +
                animation = window.requestAnimationFrame( animate );
 +
            } else {
 +
                el.style.opacity = 1;
 +
                return true;
 +
            }
 
         }
 
         }
 +
 +
        stopAnimation();
 +
        animate();
 +
    }
 +
 +
    /**
 +
    * fadeOut
 +
    * @description        fade out the tooltip
 +
    * @param{HTMLElement}  element to fade out
 +
    * @param{Function}    function to execute when fade animation completes
 +
    */
 +
    function fadeOut( el, callback ) {
 +
        var opacity = 1;
 +
        var interval = 1 / 8;
 +
 +
        function animate() {
 +
            opacity -= interval;
 +
 +
            if ( opacity > 0 ) {
 +
                el.style.opacity = opacity;
 +
                animation = window.requestAnimationFrame( animate );
 +
            } else {
 +
                el.style.opacity = 0;
 +
                el.removeAttribute( "style" );
 +
 +
                if ( typeof callback == "function" ) callback();
 +
 +
                return true;
 +
            }
 +
        }
 +
 +
        stopAnimation();
 +
        animate();
 +
    }
 +
 +
    /**
 +
    * stopAnimation
 +
    * @description  clear animationFrame queue
 +
    */
 +
    function stopAnimation() {
 +
        if ( !animation ) return;
 +
        window.cancelAnimationFrame( animation );
 +
        animation = undefined;
 
     }
 
     }
 
   
 
   
}( document );
+
}( window, document, window.mediaWiki );
  
 
/* 네타바레 감추기
 
/* 네타바레 감추기

2017년 2월 24일 (금) 17:46 기준 최신판

/*!
 * 위키쨩 페이지별 자바스크립트
 *
 * 작성자: wiki-chan.net
 * 저작권: Refer to each script block (MIT license unless defined differently)
 */

// 페이지 설정을 변수에 캐시
var cfg = mw.config.get([
    "wgAction",
    "wgCanonicalSpecialPageName",
    "wgIsMainPage",
    "wgNamespaceNumber"
]);

/*
 Pure JavaScript Tabs
 https://github.com/ltebean/tabs
 @Author: Itebean
 @License: MIT License
 ----------------------------------------*/
! function( name, definition ) {

    if ( typeof module != "undefined" && module.exports ) module.exports = definition();
    else if ( typeof define == "function" ) define( definition );
    else this[name] = definition();

}( "wikitabs", function() {

    return function tabs( container ) {
        var tabs = container.getElementsByClassName( "tab" );
        var panes = container.getElementsByClassName( "tab-pane" );

        each(tabs, function( i, tab ) {
            tab.addEventListener( "click", function( e ) {
                activate( tabs, i );
                activate( panes, i );
            });
        })

        function activate( tabs, index ) {
            each( tabs, function( i, tab ) {
                if ( i != index ) {
                    tab.classList.remove( "active" );
                } else {
                    tab.classList.add( "active" );
                }
            });
        }
    }

    function each( elements, fn ) {
        for ( var i = elements.length - 1; i >= 0; i-- ) {
            fn( i, elements[i] );
        }
    }

});

var tabs_container = document.getElementsByClassName( "tab-container" );

Array.from(tabs_container).forEach( function(e) {
    wikitabs(e);
});

/* 코드 블럭을 더블클릭해서 내용 모두 선택하기
 ----------------------------------------*/
(function() {

    const selectors = ["code", "pre", "samp", "var"];
 
    var targetArray = [];
    var nodeList;
 
    selectors.forEach( function(e) {
        nodeList = document.getElementsByTagName(e);
        targetArray = targetArray.concat( Array.from(nodeList) );
    });
 
    targetArray.forEach( function(e) {
        e.addEventListener("dblclick", function() {
            selectCode(e);
        });
    });
 
    function selectCode(el) {
        var range = document.createRange();
        var selection = window.getSelection();
 
        range.selectNodeContents(el);
        selection.removeAllRanges();
        selection.addRange(range);
    }
 
})();

/**
 * 주석 툴팁
 * @Author(s)  User:Cafeinlove
 * @License    MIT License
 */
void function( window, document, mw, undefined ) {
 
    "use strict";
 
    /**
     * Namespace numbers for pages that do not need this module
     * -1: Special
     *  8: MediaWiki
     */
    var excludedNS = [-1, 8];
 
    if ( excludedNS.indexOf( mw.config.get("wgNamespaceNumber") ) > 0 ) return;
    if ( mw.config.get( "wgAction" ) != "view" ) return;
 
    // triggering elements (links like [1], [2]...)
    var container = document.getElementById( "mw-content-text" );
    var citations = container.getElementsByClassName( "reference" );
 
    if ( citations.length === 0 ) return;
 
    var tooltip; // dynamic tooltip
    var animation; // animationFrame queue
 
    /**
     * init
     * @description  initialize the tooltip
     */
    void function init() {
        // create tooltip element and append it to body element
        tooltip = document.createElement( "div" );
        tooltip.id = "liveReference";
        document.body.appendChild( tooltip );
 
        // delegate mouse events to "#mw-content-text"
        [ "mouseover", "mouseout" ].forEach( function( e ) {
            container.addEventListener( e, function( event ) {
                checkEvent( event );
            });
        });
    }();
 
    /**
     * checkTarget
     * @description    check detected mouse event and call tooltip functions if necessary
     * @param{Object}  detected mouse event object
     */
    function checkEvent( event ) {
        var target = event.target;
 
        if ( target.tagName != "A" ) return;
        if ( !target.parentNode.classList.contains( "reference" ) ) return;
 
        if ( event.type == "mouseover" ) {
            setTooltip( target );
        } else {
            resetTooltip();
        }
    }
 
    /**
     * setTooltip
     * @description         set content and position of the tooltip
     * @param{HTMLElement}  the citation link element which user has mouse-overed
     */
    function setTooltip( trigger ) {
        // replace() escapes all dots within the anchor's hash
        // preventing error when <ref/>'s name attribute has unicode string as its value
        var sourceHash = trigger.hash.replace( /\./g, "\\." );
        var sourceEl = document.querySelector( sourceHash + " .reference-text" );
        var scroll = {
            top: document.body.scrollTop || document.documentElement.scrollTop,
            left: document.body.scrollLeft || document.documentElement.scrollLeft
        };
        var rect = trigger.getBoundingClientRect();
        var isInViewport = ( rect.top - tooltip.offsetHeight ) > 100;
 
        // position the tooltip
        tooltip.style.top = ( scroll.top + rect.top ) + "px";
        tooltip.style.left = ( scroll.left + rect.left ) + "px";
 
        // copy content from target element to the tooltip
        tooltip.innerHTML = sourceEl.innerHTML;
 
        // flip the tooltip if it is going off the viewport
        if ( !isInViewport ) tooltip.classList.add( "is-flipped" );
 
        // fade in the tooltip
        fadeIn( tooltip );
    }
 
    /**
     * resetTooltip
     * @description  reset content and position of the tooltip
     */
    function resetTooltip() {
        fadeOut( tooltip, function() {
            // reset flip
            tooltip.className = "";
 
            // remove content
            while ( tooltip.lastChild ) {
                tooltip.removeChild( tooltip.lastChild );
            }
        });
    }
 
    /**
     * fadeIn
     * @description         fade in the tooltip 
     * @param{HTMLElement}  element to fade in
     */
    function fadeIn( el ) {
        var opacity = 0;
        var interval = 1 / 8;
 
        el.style.opacity = opacity;
        el.style.visibility = "visible";
 
        function animate() {
            opacity += interval;
 
            if ( opacity < 1 ) {
                el.style.opacity = opacity;
                animation = window.requestAnimationFrame( animate );
            } else {
                el.style.opacity = 1;
                return true;
            }
        }
 
        stopAnimation();
        animate();
    }
 
    /**
     * fadeOut
     * @description         fade out the tooltip
     * @param{HTMLElement}  element to fade out
     * @param{Function}     function to execute when fade animation completes
     */
    function fadeOut( el, callback ) {
        var opacity = 1;
        var interval = 1 / 8;
 
        function animate() {
            opacity -= interval;
 
            if ( opacity > 0 ) {
                el.style.opacity = opacity;
                animation = window.requestAnimationFrame( animate );
            } else {
                el.style.opacity = 0;
                el.removeAttribute( "style" );
 
                if ( typeof callback == "function" ) callback();
 
                return true;
            }
        }
 
        stopAnimation();
        animate();
    }
 
    /**
     * stopAnimation
     * @description  clear animationFrame queue
     */
    function stopAnimation() {
        if ( !animation ) return;
        window.cancelAnimationFrame( animation );
        animation = undefined;
    }
 
}( window, document, window.mediaWiki );

/* 네타바레 감추기
 ----------------------------------------*/
$(function(){

    // {{네타 시작}} & {{네타 끝}}
    $('span.netabare').on('click', function() {
        $(this).toggleClass('show');
    });

    // {{네타}}
    var $netabox = $('blockquote.netabare');
    if ( $netabox.length == 0 || mw.config.get("wgNamespaceNumber") == 10 ) return;

    $netabox .each(function(){
        var $self = $(this); // {{네타}}

        // 다음 단락까지 모든 내용을 숨김
        $self.nextAll().each(function() {
            var nexts = this.tagName;
            var stop = ['H2', 'H3', 'H4', 'H5'];

            if ( $.inArray( nexts, stop ) !== -1 ) {
                return false; // stop function
            }
            $(this).addClass('invisible').css('opacity', '0'); // censor spoiler
        });

        // {{네타}}를 클릭하면 해당 내용이 나타남
        $self.on('click', function(){
            $(this).nextUntil($netabox).removeClass('invisible').animate({ opacity: 1 });
            $(this).addClass('expired');
        });
    });
});

/* 경고 틀
 * Taken from http://dev.wikia.com/wiki/SpoilerAlert by Pecoes
 * Customized version
 ----------------------------------------*/
$(function(){
    window.Censor = (function(){

        // Get current page's Id
        var wgArticleId = (window.mediaWiki && window.mediaWiki.config && window.mediaWiki.config.get('wgArticleId')) || window.wgArticleId;

        // Use LocalStorage
        var ids = localStorage.getItem('censorJS'+wgArticleId);

        // Censoring action
        if ( $('.censor').length && ids != 'valid' ) {

            // Check content type and switch alert message
            if ( $('#netabare').length && $('#mature').length ) {
                var messageType = '이 문서는 네타바레 & 성인용 컨텐츠를 포함하고 있습니다.<br />귀하는 성인이며, 네타바레를 읽으시겠습니까?';
            }
            else if ( $('#netabare').length ) {
                var messageType = '이 문서는 네타바레를 포함하고 있습니다. 문서를 읽으시겠습니까?';
            }
            else if ( $('#mature').length ) {
                var messageType = '이 문서는 성인용 컨텐츠를 포함하고 있습니다. 귀하는 성인입니까?';
            };
            var pageName = wgPageName.split("_").join(" ");
            var dialog =
                '<div id="dialog" class="center">' +
                '<div class="head">' + pageName + '</div>' +
                '<div class="body">' + messageType + '<br>(취소를 누르면 이전 페이지로 이동합니다.)</div>' +
                '<div class="foot">' +
                '<input type="button" id="yes" value="네" />' +
                '<input type="button" id="no" value="아니오"/>' +
                '</div></div>';
            var article = $('div.content');
            var dialogHeight;

            $('<div id="overlay">' + dialog + '</div>').appendTo(article).css({
                position: 'absolute',
                top: 0,
                left: 0,
                right: 0,
                bottom: 0,
                backgroundColor: 'rgba(255,255,255,0.9)',
                minHeight: (dialogHeight = $('#dialog').outerHeight())
            });
            $('#yes').click(function () {
                $('#dialog').remove();
                $('#overlay').fadeOut(1600, function () {
                    $(this).remove();
                });
                localStorage.setItem('censorJS'+wgArticleId, 'valid');
            });
            $('#no').click(function () {
                $('#dialog').remove();
                if (history.length) {
                    history.back();
                } else {
                    location.href = location.protocol + '//' + location.host;
                }
            });
        }
    }) (window.Censor);
});

/*
 * leanModal
 * @Author: Ray Stone, http://finelysliced.com.au
 * @Customization: 카페인러브
 *
 * lazyload fix by 페네트-
*/
(function($) {
    $.fn.extend({
        openModal: function(options) {
            var defaults = {
                type: null,
                selector: '#modal'
            };
            options = $.extend(defaults, options);
            
            return this.each(function() {
                var o = options;

                $(this).click(function(e) {
                    $(o.selector).click(function(e) {
                        e.stopPropagation();
                    });
                    $('div.modal-container, span.modal-close').click(function() {
                        closeModal(o.selector, o.type);
                    });
                    $(document).bind('keyup', function(e) {
                        if (e.keyCode == 27) closeModal(o.selector, o.type);
                    });

                    if (o.type == 'info') {
                        initContent(o.selector, $(this));
                    }

                    $(o.selector).parent().css('display', 'block');

                    if (o.type !== 'filter') {
                        adjustModal(o.selector);
                        $(window).resize(function() { adjustModal(o.selector); });
                    }
                    // lazyload fix
                    $(o.selector).find('img').lazyload().trigger('appear');

                    $('div.modal-overlay').css({
                        'display': 'block',
                        'opacity': 0
                    }).fadeTo(200, 0.8);
                    $(o.selector).fadeTo(200, 1);
                    $('body').addClass('modal-open');

                    e.preventDefault();
                });
            });

            function adjustModal(modal) {
                var height = $(modal).outerHeight(),
                    windowHeight = $(window).height();
                $(modal).css('margin-top', (windowHeight - height) / 2);
                // lazyload fix
                // 이상하게 :visible이 아니라고 뜨므로 강제로 trigger함
                $(modal).find('img').lazyload().trigger('appear');
            }

            function closeModal(modal, type) {
                $('div.modal-overlay').fadeOut(200);
                $(modal).fadeOut(200).parent().css('display', 'none');
                if (type == 'info') {
                    $(modal).find('.detail').remove();
                }
                $('body').removeClass('modal-open');
            }

            function initContent(modal, $this) {
                $this.children('div.detail').clone().appendTo(modal);
            }
        }
    });
})(jQuery);

// Toggle by Categories
function toggleCategories(trigger, target) {
    if ( trigger.hasClass('checked') ) {
        trigger.removeClass('checked');
        target.hide();
    } else {
        trigger.addClass('checked');
        target.show();
    }
}

function assembleDetails(source, table) {
    var len = source.length,
        rows = '';

    source.each(function(i){
        var c = $(this).html();
        rows += ( i==0 || i%2==0 ? '<tr><td>' : '<td>' ) + '<span class="toggle bright">★</span>' + c +  ( i%2==0 && i!==len-1 ? '</td>' : '</td></tr>' );
    });
    table.html(rows);
}

function mapSelected() {
    var 
        targets = $('#whole-season td:not(.dim)'),
        length = targets.length,
        text = '';

    for (var i = 0; i < length; i++) {
        if (i > 0) { text += ', ' };
        text += $(targets[i]).find('.title-kr a').text();
    }

    $('#selected-items-list').val(text);
    $('#selected-items-length').text(length);
}

// Common variables
window.$games = {
    otome : $('div.item.otome'),
    bl : $('div.item.bl'),
    all_ages : $('div.item.all-ages'),
    mature : $('div.item.mature')
};

$(window).load(function() {
    // 애니 방영표
    if ($('#anitable').length) {
        // 현재 요일을 하이라이트
        $('#day' + (new Date()).getDay().toString()).addClass('current');

        // 섬네일 추가 (anitableCurrentDay에서 옮겨옴)
        if ($.fn.lazyload) $('img').lazyload();

        // 작품을 클릭하면 상세정보를 띄움
        $('#anitable div.item').openModal({ type: 'info', selector: '#individual' });

        // 신작정보만 모아서 보기
        $('#filter-new a').one('click', function() {
            assembleDetails($('#anitable div.item.new div.detail'), $('#whole-season'));

            // 활성 작품 리스트를 출력하는 부분
            $('#selected-items').prepend('<textarea id="selected-items-list" />');
            $('#selected-items').on('click', function(e){ e.stopPropagation(); });
            mapSelected();
        });
        $('#filter-new a').openModal({ type: 'filter', selector: '#whole-season' });

        // 신작정보를 클릭하면 토글 + 토글 상태에 따라서 활성 작품의 리스트를 출력
        $('#whole-season').on('click', 'span.bright', function() {
            $(this).removeClass('bright').addClass('dim');
            $(this).parent().addClass('dim');
            mapSelected();
        });
        $('#whole-season').on('click', 'span.dim', function() {
            $(this).removeClass('dim').addClass('bright');
            $(this).parent().removeClass('dim');
            mapSelected();
        });
    }

    // 게임 발매표
    if ($('#gametable').length) {
        // 현재 월을 하이라이트
        var month = ((new Date()).getMonth());
        $('#gametable div.month').eq(month).addClass('current');

        // 카테고리의 크기 표시
        $('#filter-otome span.num')
            .text($games.otome.length);
        $('#filter-bl span.num')
            .text($games.bl.length);
        $('#filter-all-ages span.num')
            .text($games.all_ages.length);
        $('#filter-mature span.num')
            .text($games.mature.length);

        // 카테고리를 클릭하면 장르별 토글
        $('#filter-otome').click(function(){
            toggleCategories($(this), $games.otome);
        });
        $('#filter-bl').click(function(){
            toggleCategories($(this), $games.bl);
        });
        $('#filter-all-ages').click(function(){
            toggleCategories($(this), $games.all_ages);
        });
        $('#filter-mature').click(function(){
            toggleCategories($(this), $games.mature);
        });

        // 작품을 클릭하면 상세정보를 띄움
        $('#gametable div.item').openModal({ type: 'info', selector: '#individual' });

        // 연간 정보 모아서 보기
        assembleDetails($('#gametable div.detail'), $('#whole-year'));
        $('#view-all a').openModal({ type: 'filter', selector: '#whole-year' });

        // 월별 정보 보기
        $('#gametable span.more').click(function() {
            var source = $(this).parent().siblings('div.item').children('div.detail');
            assembleDetails(source, $('#by-month'));
        });
        $('#gametable span.more').openModal({ type: 'filter', selector: '#by-month' });
    }
});