MediaWiki:Gadget-refTooltip.js

$( function {	'use strict';	var i18n = {		cancelButton: 'Cancel',		doneButton: 'Done',		enableLabel: 'Enable reference tooltips',		optionsButtonTitle: 'Change reference tooltip options',		referencesSectionName: 'References',		saveFailedStorageFull: "Is your browser's localStorage full?",		saveFailedTitle: 'Saving options failed'	};	var $win = $( window );	var $body = $( '.mw-body' );	var $content = $( '#mw-content-text' );	var $tooltip = $;	var $tooltipText = $;	var tooltipRect, tooltipInnerWidth, tooltipOffset, $anchor, anchorRect;	var showTimer, hideTimer;	var loggedIn = !!mw.config.get( 'wgUserId' );	var options = {		enabled: true	};	if ( !loggedIn ) {		try {			options = JSON.parse( localStorage.refTooltip );		} catch ( e ) {}	}	var createTooltip = function( $anchor, content, showOptions ) {		// Get rid of any existing tooltip		$tooltip.remove;		// Create the tooltip		$tooltip = $( ' ' ).addClass( 'ref-tooltip' ).data( { anchor: $anchor, fresh: true, } ).hover( function { // Callback to the ref's hover functions when the tooltip is hovered over $anchor.mouseenter; }, function { $anchor.mouseleave; } );		$tooltipText = $( ' ' ).addClass( 'ref-tooltip-text' )			.append( content ).appendTo( $tooltip );		if ( showOptions ) {			$tooltipText.prepend( $( ' ' ).addClass( 'pixel-image ref-tooltip-options-button' ) .attr( 'title', i18n.optionsButtonTitle ) );		}		$( ' ' ).addClass( 'ref-tooltip-arrow' ).appendTo( $tooltip );		$tooltip.appendTo( 'body' );		// Set width to content size		$tooltipText.width( $tooltipText.width + 1 );		setPos( true );		// Data prevents tooltips being immediately closed if opened via a click		setTimeout( function { $tooltip.removeData( 'fresh' ); }, 0 );	};	var removeTooltip = function {		$tooltip.trigger( 'refTooltip-close' );		$tooltip.remove;		$tooltip = $;	};	var getRefText = function( $ref ) {		var refId = $ref.find( 'a' ).attr( 'href' ).split( '#' )[1];		var $refText = $( document.getElementById( refId ) ).clone;		$refText.find( '.mw-cite-backlink' ).remove;		return $refText.html;	};	var setPos = function( initial ) {		if ( !$tooltip.length ) {			return;		}		if ( initial ) {			tooltipRect = $tooltipText[0].getBoundingClientRect;			tooltipInnerWidth = $tooltipText.width;			tooltipOffset = {				top: parseFloat( $tooltipText.css( 'margin-top' ) ),				left: parseFloat( $tooltipText.css( 'margin-left' ) )			};			$anchor = $tooltip.data( 'anchor' );			anchorRect = $anchor[0].getBoundingClientRect;		} else {			$tooltip.removeClass( 'ref-tooltip-flipped' );			$tooltipText.css( 'margin-left', '' );		}		// Position the tooltip var tooltipPos = { top: $win.scrollTop, left: $win.scrollLeft };		if ( anchorRect.top + tooltipOffset.top < tooltipRect.height ) { $tooltip.addClass( 'ref-tooltip-flipped' ); tooltipPos.top += anchorRect.bottom; } else { tooltipPos.top += anchorRect.top - tooltipRect.height; }		tooltipPos.left += anchorRect.left + anchorRect.width / 2; // Stop it going off the side of the page var contentPadding = parseFloat( $body.css( 'padding-right' ) ); var contentBoundary = $body[0].getBoundingClientRect.right - contentPadding / 2; var overlap = anchorRect.left + tooltipOffset.left + tooltipRect.width - contentBoundary; if ( overlap > 0 ) { $tooltipText.css(				'margin-left',				Math.max( tooltipOffset.left - overlap, -tooltipInnerWidth )			); }		$tooltip.css( tooltipPos ); };	var bindRefHandlers = function { $content.on( {			'mouseenter.refTooltip': function {				var $this = $( this );				clearTimeout( hideTimer );				// Current tooltip, do nothing				if ( $tooltip.length && (					$this.is( $tooltip.data( 'anchor' ) ) || $.contains( $tooltip[0], this )				) ) {					return;				}				// Create the tooltip if timeout succeeds				showTimer = setTimeout( function { createTooltip( $this, getRefText( $this ), true ); }, 200 );			},			'mouseleave.refTooltip': function {				clearTimeout( showTimer );				// Remove the tooltip if timeout succeeds				hideTimer = setTimeout( function { removeTooltip; }, 300 );			}		}, '.reference' ); };	// When anywhere but the tooltip or anchor is clicked, remove it immediately $( window ).on( 'click.refTooltip', function( e ) {		if ( $tooltip.length && !$tooltip.data( 'fresh' ) && !$.contains( $tooltip[0], e.target ) ) {			clearTimeout( showTimer );			removeTooltip;		}	} ); // TODO: Replace with mw.util.escapeId on MW 1.27 var escapeId = function( id ) { return mw.util.rawurlencode( String( id ).replace( / /g, '_' ) ) .replace( /%3A/g, ':' ) .replace( /%/g, '.' ); };	$( document.getElementById( escapeId( i18n.referencesSectionName ) ) ).before(		$( ' ' ).addClass( 'pixel-image ref-tooltip-options-button' )			.attr( 'title', i18n.optionsButtonTitle )	); $( 'body' ).on( 'click.refTooltip', '.ref-tooltip-options-button', function( e ) {		// Just close the tooltip if it is already open		if ( $tooltip.length && $tooltip.data( 'anchor' ).is( e.target ) ) {			return;		}		// Disable ref handlers while options are open		$content.off( 'mouseenter.refTooltip mouseleave.refTooltip' );		$tooltip.on( 'refTooltip-close', function { if ( options.enabled ) { bindRefHandlers; }		} );		var $anchor = $( this );		$anchor.addClass( 'ref-tooltip-loading' );		// Replace current tooltip if clicking the options button within a tooltip		if ( $tooltip.length && $.contains( $tooltip[0], $anchor[0] ) ) {			$anchor = $tooltip.data( 'anchor' );		}		mw.loader.using( [ 'mediawiki.api', 'mediawiki.ui.button', 'mediawiki.ui.checkbox' ], function { $anchor.removeClass( 'ref-tooltip-loading' ); var $optionsText = $( ' ' ).addClass( 'ref-tooltip-options' ).append(				$( ' ' ).addClass( 'mw-ui-checkbox' ).append( $( ' ' ).attr( {						type: 'checkbox',						id: 'ref-tooltip-options-enabled',						checked: options.enabled					} ), $( ' ' ).attr( 'for', 'ref-tooltip-options-enabled' ).text( i18n.enableLabel ) ),				$( ' ' ).addClass( 'ref-tooltip-actions' ).append( $( ' ' ).addClass( 'mw-ui-button mw-ui-quiet' ).text( i18n.cancelButton ) .on( 'click.refTooltip', function {							removeTooltip;						} ), $( ' ' ).addClass( 'mw-ui-button mw-ui-progressive' ).text( i18n.doneButton ) .on( 'click.refTooltip', function {							options.enabled = $( '#ref-tooltip-options-enabled' ).prop( 'checked' );							var saveOptions = $.Deferred;							if ( loggedIn ) {								saveOptions = new mw.Api.postWithToken( 'edit', { action: 'options', optionname: 'gadget-refTooltip', optionvalue: options.enabled ? undefined : 0 } );							} else {								try {									localStorage.refTooltip = JSON.stringify( options );									saveOptions.resolve;								} catch ( e ) {									saveOptions.reject( 'storage' );								}							}							saveOptions.then( function { removeTooltip; }, function( code, error ) { mw.notify(									code === 'storage' ? i18n.saveFailedStorageFull : error,									{ title: i18n.saveFailedTitle }								); } );						} )				)			);			createTooltip( $anchor, $optionsText, false ); $tooltip.on( 'refTooltip-close', function {				if ( options.enabled ) {					bindRefHandlers;				}			} ); } );	} );	// Finally, enable tooltips if ( options.enabled ) { bindRefHandlers; } } );

// Add width and height to Element.getBoundingClientRect in IE8 // TODO: Remove on 1.27 if ( window.TextRectangle && !TextRectangle.prototype.width ) { Object.defineProperty( TextRectangle.prototype, 'width', {		get: function { return this.right - this.left; }	} ); Object.defineProperty( TextRectangle.prototype, 'height', {		get: function { return this.bottom - this.top; }	} ); }