MediaWiki:Gadget-ReferenceTooltips.js
items: [ this.panelSettings, this.panelDisabled ]
} );
this.$body.append( this.stackLayout.$element );
};
SettingsDialog.prototype.getSetupProcess = function ( data ) {
return SettingsDialog.parent.prototype.getSetupProcess.call( this, data )
.next( function () {
this.stackLayout.setItem( this.panelSettings );
this.actions.setMode( 'basic' );
}, this );
};
SettingsDialog.prototype.getActionProcess = function ( action ) {
var dialog = this;
if ( action === 'save' ) {
return new OO.ui.Process( function () {
var newDelay = Number( dialog.delayInput.getValue() );
enabled = dialog.enableOption.isSelected();
if ( newDelay >= 0 && newDelay <= 5000 ) {
delay = newDelay;
}
activatedByClick = dialog.clickOption.isSelected();
tooltipsForComments = dialog.tooltipsForCommentsCheckbox.isSelected();
setSettingsCookie();
if ( enabled ) {
dialog.close();
disableRt();
rt( $content );
} else {
dialog.actions.setMode( 'disabled' );
dialog.stackLayout.setItem( dialog.panelDisabled );
disableRt();
addEnableLink();
}
} );
} else if ( action === 'deactivated' ) {
dialog.close();
}
return SettingsDialog.parent.prototype.getActionProcess.call( this, action );
};
SettingsDialog.prototype.getBodyHeight = function () {
return this.stackLayout.getCurrentItem().$element.outerHeight( true );
};
tooltip.upToTopParent( function adjustRightAndHide() {
if ( this.isPresent ) {
if ( this.$element[ 0 ].style.right ) {
this.$element.css(
'right',
'+=' + ( window.innerWidth - $window.width() )
);
}
this.te.hideRef( true );
}
} );
if ( !windowManager ) {
windowManager = new OO.ui.WindowManager();
$body.append( windowManager.$element );
}
settingsDialog = new SettingsDialog();
windowManager.addWindows( [ settingsDialog ] );
settingsWindow = windowManager.openWindow( settingsDialog );
settingsWindow.opened.then( function () {
settingsDialogOpening = false;
} );
settingsWindow.closed.then( function () {
windowManager.clearWindows();
} );
}
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 );
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 = $(  )
.addClass( 'rt-tooltip' )
.attr( 'id', this.id )
.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(  )
.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 ) {
$(  )
.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 );
} )
.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 = $(  )
.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() + 6 ) {
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 ) );
} );
}
settingsString = mw.cookie.get( 'RTsettings',  );
if ( settingsString ) {
settings = settingsString.split( '|' );
enabled = Boolean( Number( settings[ 0 ] ) );
delay = Number( settings[ 1 ] );
activatedByClick = Boolean( Number( settings[ 2 ] ) );
// The forth value was added later, so we provide for a default value. See comments below
// for why we use "IS_TOUCHSCREEN && IS_MOBILE".
tooltipsForComments = settings[ 3 ] === undefined ?
IS_TOUCHSCREEN && IS_MOBILE :
Boolean( Number( settings[ 3 ] ) );
} else {
enabled = true;
delay = 200;
// Since the mobile browser check is error-prone, adding IS_MOBILE condition here would probably
// leave cases where a user interacting with the browser using touches doesn't know how to call
// a tooltip in order to switch to activation by click. Some touch-supporting laptop users
// interacting by touch (though probably not the most popular use case) would not be happy too.
activatedByClick = IS_TOUCHSCREEN;
// Arguably we shouldn't convert native tooltips into gadget tooltips for devices that have
// mouse support, even if they have touchscreens (there are laptops with touchscreens).
// IS_TOUCHSCREEN check here is for reliability, since the mobile check is prone to false
// positives.
tooltipsForComments = IS_TOUCHSCREEN && IS_MOBILE;
}
mw.hook( 'wikipage.content' ).add( rt );
}() );