mediawiki-extensions-Revisi.../modules/ext.RevisionSlider.RevisionListView.js
Leszek Manicki d737153e29 Smarter setting of the "gravity" of tooltips
This adjusts the position of the tooltip depending on what is the
position of the related revision bar in the plot, and on the
size of the contents of the tooltip.

This change makes the tooltip be always displayed below
the revision plot, so it is visible to the user no matter if there is
enough (visible) space above the plot.
Also now it is checked if there is enough space on the left and right
side of the browser window to display the tooltip centered horizontally.
If not, this adjusts the horizontal position of the tooltip so that
it does not get shown outside of the window.
The latter in particular improves displaying of longer edit
summaries in RTL mode. Apparently LTR-centered browsers took care
of not showing the tooltip outside the right edge of the window
but in the case of left edge the tooltip could run outside of the window
leaving a part of summary not visible to the user without scrolling.

Bug: T141071
Bug: T141093
Change-Id: I8d519c5fd42d8403b527fa97d72a5c46991fc27b
2016-07-25 12:14:42 +02:00

266 lines
7.1 KiB
JavaScript

( function ( mw, $ ) {
/**
* @param {RevisionList} revisionList
* @constructor
*/
var RevisionListView = function ( revisionList ) {
this.revisionList = revisionList;
};
$.extend( RevisionListView.prototype, {
/**
* @type {RevisionList}
*/
revisionList: null,
/**
* @type {number}
*/
tooltipTimeout: -1,
/**
* @type {jQuery}
*/
currentTooltip: null,
/**
* @param {number} revisionTickWidth
* @return {jQuery}
*/
render: function ( revisionTickWidth ) {
var $html = $( '<div>' ).addClass( 'mw-revslider-revisions' ),
revs = this.revisionList.getRevisions(),
maxChangeSizeLogged = Math.log( this.revisionList.getBiggestChangeSize() ),
self = this,
i, diffSize, tooltip, relativeChangeSize,
showTooltip = function () {
self.showTooltip( $( this ) );
$( this ).tipsy( 'show' );
},
hideTooltip = function () {
self.hideTooltip( $( this ) );
},
tooltipGravity = function () {
// Returns a function setting a gravity of the tooltip so that it will be entirely visible
// Based on tipsy's own $.fn.tipsy.autoBounds, with considering the width of the
// inner contents of the tooltip, and assuming the gravity always starts with 'n'
return function () {
var dir = 'n',
$this = $( this ),
$tip = $this.tipsy( true ).$tip,
boundLeft = $( document ).scrollLeft() + $tip.outerWidth();
if ( $this.offset().left < boundLeft ) {
dir += 'w';
}
if ( $( window ).width() + $( document ).scrollLeft() - $this.offset().left < 0 ) {
dir += 'e';
}
return dir;
};
};
for ( i = 0; i < revs.length; i++ ) {
diffSize = revs[ i ].getRelativeSize();
relativeChangeSize = diffSize !== 0 ? Math.ceil( 65.0 * Math.log( Math.abs( diffSize ) ) / maxChangeSizeLogged ) + 5 : 0;
tooltip = this.makeTooltip( revs[ i ] );
$html
.append( $( '<div>' )
.addClass( 'mw-revslider-revision-wrapper' )
.attr( 'title', tooltip )
.width( revisionTickWidth )
.tipsy( {
gravity: tooltipGravity(),
html: true,
trigger: 'manual',
className: 'mw-revslider-revision-tooltip mw-revslider-revision-tooltip-' + ( i + 1 )
} )
.append( $( '<div>' )
.addClass( 'mw-revslider-revision' )
.attr( 'data-revid', revs[ i ].getId() )
.attr( 'data-pos', i + 1 )
.css( {
height: relativeChangeSize + 'px',
width: revisionTickWidth + 'px',
top: diffSize > 0 ? '-' + relativeChangeSize + 'px' : 0
} )
.addClass( diffSize > 0 ? 'mw-revslider-revision-up' : 'mw-revslider-revision-down' )
.append( $( '<div>' ).addClass( 'mw-revslider-revision-border-box' ) )
)
.mouseover( showTooltip )
.mouseout( hideTooltip )
);
}
this.keepTooltipsOnHover();
return $html;
},
/**
* Hides the current tooltip immediately
*/
hideCurrentTooltip: function () {
if ( this.tooltipTimeout !== -1 ) {
window.clearTimeout( this.tooltipTimeout );
this.currentTooltip.tipsy( 'hide' );
this.currentTooltip.removeClass( 'mw-revslider-revision-wrapper-hovered' );
}
},
/**
* Hides the tooltip after 500ms
*
* @param {jQuery} $rev
*/
hideTooltip: function ( $rev ) {
this.tooltipTimeout = window.setTimeout( function () {
$rev.tipsy( 'hide' );
$rev.removeClass( 'mw-revslider-revision-wrapper-hovered' );
}, 500 );
},
/**
* Hides the previous tooltip and shows the new one
*
* @param {jQuery} $rev
*/
showTooltip: function ( $rev ) {
this.hideCurrentTooltip();
$rev.tipsy( 'show' );
$rev.addClass( 'mw-revslider-revision-wrapper-hovered' );
this.currentTooltip = $rev;
},
/**
* Sets event handlers on tooltips so they do not disappear when hovering over them
*/
keepTooltipsOnHover: function () {
var self = this;
$( document )
.on( 'mouseover', '.mw-revslider-revision-tooltip', function () {
window.clearTimeout( self.tooltipTimeout );
} )
.on( 'mouseout', '.mw-revslider-revision-tooltip', function () {
self.hideTooltip( self.currentTooltip );
} );
},
/**
* Generates the HTML for a tooltip that appears on hover above each revision on the slider
*
* @param {Revision} rev
* @return {string}
*/
makeTooltip: function ( rev ) {
var $tooltip = $( '<div>' )
.append(
$( '<p>' ).append(
mw.message( 'revisionslider-label-date', rev.getFormattedDate() ).parseDom()
),
this.makeUserLine( rev.getUser() ),
this.makeCommentLine( rev ),
this.makePageSizeLine( rev.getSize() ),
this.makeChangeSizeLine( rev.getRelativeSize() ),
rev.isMinor() ? $( '<p>' ).text( mw.message( 'revisionslider-minoredit' ).text() ) : ''
);
return $tooltip.html();
},
/**
* Generates a link to user page or to contributions page for IP addresses
*
* @param {string} user
* @return {string}
*/
getUserPage: function ( user ) {
return ( mw.util.isIPAddress( user, false ) ? 'Special:Contributions/' : 'User:' ) + mw.html.escape( user );
},
/**
* Generates the HTML for the user label
*
* @param {string} userString
* @return {string|jQuery}
*/
makeUserLine: function ( userString ) {
if ( !userString ) {
return '';
}
return $( '<bdi>' ).append( $( '<p>' ).append(
mw.message( 'revisionslider-label-username', mw.html.escape( userString ), this.getUserPage( userString ) ).parseDom()
) );
},
/**
* Generates the HTML for the comment label
*
* @param {Revision} rev
* @return {string|jQuery}
*/
makeCommentLine: function ( rev ) {
if ( rev.hasEmptyComment() ) {
return '';
}
return $( '<bdi>' ).append(
$( '<p>' ).append(
$( '<strong>' ).text( mw.message( 'revisionslider-label-comment' ).text() ),
$( '<em>' ).append(
rev.getParsedComment()
)
)
);
},
/**
* Generates the HTML for the page size label
*
* @param {number} size
* @return {jQuery}
*/
makePageSizeLine: function ( size ) {
return $( '<p>' ).append(
mw.message( 'revisionslider-label-page-size', mw.language.convertNumber( size ), size ).parseDom()
);
},
/**
* Generates the HTML for the change size label
*
* @param {number} relativeSize
* @return {jQuery}
*/
makeChangeSizeLine: function ( relativeSize ) {
var changeSizeClass = 'mw-revslider-change-none',
leadingSign = '',
$changeNumber;
if ( relativeSize > 0 ) {
changeSizeClass = 'mw-revslider-change-positive';
leadingSign = '+';
} else if ( relativeSize < 0 ) {
changeSizeClass = 'mw-revslider-change-negative';
}
$changeNumber = $( '<span>' )
.addClass( changeSizeClass )
.attr( {
dir: 'ltr' // Make sure that minus/plus is on the left
} )
.text( leadingSign + mw.language.convertNumber( relativeSize ) );
return $( '<p>' ).append(
mw.message( 'revisionslider-label-change-size', $changeNumber, relativeSize ).parseDom()
);
}
} );
mw.libs.revisionSlider = mw.libs.revisionSlider || {};
mw.libs.revisionSlider.RevisionListView = RevisionListView;
}( mediaWiki, jQuery ) );