MediaWiki:Gadget-Tooltip.js

注意:在发布之后,您可能需要清除浏览器缓存才能看到所作出的变更的影响。

  • Firefox或Safari:按住Shift的同时单击刷新,或按Ctrl-F5Ctrl-R(Mac为⌘-R
  • Google Chrome:Ctrl-Shift-R(Mac为⌘-Shift-R
  • Internet Explorer或Edge:按住Ctrl的同时单击刷新,或按Ctrl-F5
  • Opera:Ctrl-F5
/* <nowiki> ← 避免被归类为有脚本错误的页面*/
    // 当添加新类型浮层时,需要对应在“★”注释之后的部分添加代码
    $(function (){
        if (!['view', 'submit'].includes(mw.config.get('wgAction'))) return;
        var TOOLTIP_CACHE_BLOCK_ID = 'bili-tt-cache-block';
        if (document.getElementById(TOOLTIP_CACHE_BLOCK_ID)) return; /* 避免重复调用 */
        var TOOLTIP_CACHE_BLOCK = $();
        var Link_CLASS_NAME = 'bili-tt';
        var CACHE_TIME = 3600; // 解析内容保存在服务器和本地的缓存时间,单位是秒,如果内容不经常改的话,可以设长一些减少服务器压力,增加读取速度
        
        var actions = {};
        actions.replace = function(block){ // 提取块中的第一个<a>的地址,并将整个块替换为<a>
            var inner_link = block.querySelector('a[href]'); // 获取块内的第一个a,获取其href和title,然后移除
            if (!inner_link) return block;
            
            var link = document.createElement('a');
            link.setAttribute('href', inner_link.getAttribute('href'));
            if (inner_link.innerHTML.trim() !== ""){
                var span = document.createElement('span');
                span.innerHTML = inner_link.innerHTML;
                inner_link.parentElement.insertBefore(span, inner_link);
            }
            inner_link.remove();
            
            var attrs = block.attributes;
            for(var i = attrs.length - 1; i >= 0; i--){ // 转移各项属性
                var attr = attrs[i];
                block.removeAttributeNode(attr);
                link.setAttributeNode(attr);
            }
            link.innerHTML = block.innerHTML;
            block.parentElement.insertBefore(link, block); // 取代位置
            block.remove();
            return link;
        };
        if (!fIsMobile()){
            actions.listen = function(link){
                $(link).mouseenter(function(ev){
                    var $this = $(this);
                    var $div = get_div($this);
                    $this.data('event',ev); // 记录鼠标位置,用于在load_tooltip_lua后重新set_tt_position
     
                    $div.show();
                    TOOLTIP_CACHE_BLOCK.show();
                    set_position($this);
                }).mouseleave(function(){
                    var $this = $(this);
                    TOOLTIP_CACHE_BLOCK.hide();
                    get_div($this).hide();
                });
            };
            actions.init = function(range){
                // 添加缓存浮层的区域
                if (TOOLTIP_CACHE_BLOCK.length === 0){
                    $("body").append('<div id="' + TOOLTIP_CACHE_BLOCK_ID + '"></div>');
                    TOOLTIP_CACHE_BLOCK = $('#' + TOOLTIP_CACHE_BLOCK_ID);
                }
        
                // 为所有浮层添加事件
                $(range).find('.' + Link_CLASS_NAME).addBack('.' + Link_CLASS_NAME).each(function(){
                    actions.listen(actions.replace(this));
                });
            };
        }
        else {
            actions.listen = function(link) {
                $(link).click(function(){
                    var $this = $(this);
                    var $div = get_div($this);
     
                    $div.show();// 显示对应的内容
                    TOOLTIP_CACHE_BLOCK.show();
                    $('#tt-modal').modal('show'); // 显示模态框
                    var $btn = $('#tt-modal-btn');
                    if ($this.attr('href')){ // 跳转按钮
                        $btn.attr('href', $this.attr('href'));
                        $btn.show();
                    }
                    else{ // 如果不是链接,隐藏按钮
                        $btn.hide();
                    }
                    return false;
                });
            };
            actions.init = function(range){
                if (TOOLTIP_CACHE_BLOCK.length === 0){
                    $("body").append('<div class="modal fade" id="tt-modal" tabindex="-1" role="dialog">'
                        + '<div class="modal-dialog">'
                            + '<div id="' + TOOLTIP_CACHE_BLOCK_ID + '">'
                            + '</div>'
                            + '<a id="tt-modal-btn" class="btn btn-primary">前往详情页</a>'
                        + '</div>'
                    + '</div>');
                    TOOLTIP_CACHE_BLOCK = $('#' + TOOLTIP_CACHE_BLOCK_ID);
                    $('#tt-modal').click(function(){
                        $('#tt-modal').modal('hide'); // 任意位置点击后隐藏模态框
                        TOOLTIP_CACHE_BLOCK.children('.bili-tt-cache').hide();
                    });
                }
                
                $(range).find('.' + Link_CLASS_NAME).addBack('.' + Link_CLASS_NAME).each(function(){
                    actions.listen(actions.replace(this));
                });
            };
        }
        
        actions.init(document.getElementById('mw-content-text'));
        
        // 监听dom变动,为新插入的tt注册事件
        new MutationObserver(function(mutationsList){
            mutationsList.forEach(function(mutation){
                if (mutation.addedNodes) mutation.addedNodes.forEach(actions.init);
            });
        }).observe(document.getElementById('mw-content-text'), {
            childList: true, // 监视节点增删事件
            subtree: true // 包含所有后代节点(而不只是子节点)
        });
        
        function get_div($link){
            var tt_type = get_data($link, 'type');
            var tt_name = get_data($link, 'name').replace(' ', '_');
            var $div = $(document.getElementById(get_div_id(tt_type,tt_name)));
            if ($div.length === 0){
                $div = new_div($link, fIsMobile() ? null : function(){set_position($link);});
            }
            return $div;
        }
        function get_div_id(type, name){
            return encodeURIComponent('tt-' + type + '-' + name).replace(/%/g,'.').replace(/[~'!()*]/g,'_');
        }
        
        function get_data($node, name){
            return $node.attr("data-" + name) || "";
        }
     
        // 根据浮层大小和当前元素的位置,决定浮层的位置(尽量让浮层显示在屏幕中)
        function set_position($link) {
            var window_h = $(window).height();
            var window_w = $(window).width();
            var top = $link.offset().top - $(document).scrollTop();
            var left = $link.offset().left; // 元素左侧绝对位置,若发生换行则为第二行的开始位置
            var width = $link.outerWidth();
            var parent = $link.parent();
            var relativeLeft = $link[0].offsetLeft; // 元素第一行左侧相对于容器的位置
            var ttWidth = TOOLTIP_CACHE_BLOCK.outerWidth();
            var ttHeight = TOOLTIP_CACHE_BLOCK.outerHeight();
            
            TOOLTIP_CACHE_BLOCK.css("left","unset");
            TOOLTIP_CACHE_BLOCK.css("right","unset");
            if (relativeLeft + width > parent.innerWidth()){ // 元素左侧相对偏移+元素外侧宽度>容器内侧宽度,发生换行
                var mouseEvent = $link.data('event');
                if (mouseEvent.clientX > window_w / 2){ // 鼠标在左侧
                    TOOLTIP_CACHE_BLOCK.css("right", window_w - (left + relativeLeft) + 10); // 元素第一行的左侧位置
                }
                else {
                    TOOLTIP_CACHE_BLOCK.css("left", mouseEvent.clientX + 10); // 鼠标滑入位置
                }
            }
            else if (left > window_w / 2){
                TOOLTIP_CACHE_BLOCK.css("right", window_w - left + 10);
            }
            else {
                TOOLTIP_CACHE_BLOCK.css("left", left + width + 10);
            }
     
     
            TOOLTIP_CACHE_BLOCK.css("top", top);
            
        }
     
        // 生成浮层
        function new_div($link, callback){
            callback = callback || function(){};
            var tt_type = get_data($link, 'type');
            if (tt_type === 'child'){
                $link.attr('data-name', Math.random().toString().replace('0.',''));
            }
            var tt_name = get_data($link, 'name').replace(' ', '_');
            var $div = $('<div></div>');
            $div.attr('id', get_div_id(tt_type,tt_name)).addClass('bili-tt-cache');
            // 可以为每种type的浮层,写不同的占位文本
            // 如果浮层内容简单,也可以直接将其内容使用js生成
            switch (tt_type){
                // 可以写多种类型
                // ★添加类型时,需要在此处添加新的case来为不同类型指定未加载前的占位代码
                // case 'test':
                //	 ...
                //	 break;
                case 'raw':
                    $div.text(tt_name);
                    break;
                case 'child':
                    $link.children('.tt-child').show().appendTo($div);
                    break;
            }
            $div.appendTo(TOOLTIP_CACHE_BLOCK);
            
            // 异步读取模板解析结果
            // 如果提示框内容比较简单,则可以在上面用js直接生成,不需要在此处配置
            var wikitext = '';
            switch (tt_type.slice(0,1)){
                case '@': // 模板
                    var template_name = tt_type.slice(1);
                    wikitext = '{{' + template_name + '|' + tt_name.replace('_', '').replaceAll('\\n', '<br>') + '}}'; // 调用tt_type对应的模板,参数为tt_name
                    break;
                case '$': // 模块
                    var module_name = tt_type.slice(1);
                    wikitext = '{{#invoke:' + module_name + '|tooltip|' + tt_name + '}}'; // 调用tt_type对应模块的tooltip方法,参数为tt_name
                    break;
                default:
                    switch (tt_type){
                        case 'test':
                            wikitext = '{{#if:1|test|' + tt_name + '}}';
                            break;
                        case 'wikitext':
                            wikitext = tt_name;
                            break;
                    }
            }
            if (wikitext){
                $div.text('加载中...');
                load_wikitext(wikitext, $div).then(callback);
            }
            else{
                callback();
            }
            
            return $div;
        }
        
        // 根据wikitext生成浮层内容
        function load_wikitext(wikitext, $div, again) {
            // 文档:https://wiki.biligame.com/pcr/api.php?action=help&modules=parse
            return $.get('/octopathsp/api.php',{
                action: "parse", // 解析
                format: "json", // 返回内容的格式
                disablelimitreport: true, // 不返回使用内存、时间信息
                prop: "text", // 返回解析后的文本
                contentmodel: "wikitext", // 内容模型
                smaxage: CACHE_TIME,
                maxage: CACHE_TIME,
                text: wikitext, // 待解析文本
                _: mw.config.get('debug') ? Date.now() : null // 调试时不使用缓存
            }).then(function(result){
                if (result && result.parse && result.parse.text){
                    $div.html(result.parse.text['*']);
                }
                else{
                    throw result;
                }
            })['catch'](function(error){ // 辣鸡解析器,不让我直接.catch
                if(again){
                    $div.text('读取失败');
                    console.log('获取"' + wikitext + '"失败', error);
                }
                else{
                    $div.text('再次尝试...');
                    return load_wikitext(wikitext, $div, true);
                }
            });
        }
    });
    /* </nowiki> */