跳转到内容

MediaWiki:Gadget-refTooltip.js

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

  • 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
$( function() {
	'use strict';
	
	var HanAssist = require("ext.gadget.HanAssist");
    var i18n = HanAssist.batchConv({
		cancelButton: {hans: "取消", hant: "取消"},
		doneButton: {hans: "完成", hant: "完成"},
		enableLabel: {hans: "启用参考提示", hant: "啟用參考提示"},
		optionsButtonTitle: {hans: "更改参考提示选项", hant: "變更參考提示選項"},
		referencesSectionName: '参考',
		saveFailedStorageFull: {hans: "你的浏览器本地内存是否已满?", hant: "您的瀏覽器的本機存儲空間是否已滿?"},
		saveFailedTitle: {hans: "选项保存失败", hant: "選項儲存失敗"}
	});
	
	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 = $( '<div>' ).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 = $( '<div>' ).addClass( 'ref-tooltip-text' )
			.append( content ).appendTo( $tooltip );
		if ( showOptions ) {
			$tooltipText.prepend(
				$( '<button>' ).addClass( 'pixel-image ref-tooltip-options-button' )
					.attr( 'title', i18n.optionsButtonTitle )
			);
		}
		$( '<div>' ).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(
		$( '<button>' ).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 = $( '<div>' ).addClass( 'ref-tooltip-options' ).append(
				$( '<div>' ).addClass( 'mw-ui-checkbox' ).append(
					$( '<input>' ).attr( {
						type: 'checkbox',
						id: 'ref-tooltip-options-enabled',
						checked: options.enabled
					} ),
					$( '<label>' ).attr( 'for', 'ref-tooltip-options-enabled' ).text( i18n.enableLabel )
				),
				$( '<div>' ).addClass( 'ref-tooltip-actions' ).append(
					$( '<button>' ).addClass( 'mw-ui-button mw-ui-quiet' ).text( i18n.cancelButton )
						.on( 'click.refTooltip', function() {
							removeTooltip();
						} ),
					$( '<button>' ).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; }
	} );
}
Cookie帮助我们提供我们的服务。通过使用我们的服务,您同意我们使用cookie。