MediaWiki:Gadget-ReferenceTooltips.js
阅读:105 更新:2020-7-12
来自柯南百科
注意:在发布之后,您可能需要清除浏览器缓存才能看到所作出的变更的影响。
- Firefox或Safari:按住Shift的同时单击刷新,或按Ctrl-F5或Ctrl-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);
}());