diff --git a/resources/mmv/mmv.js b/resources/mmv/mmv.js
index 71724a28d..be456a922 100755
--- a/resources/mmv/mmv.js
+++ b/resources/mmv/mmv.js
@@ -632,6 +632,33 @@
}
};
+ /**
+ * @event mmv.close
+ * Fired when the viewer should be closed. This is used by components (e.g. the close button)
+ * to notify the main app that it should close.
+ */
+ /**
+ * @event mmv-close
+ * Fired when the viewer is closed. This is used by the main app to notify other components
+ * (notably the bootstrap).
+ */
+ /**
+ * @event mmv-next
+ * Fired when the user requests the next image.
+ */
+ /**
+ * @event mmv-prev
+ * Fired when the user requests the previous image.
+ */
+ /**
+ * @event mmv-resize
+ * Fired when the screen size changes.
+ */
+ /**
+ * @event mmv-request-thumbnail
+ * Used by components to request a thumbnail URL for the current thumbnail, with a given size.
+ * @param {number} size
+ */
/**
* Registers all event handlers
*/
diff --git a/resources/mmv/ui/mmv.ui.metadataPanel.js b/resources/mmv/ui/mmv.ui.metadataPanel.js
index 356c5a676..c390176cd 100644
--- a/resources/mmv/ui/mmv.ui.metadataPanel.js
+++ b/resources/mmv/ui/mmv.ui.metadataPanel.js
@@ -215,7 +215,7 @@
};
MPP.initializeButtons = function () {
- this.buttons = new mw.mmv.ui.StripeButtons( this.$titleDiv );
+ this.buttons = new mw.mmv.ui.StripeButtons( this.$titleDiv, window.localStorage );
};
/**
diff --git a/resources/mmv/ui/mmv.ui.stripeButtons.js b/resources/mmv/ui/mmv.ui.stripeButtons.js
index dc82bfc45..756aa0f65 100644
--- a/resources/mmv/ui/mmv.ui.stripeButtons.js
+++ b/resources/mmv/ui/mmv.ui.stripeButtons.js
@@ -25,10 +25,14 @@
* metadata panel).
* @constructor
* @param {jQuery} $container
+ * @param {Object} localStorage the localStorage object, for dependency injection
*/
- function StripeButtons( $container ) {
+ function StripeButtons( $container, localStorage ) {
mw.mmv.ui.Element.call( this, $container );
+ /** @property {Object} localStorage the window.localStorage object */
+ this.localStorage = localStorage;
+
this.$buttonContainer = $( '
' )
.addClass( 'mw-mmv-stripe-button-container' )
.appendTo( $container );
@@ -106,10 +110,83 @@
href: this.getFeedbackSurveyUrl()
} ).click( function ( e ) {
buttons.openSurveyInNewWindow();
+ buttons.maxOutTooltipDisplayCount();
e.preventDefault();
} );
};
+ SBP.feedbackSettings = {
+ /** Show the tooltip this many seconds to get the user's attention, even when it is not hovered. */
+ tooltipDisplayDuration: 5,
+ /** Wait for this long after the viewer is opened, before showing the tooltip. */
+ tooltipDelay: 5,
+ /** Only show the tooltip this many times */
+ tooltipMaxDisplayCount: 3
+ };
+
+ /**
+ * Returns the number of times the tooltip was shown so far. This number is set to 999 if the
+ * user clicked on the link already, or we cannot count how many times the tooltip was shown
+ * already.
+ * @return {number}
+ */
+ SBP.getTooltipDisplayCount = function () {
+ if ( !this.localStorage ) {
+ return 999;
+ }
+ if ( this.tooltipDisplayCount === undefined ) {
+ this.tooltipDisplayCount = this.localStorage.getItem( 'mmv.tooltipDisplayCount' );
+ if ( this.tooltipDisplayCount === null ) {
+ this.tooltipDisplayCount = 0;
+ this.localStorage.setItem( 'mmv.tooltipDisplayCount', 0 );
+ }
+ }
+ return this.tooltipDisplayCount;
+ };
+
+ /**
+ * Increases tooltip display count.
+ */
+ SBP.increaseTooltipDisplayCount = function () {
+ this.getTooltipDisplayCount();
+ if ( this.tooltipDisplayCount !== undefined ) {
+ this.tooltipDisplayCount++;
+ this.localStorage.setItem( 'mmv.tooltipDisplayCount', this.tooltipDisplayCount );
+ }
+ };
+
+ /**
+ * Sets tooltip display count so large that the tooltip will never be shown again.
+ * We use this for users who already opened the form.
+ */
+ SBP.maxOutTooltipDisplayCount = function () {
+ this.getTooltipDisplayCount();
+ if ( this.tooltipDisplayCount !== undefined ) {
+ this.tooltipDisplayCount = 999;
+ this.localStorage.setItem( 'mmv.tooltipDisplayCount', this.tooltipDisplayCount );
+ }
+ };
+
+ /**
+ * Show the tooltip to the user if it was not shown often enough yet.
+ */
+ SBP.maybeDisplayTooltip = function () {
+ if (
+ this.readyToShowFeedbackTooltip &&
+ this.getTooltipDisplayCount() < this.feedbackSettings.tooltipMaxDisplayCount
+ ) {
+ this.buttons.$feedback.tipsy( 'show' );
+ this.setTimer( 'feedbackTooltip.hide', function () {
+ this.buttons.$feedback.tipsy( 'hide' );
+ }, this.feedbackSettings.tooltipDisplayDuration * 1000 );
+ this.increaseTooltipDisplayCount();
+ this.readyToShowFeedbackTooltip = false;
+ } else {
+ // if the tooltip is visible already, make sure it is not hidden too quickly
+ this.resetTimer( 'feedbackTooltip.hide' );
+ }
+ };
+
/**
* Checks if it is suitable to show a survey to the current user.
*/
@@ -193,6 +270,8 @@
if ( !mw.user.isAnon() ) {
this.setDescriptionPageButton( imageInfo, repoInfo );
}
+
+ this.maybeDisplayTooltip();
};
/**
@@ -221,7 +300,7 @@
this.setInlineStyle( 'stripe-button-description-page',
'.mw-mmv-stripe-button-dynamic:before {' +
'background-image: url("' + repoInfo.favIcon + '");' +
- '}'
+ '}'
);
}
};
@@ -263,6 +342,12 @@
this.handleEvent( 'mmv-reuse-closed', function () {
buttons.$reuse.removeClass( 'open' );
} );
+
+ this.readyToShowFeedbackTooltip = false;
+ this.setTimer( 'feedbackTooltip.show', function () {
+ this.readyToShowFeedbackTooltip = true;
+ this.maybeDisplayTooltip();
+ }, this.feedbackSettings.tooltipDelay * 1000 );
};
/**
@@ -271,6 +356,8 @@
SBP.unattach = function () {
this.constructor.super.prototype.unattach.call( this );
this.buttons.$reuse.off( 'click.mmv-stripeButtons' );
+
+ this.clearTimer( 'feedbackTooltip.show' );
};
mw.mmv.ui.StripeButtons = StripeButtons;
diff --git a/tests/qunit/mmv/ui/mmv.ui.stripeButtons.test.js b/tests/qunit/mmv/ui/mmv.ui.stripeButtons.test.js
index ed1280bc9..11ca67b79 100644
--- a/tests/qunit/mmv/ui/mmv.ui.stripeButtons.test.js
+++ b/tests/qunit/mmv/ui/mmv.ui.stripeButtons.test.js
@@ -23,6 +23,7 @@
// pretend surveys are enabled for this site
oldShowSurvey = mw.config.get( 'wgMultimediaViewer' ).showSurvey;
mw.config.get( 'wgMultimediaViewer' ).showSurvey = true;
+ this.clock = this.sandbox.useFakeTimers();
},
teardown: function () {
mw.config.get( 'wgMultimediaViewer' ).showSurvey = oldShowSurvey;
@@ -31,7 +32,10 @@
function createStripeButtons() {
var fixture = $( '#qunit-fixture' );
- return new mw.mmv.ui.StripeButtons( fixture );
+ return new mw.mmv.ui.StripeButtons( fixture, {
+ getItem: function () { return 999; },
+ setItem: $.noop
+ } );
}
QUnit.test( 'Sanity test, object creation and UI construction', 4, function ( assert ) {
@@ -110,4 +114,57 @@
$( document ).off( 'mmv-reuse-open.test' );
} );
+
+ QUnit.test( 'Feedback tooltip', 8, function ( assert ) {
+ var buttons = createStripeButtons(),
+ displayCount,
+ hasTooltip = function () { return !!$( '.tipsy' ).length; };
+
+ this.sandbox.stub( buttons.localStorage, 'getItem', function () { return displayCount; } );
+ this.sandbox.stub( buttons.localStorage, 'setItem', function ( _, val ) { displayCount = val; } );
+
+ displayCount = 0;
+ buttons.attach();
+
+ assert.ok( !hasTooltip(), 'No tooltip initially' );
+
+ this.clock.tick( 1000 );
+ assert.ok( !hasTooltip(), 'No tooltip after 1s' );
+
+ this.clock.tick( 5000 );
+ assert.ok( hasTooltip(), 'Tooltip visible after 6s' );
+ assert.strictEqual( displayCount, 1, 'displayCount was increased' );
+
+ this.clock.tick( 5000 );
+ assert.ok( !hasTooltip(), 'Tooltip hidden again after 11s' );
+
+ buttons.unattach();
+ delete buttons.tooltipDisplayCount;
+
+ displayCount = 3;
+ buttons.attach();
+
+ this.clock.tick( 6000 );
+ assert.ok( !hasTooltip(), 'No tooltip after 6s when display count limit reached' );
+
+ buttons.unattach();
+ delete buttons.tooltipDisplayCount;
+
+ displayCount = 0;
+ buttons.openSurveyInNewWindow = this.sandbox.stub();
+ buttons.attach();
+ buttons.buttons.$feedback.triggerHandler( 'click' );
+
+ this.clock.tick( 6000 );
+ assert.ok( !hasTooltip(), 'No tooltip if button was clicked' );
+
+ buttons.unattach();
+ delete buttons.tooltipDisplayCount;
+
+ displayCount = 0;
+ buttons.attach();
+ buttons.unattach();
+ this.clock.tick( 6000 );
+ assert.ok( !hasTooltip(), 'No tooltip when unattached' );
+ } );
}( mediaWiki, jQuery ) );