来自柯南百科

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

  • 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
// See [[mw:Reference Tooltips]]
// Source https://en.wikipedia.org/wiki/MediaWiki:Gadget-ReferenceTooltips.js
// 因 柯南百科 的需求而进行了修改

(function () {

    // enwiki settings
    var REF_LINK_SELECTOR = '.reference, a[href^="#CITEREF"]',
        COMMENTED_TEXT_CLASS = 'rt-commentedText',
        COMMENTED_TEXT_SELECTOR = (COMMENTED_TEXT_CLASS ? '.' + COMMENTED_TEXT_CLASS + ', ' : '') +
            'abbr[title]';

    mw.messages.set(wgULS({
        'rt-settings': '跳转',
        'rt-enable-footer': '启用参考文献提示工具',
        'rt-settings-title': '参考文献提示工具',
        'rt-save': '保存',
        'rt-cancel': '取消',
        'rt-enable': '启用',
        'rt-disable': '禁用',
        'rt-activationMethod': '提示工具显示方式',
        'rt-hovering': '鼠标悬浮',
        'rt-clicking': '点击',
        'rt-delay': '工具提示显示延迟(毫秒)',
        'rt-tooltipsForComments': '在参考文献提示工具样式中用<span title="提示工具的例子" class="' + (COMMENTED_TEXT_CLASS || 'rt-commentedText') + '" style="border-bottom: 1px dotted; cursor: help;">虚下划线</span>的方式在文字上显示提示工具(可以在不支持鼠标的设备上显示提示工具)',
        'rt-disabledNote': '你可以通过页脚中的链接重新启用参考文献提示工具',
        'rt-done': '完成',
        'rt-enabled': '参考文献提示工具已启用'
    }, {
        'rt-settings': '跳轉',
        'rt-enable-footer': '啟用參考文獻提示工具',
        'rt-settings-title': '參考文獻提示工具',
        'rt-save': '儲存',
        'rt-cancel': '取消',
        'rt-enable': '啟用',
        'rt-disable': '停用',
        'rt-activationMethod': '提示工具顯示方式',
        'rt-hovering': '滑鼠懸浮',
        'rt-clicking': '點擊',
        'rt-delay': '工具提示顯示延遲(毫秒)',
        'rt-tooltipsForComments': '在參考文獻提示工具樣式中用<span title="提示工具的例子" class="' + (COMMENTED_TEXT_CLASS || 'rt-commentedText') + '" style="border-bottom: 1px dotted; cursor: help;">虛底線</span>的方式在文字上顯示提示工具(可以在不支持滑鼠的裝置上顯示提示工具)',
        'rt-disabledNote': '你可以通過頁尾中的連結重新啟用參考文獻提示工具',
        'rt-done': '完成',
        'rt-enabled': '參考文獻提示工具已啟用'
    }));

    // "Global" variables
    var CLASSES = {
        FADE_IN_DOWN: 'rt-fade-in-down',
        FADE_IN_UP: 'rt-fade-in-up',
        FADE_OUT_DOWN: 'rt-fade-out-down',
        FADE_OUT_UP: 'rt-fade-out-up'
    },
        CLIENT_NAME = $.client.profile().name,
        enabled, delay, activatedByClick, tooltipsForComments,
        $body = $(document.body),
        $window = $(window);

    function rt($content) {
        // Popups gadget & Reference Previews
        if (window.pg || mw.config.get("wgPopupsReferencePreviews")) {
            return;
        }

        var teSelector;

        function enableRt() {
            enabled = true;
            $('.rt-enableItem').remove();
            rt($content);
            mw.notify(mw.msg('rt-enabled'));
        }

        function disableRt() {
            $content.find(teSelector).removeClass('rt-commentedText').off('.rt');
            $body.off('.rt');
            $window.off('.rt');
        }

        function addEnableLink() {
            // #footer-places – Vector
            // #f-list – Timeless, Monobook, Modern
            // parent of #footer li – Cologne Blue
            var $footer = $('#footer-places, #f-list');
            if (!$footer.length) {
                $footer = $('#footer li').parent();
            }
            $footer.append(
                $('<li>')
                    .addClass('rt-enableItem')
                    .append(
                        $('<a>')
                            .text(mw.msg('rt-enable-footer'))
                            .attr('href', 'javascript:')
                            .click(function (e) {
                                e.preventDefault();
                                enableRt();
                            })
                    )
            );
        }

        function TooltippedElement($element) {
            var events,
                te = this;

            function onStartEvent(e) {
                var showRefArgs;

                if (activatedByClick && te.type !== 'commentedText' && e.type !== 'contextmenu') {
                    e.preventDefault();
                }
                if (!te.noRef) {
                    showRefArgs = [$(this)];
                    if (te.type !== 'supRef') {
                        showRefArgs.push(e.pageX, e.pageY);
                    }
                    te.showRef.apply(te, showRefArgs);
                }
            }

            function onEndEvent() {
                if (!te.noRef) {
                    te.hideRef();
                }
            }

            if (!$element) {
                return;
            }

            // TooltippedElement.$element and TooltippedElement.$originalElement will be different when
            // the first is changed after its cloned version is hovered in a tooltip
            this.$element = $element;
            this.$originalElement = $element;
            if (this.$element.is(REF_LINK_SELECTOR)) {
                if (this.$element.prop('tagName') === 'SUP') {
                    this.type = 'supRef';
                } else {
                    this.type = 'harvardRef';
                }
            } else {
                this.type = 'commentedText';
                this.comment = this.$element.attr('title');
                if (!this.comment) {
                    return;
                }
                this.$element.addClass('rt-commentedText');
            }

            if (activatedByClick) {
                events = {
                    'click.rt': onStartEvent
                };
                // Adds an ability to see tooltips for links
                if (this.type === 'commentedText' &&
                    (this.$element.closest('a').length ||
                        this.$element.has('a').length
                    )
                ) {
                    events['contextmenu.rt'] = onStartEvent;
                }
            } else {
                events = {
                    'mouseenter.rt': onStartEvent,
                    'mouseleave.rt': onEndEvent
                };
            }

            this.$element.on(events);

            this.hideRef = function (immediately) {
                clearTimeout(te.showTimer);

                if (this.type === 'commentedText') {
                    this.$element.attr('title', this.comment);
                }

                if (this.tooltip && this.tooltip.isPresent) {
                    if (activatedByClick || immediately) {
                        this.tooltip.hide();
                    } else {
                        this.hideTimer = setTimeout(function () {
                            te.tooltip.hide();
                        }, 200);
                    }
                } else if (this.$ref && this.$ref.hasClass('rt-target')) {
                    this.$ref.removeClass('rt-target');
                    if (activatedByClick) {
                        $body.off('click.rt touchstart.rt', this.onBodyClick);
                    }
                }
            };

            this.showRef = function ($element, ePageX, ePageY) {
                // Popups gadget
                if (window.pg) {
                    disableRt();
                    return;
                }

                if (this.tooltip && !this.tooltip.$content.length) {
                    return;
                }

                var tooltipInitiallyPresent = this.tooltip && this.tooltip.isPresent;

                function reallyShow() {
                    var viewportTop, refOffsetTop, teHref;

                    if (!te.$ref && !te.comment) {
                        teHref = te.type === 'supRef' ?
                            te.$element.find('a').attr('href') :
                            te.$element.attr('href'); // harvardRef
                        te.$ref = teHref &&
                            $('#' + $.escapeSelector(teHref.slice(1)));
                        if (!te.$ref || !te.$ref.length || !te.$ref.text()) {
                            te.noRef = true;
                            return;
                        }
                    }

                    if (!tooltipInitiallyPresent && !te.comment) {
                        viewportTop = $window.scrollTop();
                        refOffsetTop = te.$ref.offset().top;
                        if (!activatedByClick &&
                            viewportTop < refOffsetTop &&
                            viewportTop + $window.height() > refOffsetTop + te.$ref.height() &&
                            // There can be gadgets/scripts that make references horizontally scrollable.
                            $window.width() > te.$ref.offset().left + te.$ref.width()
                        ) {
                            // Highlight the reference itself
                            te.$ref.addClass('rt-target');
                            return;
                        }
                    }

                    if (!te.tooltip) {
                        te.tooltip = new Tooltip(te);
                        if (!te.tooltip.$content.length) {
                            return;
                        }
                    }

                    // If this tooltip is called from inside another tooltip. We can't define it
                    // in the constructor since a ref can be cloned but have the same Tooltip object;
                    // so, Tooltip.parent is a floating value.
                    te.tooltip.parent = te.$element.closest('.rt-tooltip').data('tooltip');
                    if (te.tooltip.parent && te.tooltip.parent.disappearing) {
                        return;
                    }

                    te.tooltip.show();

                    if (tooltipInitiallyPresent) {
                        if (te.tooltip.$element.hasClass('rt-tooltip-above')) {
                            te.tooltip.$element.addClass(CLASSES.FADE_IN_DOWN);
                        } else {
                            te.tooltip.$element.addClass(CLASSES.FADE_IN_UP);
                        }
                        return;
                    }

                    te.tooltip.calculatePosition(ePageX, ePageY);

                    $window.on('resize.rt', te.onWindowResize);
                }

                // We redefine this.$element here because e.target can be a reference link inside
                // a reference tooltip, not a link that was initially assigned to this.$element
                this.$element = $element;

                if (this.type === 'commentedText') {
                    this.$element.attr('title', '');
                }

                if (activatedByClick) {
                    if (tooltipInitiallyPresent ||
                        (this.$ref && this.$ref.hasClass('rt-target'))
                    ) {
                        return;
                    } else {
                        setTimeout(function () {
                            $body.on('click.rt touchstart.rt', te.onBodyClick);
                        }, 0);
                    }
                }

                if (activatedByClick || tooltipInitiallyPresent) {
                    reallyShow();
                } else {
                    this.showTimer = setTimeout(reallyShow, delay);
                }
            };

            this.onBodyClick = function (e) {
                if (!te.tooltip && !te.$ref.hasClass('rt-target')) {
                    return;
                }

                var $current = $(e.target);

                function contextMatchesParameter(parameter) {
                    return this === parameter;
                }

                // The last condition is used to determine cases when a clicked tooltip is the current
                // element's tooltip or one of its descendants
                while ($current.length &&
                    (!$current.hasClass('rt-tooltip') ||
                        !$current.data('tooltip') ||
                        !$current.data('tooltip').upToTopParent(
                            contextMatchesParameter, [te.tooltip],
                            true
                        )
                    )
                ) {
                    $current = $current.parent();
                }
                if (!$current.length) {
                    te.hideRef();
                }
            };

            this.onWindowResize = function () {
                te.tooltip.calculatePosition();
            };
        }

        function Tooltip(te) {

            var tooltip = this;

            // This variable can change: one tooltip can be called from a harvard-style reference link
            // that is put into different tooltips
            this.te = te;

            switch (this.te.type) {
                case 'supRef':
                    this.id = 'rt-' + this.te.$originalElement.attr('id');
                    this.$content = this.te.$ref
                        .contents()
                        .filter(function (i) {
                            var $this = $(this);
                            return this.nodeType === Node.TEXT_NODE ||
                                !($this.is('.mw-cite-backlink') ||
                                    (i === 0 &&
                                        // Template:Cnote, Template:Note
                                        ($this.is('b') ||
                                            // Template:Note_label
                                            $this.is('a') &&
                                            $this.attr('href').indexOf('#ref') === 0
                                        )
                                    )
                                );
                        })
                        .clone(true)
                        .append($(this.te.$originalElement).next(".referencepage").contents().clone(true));
                    break;
                case 'harvardRef':
                    this.id = 'rt-' + this.te.$originalElement.closest('li').attr('id');
                    this.$content = this.te.$ref
                        .clone(true)
                        .removeAttr('id');
                    break;
                case 'commentedText':
                    this.id = 'rt-' + String(Math.random()).slice(2);
                    this.$content = $(document.createTextNode(this.te.comment));
                    break;
            }
            if (!this.$content.length) {
                return;
            }

            this.insideWindow = Boolean(this.te.$element.closest('.oo-ui-window').length);

            this.$element = $('<div>')
                .addClass('rt-tooltip')
                .attr('id', this.id)
                .attr('data-href', this.te.$ref.attr('id')) //YOHO
                .attr('role', 'tooltip')
                .data('tooltip', this);
            if (this.insideWindow) {
                this.$element.addClass('rt-tooltip-insideWindow');
            }

            // We need the $content interlayer here in order for the settings icon to have correct
            // margins
            this.$content = this.$content
                .wrapAll('<div>')
                .parent()
                .addClass('rt-tooltipContent')
                .addClass('mw-parser-output')
                .appendTo(this.$element);

            if (!activatedByClick) {
                this.$element
                    .mouseenter(function () {
                        if (!tooltip.disappearing) {
                            tooltip.upToTopParent(function () {
                                this.show();
                            });
                        }
                    })
                    .mouseleave(function (e) {
                        // https://stackoverflow.com/q/47649442 workaround. Relying on relatedTarget
                        // alone has pitfalls: when alt-tabbing, relatedTarget is empty too
                        if (CLIENT_NAME !== 'chrome' ||
                            (!e.originalEvent ||
                                e.originalEvent.relatedTarget !== null ||
                                !tooltip.clickedTime ||
                                $.now() - tooltip.clickedTime > 50
                            )
                        ) {
                            tooltip.upToTopParent(function () {
                                this.te.hideRef();
                            });
                        }
                    })
                    .click(function () {
                        tooltip.clickedTime = $.now();
                    });
            }

            if (!this.insideWindow) {
                $('<div>')
                    .addClass('rt-settingsLink')
                    .attr('title', mw.msg('rt-settings'))
                    .click(function () {
                        // if (settingsDialogOpening) {
                        //     return;
                        // }
                        // settingsDialogOpening = true;

                        // if (mw.loader.getState('oojs-ui') !== 'ready') {
                        //     if (cursorWaitCss) {
                        //         cursorWaitCss.disabled = false;
                        //     } else {
                        //         cursorWaitCss = mw.util.addCSS('body { cursor: wait; }');
                        //     }
                        // }
                        // mw.loader.using(['oojs', 'oojs-ui'], openSettingsDialog);
                        var href = $(this).offsetParent().attr("data-href");
                        var ypos = $(document.getElementById(href)).offset().top - 100;
                        window.location.href = "#" + href;
                        $('html, body').animate({ scrollTop: ypos }, 0);
                    })
                    .prependTo(this.$content);
            }

            // Tooltip tail element is inside tooltip content element in order for the tooltip
            // not to disappear when the mouse is above the tail
            this.$tail = $('<div>')
                .addClass('rt-tooltipTail')
                .prependTo(this.$element);

            this.disappearing = false;

            this.show = function () {
                this.disappearing = false;
                clearTimeout(this.te.hideTimer);
                clearTimeout(this.te.removeTimer);

                this.$element
                    .removeClass(CLASSES.FADE_OUT_DOWN)
                    .removeClass(CLASSES.FADE_OUT_UP);

                if (!this.isPresent) {
                    $body.append(this.$element);
                }

                this.isPresent = true;
            };

            this.hide = function () {
                var tooltip = this;

                tooltip.disappearing = true;

                if (tooltip.$element.hasClass('rt-tooltip-above')) {
                    tooltip.$element
                        .removeClass(CLASSES.FADE_IN_DOWN)
                        .addClass(CLASSES.FADE_OUT_UP);
                } else {
                    tooltip.$element
                        .removeClass(CLASSES.FADE_IN_UP)
                        .addClass(CLASSES.FADE_OUT_DOWN);
                }

                tooltip.te.removeTimer = setTimeout(function () {
                    if (tooltip.isPresent) {
                        tooltip.$element.detach();

                        tooltip.$tail.css('left', '');

                        if (activatedByClick) {
                            $body.off('click.rt touchstart.rt', tooltip.te.onBodyClick);
                        }
                        $window.off('resize.rt', tooltip.te.onWindowResize);

                        tooltip.isPresent = false;
                    }
                }, 200);
            };

            this.calculatePosition = function (ePageX, ePageY) {
                var teElement, teOffsets, teOffset, tooltipTailOffsetX, tooltipTailLeft,
                    offsetYCorrection = 0;

                this.$tail.css('left', '');

                teElement = this.te.$element.get(0);
                if (ePageX !== undefined) {
                    tooltipTailOffsetX = ePageX;
                    teOffsets = teElement.getClientRects &&
                        teElement.getClientRects() ||
                        teElement.getBoundingClientRect();
                    if (teOffsets.length > 1) {
                        for (var i = teOffsets.length - 1; i >= 0; i--) {
                            if (ePageY >= Math.round($window.scrollTop() + teOffsets[i].top) &&
                                ePageY <= Math.round(
                                    $window.scrollTop() + teOffsets[i].top + teOffsets[i].height
                                )
                            ) {
                                teOffset = teOffsets[i];
                            }
                        }
                    }
                }

                if (!teOffset) {
                    teOffset = teElement.getClientRects &&
                        teElement.getClientRects()[0] ||
                        teElement.getBoundingClientRect();
                }
                teOffset = {
                    top: $window.scrollTop() + teOffset.top,
                    left: $window.scrollLeft() + teOffset.left,
                    width: teOffset.width,
                    height: teOffset.height
                };
                if (!tooltipTailOffsetX) {
                    tooltipTailOffsetX = (teOffset.left * 2 + teOffset.width) / 2;
                }
                if (CLIENT_NAME === 'msie' && this.te.type === 'supRef') {
                    offsetYCorrection = -Number(
                        this.te.$element.parent().css('font-size').replace('px', '')
                    ) / 2;
                }
                this.$element.css({
                    top: teOffset.top - this.$element.outerHeight() - 7 + offsetYCorrection,
                    left: tooltipTailOffsetX - 20,
                    right: ''
                });

                // Is it squished against the right side of the page?
                if (this.$element.offset().left + this.$element.outerWidth() > $window.width() - 1) {
                    this.$element.css({
                        left: '',
                        right: 0
                    });
                    tooltipTailLeft = tooltipTailOffsetX - this.$element.offset().left - 5;
                }

                // Is a part of it above the top of the screen?
                if (teOffset.top < this.$element.outerHeight() + $window.scrollTop() + 70) {
                    this.$element
                        .removeClass('rt-tooltip-above')
                        .addClass('rt-tooltip-below')
                        .addClass(CLASSES.FADE_IN_UP)
                        .css({
                            top: teOffset.top + teOffset.height + 9 + offsetYCorrection
                        });
                    if (tooltipTailLeft) {
                        this.$tail.css('left', (tooltipTailLeft + 12) + 'px');
                    }
                } else {
                    this.$element
                        .removeClass('rt-tooltip-below')
                        .addClass('rt-tooltip-above')
                        .addClass(CLASSES.FADE_IN_DOWN)
                        // A fix for cases when a tooltip shown once is then wrongly positioned when it
                        // is shown again after a window resize. We just repeat what is above.
                        .css({
                            top: teOffset.top - this.$element.outerHeight() - 7 + offsetYCorrection
                        });
                    if (tooltipTailLeft) {
                        // 12 is the tail element width/height
                        this.$tail.css('left', tooltipTailLeft + 'px');
                    }
                }
            };

            // Run some function for all the tooltips up to the top one in a tree. Its context will be
            // the tooltip, while its parameters may be passed to Tooltip.upToTopParent as an array
            // in the second parameter. If the third parameter passed to ToolTip.upToTopParent is true,
            // the execution stops when the function in question returns true for the first time,
            // and ToolTip.upToTopParent returns true as well.
            this.upToTopParent = function (func, parameters, stopAtTrue) {
                var returnValue,
                    currentTooltip = this;

                do {
                    returnValue = func.apply(currentTooltip, parameters);
                    if (stopAtTrue && returnValue) {
                        break;
                    }
                } while (currentTooltip = currentTooltip.parent);

                if (stopAtTrue) {
                    return returnValue;
                }
            };
        }

        if (!enabled) {
            addEnableLink();
            return;
        }

        teSelector = REF_LINK_SELECTOR;
        if (tooltipsForComments) {
            teSelector += ', ' + COMMENTED_TEXT_SELECTOR;
        }
        $content.find(teSelector).each(function () {
            new TooltippedElement($(this));
        });
    }
    enabled = true;
    delay = 200;
    activatedByClick = true;
    tooltipsForComments = false;


    mw.hook('wikipage.content').add(rt);

}());