MediaWiki:Gadget-GeneralTooltips.js
阅读:107 更新:2022-8-4
来自柯南百科
注意:在发布之后,您可能需要清除浏览器缓存才能看到所作出的变更的影响。
- 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。
(function () {
var COMMENTED_TEXT_SELECTOR = '.tt-commentedText, .tt-general, .tt',
SOURCE_CLASS = 'tt',
TARGET_CLASS = 'tt-general',
TOOLTIP_TIMEOUT = 500 /* ms */;
// "Global" variables
var CLASSES = {
FADE_IN_DOWN: 'tt-fade-in-down',
FADE_IN_UP: 'tt-fade-in-up',
FADE_OUT_DOWN: 'tt-fade-out-down',
FADE_OUT_UP: 'tt-fade-out-up'
},
CLIENT_NAME = $.client.profile().name,
enabled, delay,
$body = $(document.body),
$window = $(window);
function isMobile() {
return /Mobi|Android/i.test(navigator.userAgent);
}
function activatedByClick() {
return isMobile();
}
function tt($content) {
// Popups gadget & Reference Previews
if (window.pg || mw.config.get("wgPopupsReferencePreviews")) {
return;
}
var teSelector;
function enableTt() {
enabled = true;
$('.tt-enableItem').remove();
rt($content);
mw.notify(mw.msg('tt-enabled'));
}
function disableTt() {
$content.find(teSelector).removeClass('tt-commentedText').off('.tt');
$body.off('.tt');
$window.off('.tt');
}
function TooltippedElement($element) {
var events,
te = this;
function onStartEvent(e) {
var showRefArgs;
e.preventDefault();
if (!te.noRef) {
showRefArgs = [$(this)];
te.showRef.apply(te, showRefArgs);
}
}
function onEndEvent() {
if (!te.noRef) {
if (te.tooltip) {
$hover = $(':hover').filter('.tt-tooltip');
if ($hover.length > 0) {
return;
}
}
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;
this.type = 'commentedText';
this.saveComment = this.$element.attr('title');
this.comment = this.$element.next().find('.tt-hidetext').html();
if (!this.comment) {
return;
}
//YOHO 增加有链接时的按钮
if (this.$element.closest('a').length || this.$element.has('a').length) {
this.comment += '<a href="' + this.$element.find("a").attr('href') + '"><div class="tt-Link"></div></a>';
}
this.$element.addClass('tt-commentedText');
if (activatedByClick()) {
events = {
'click.tt': onStartEvent
};
// Adds an ability to see tooltips for links
if (this.$element.closest('a').length || this.$element.has('a').length) {
events['contextmenu.tt'] = onStartEvent;
}
} else {
events = {
'mouseenter.tt': onStartEvent,
'mouseleave.tt': onEndEvent
};
}
this.$element.on(events);
this.hideRef = function (immediately) {
clearTimeout(te.showTimer);
if (this.type === 'commentedText') {
this.$element.attr('title', this.saveComment);
}
if (this.tooltip && this.tooltip.isPresent) {
this.tooltip.hide();
} else if (this.$ref && this.$ref.hasClass('tt-target')) {
this.$ref.removeClass('tt-target');
$body.off('click.tt touchstart.tt', this.onBodyClick);
}
};
this.showRef = function ($element, ePageX, ePageY) {
// Popups gadget
if (window.pg) {
disableTt();
return;
}
if (this.tooltip && !this.tooltip.$content.length) {
return;
}
var tooltipInitiallyPresent = this.tooltip && this.tooltip.isPresent;
function reallyShow() {
var viewportTop, refOffsetTop, teHref;
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('.tt-tooltip').data('tooltip');
if (te.tooltip.parent && te.tooltip.parent.disappearing) {
return;
}
te.tooltip.show();
if (tooltipInitiallyPresent) {
if (te.tooltip.$element.hasClass('tt-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.tt', 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;
this.$element.attr('title', '');
if (tooltipInitiallyPresent ||
(this.$ref && this.$ref.hasClass('tt-target'))
) {
return;
} else if (activatedByClick()) {
setTimeout(function () {
$body.on('click.tt touchstart.tt', te.onBodyClick);
}, 0);
}
reallyShow();
};
this.onBodyClick = function (e) {
if (!te.tooltip && !te.$ref.hasClass('tt-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('tt-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;
this.id = 'tt-' + String(Math.random()).slice(2);
this.$content = $('<div></div>').html(this.te.comment);
if (!this.$content.length) {
return;
}
this.insideWindow = Boolean(this.te.$element.closest('.oo-ui-window').length);
this.$element = $('<div>')
.addClass('tt-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('tt-tooltip-insideWindow');
}
var tte = this.te;
if (!activatedByClick()) {
this.$element.on('mouseleave.tt',
function () {
tte.hideRef();
}
)
}
// We need the $content interlayer here in order for the settings icon to have correct
// margins
this.$content = this.$content
//.wrapAll('<div>')
//.parent()
.addClass('tt-tooltipContent')
.addClass('mw-parser-output')
.appendTo(this.$element);
// 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('tt-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('tt-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', '');
$body.off('click.tt touchstart.tt', tooltip.te.onBodyClick);
$window.off('resize.tt', 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('tt-tooltip-above')
.addClass('tt-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('tt-tooltip-below')
.addClass('tt-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;
}
};
}
teSelector = '';
teSelector += COMMENTED_TEXT_SELECTOR;
$content.find(teSelector).each(function () {
if ($(this).hasClass(SOURCE_CLASS)) {
$(this).removeClass(SOURCE_CLASS).addClass(TARGET_CLASS)
$(this).data('toggle', '');
}
new TooltippedElement($(this));
});
}
enabled = true;
delay = 200;
mw.hook('wikipage.content').add(tt);
}());