4 * @author Ryan Kaldari, 2010
5 * @author Neil Kandalgaonkar, 2010-11
6 * @author Moriel Schottlender, 2015
11 ( function ( mw
, $ ) {
13 * This is a way of getting simple feedback from users. It's useful
14 * for testing new features -- users can give you feedback without
15 * the difficulty of opening a whole new talk page. For this reason,
16 * it also tends to collect a wider range of both positive and negative
17 * comments. However you do need to tend to the feedback page. It will
18 * get long relatively quickly, and you often get multiple messages
19 * reporting the same issue.
21 * It takes the form of thing on your page which, when clicked, opens a small
22 * dialog box. Submitting that dialog box appends its contents to a
23 * wiki page that you specify, as a new section.
25 * This feature works with classic MediaWiki pages
26 * and is not compatible with LiquidThreads or Flow.
28 * Minimal usage example:
30 * var feedback = new mw.Feedback();
31 * $( '#myButton' ).click( function () { feedback.launch(); } );
33 * You can also launch the feedback form with a prefilled subject and body.
34 * See the docs for the #launch() method.
38 * @param {Object} [config] Configuration object
39 * @cfg {mw.Api} [api] if omitted, will just create a standard API
40 * @cfg {mw.Title} [title="Feedback"] The title of the page where you collect
42 * @cfg {string} [dialogTitleMessageKey="feedback-dialog-title"] Message key for the
43 * title of the dialog box
44 * @cfg {mw.Uri|string} [bugsLink="//phabricator.wikimedia.org/maniphest/task/create/"] URL where
46 * @cfg {mw.Uri|string} [bugsListLink="//phabricator.wikimedia.org/maniphest/query/advanced"] URL
47 * where bugs can be listed
48 * @cfg {boolean} [showUseragentCheckbox=false] Show a Useragent agreement checkbox as part of the form.
49 * @cfg {boolean} [useragentCheckboxMandatory=false] Make the Useragent checkbox mandatory.
50 * @cfg {string|jQuery} [useragentCheckboxMessage] Supply a custom message for the useragent checkbox.
51 * defaults to a combination of 'feedback-terms' and 'feedback-termsofuse' which includes a link to the
52 * wiki's Term of Use page.
54 mw
.Feedback
= function MwFeedback( config
) {
55 config
= config
|| {};
57 this.api
= config
.api
|| new mw
.Api();
58 this.dialogTitleMessageKey
= config
.dialogTitleMessageKey
|| 'feedback-dialog-title';
60 // Feedback page title
61 this.feedbackPageTitle
= config
.title
|| new mw
.Title( 'Feedback' );
64 this.bugsTaskSubmissionLink
= config
.bugsLink
|| '//phabricator.wikimedia.org/maniphest/task/create/';
65 this.bugsTaskListLink
= config
.bugsListLink
|| '//phabricator.wikimedia.org/maniphest/query/advanced';
68 this.useragentCheckboxShow
= !!config
.showUseragentCheckbox
;
69 this.useragentCheckboxMandatory
= !!config
.useragentCheckboxMandatory
;
70 this.useragentCheckboxMessage
= config
.useragentCheckboxMessage
||
72 .append( mw
.msg( 'feedback-terms' ) )
73 .add( $( '<p>' ).append( mw
.message( 'feedback-termsofuse' ).parse() ) );
76 this.thankYouDialog
= new OO
.ui
.MessageDialog();
80 OO
.initClass( mw
.Feedback
);
82 /* Static Properties */
83 mw
.Feedback
.static.windowManager
= null;
84 mw
.Feedback
.static.dialog
= null;
89 * Respond to dialog submit event. If the information was
90 * submitted, either successfully or with an error, open
91 * a MessageDialog to thank the user.
92 * @param {string} [status] A status of the end of operation
93 * of the main feedback dialog. Empty if the dialog was
94 * dismissed with no action or the user followed the button
95 * to the external task reporting site.
97 mw
.Feedback
.prototype.onDialogSubmit = function ( status
) {
98 var dialogConfig
= {};
102 title
: mw
.msg( 'feedback-thanks-title' ),
103 message
: $( '<span>' ).append(
106 this.feedbackPageTitle
.getNameText(),
110 href
: this.feedbackPageTitle
.getUrl()
117 label
: mw
.msg( 'feedback-close' ),
127 title
: mw
.msg( 'feedback-error-title' ),
128 message
: mw
.msg( 'feedback-' + status
),
132 label
: mw
.msg( 'feedback-close' ),
140 // Show the message dialog
141 if ( !$.isEmptyObject( dialogConfig
) ) {
142 this.constructor.static.windowManager
.openWindow(
150 * Modify the display form, and then open it, focusing interface on the subject.
152 * @param {Object} [contents] Prefilled contents for the feedback form.
153 * @param {string} [contents.subject] The subject of the feedback
154 * @param {string} [contents.message] The content of the feedback
156 mw
.Feedback
.prototype.launch = function ( contents
) {
158 if ( !this.constructor.static.dialog
) {
159 this.constructor.static.dialog
= new mw
.Feedback
.Dialog();
160 this.constructor.static.dialog
.connect( this, { submit
: 'onDialogSubmit' } );
162 if ( !this.constructor.static.windowManager
) {
163 this.constructor.static.windowManager
= new OO
.ui
.WindowManager();
164 this.constructor.static.windowManager
.addWindows( [
165 this.constructor.static.dialog
,
169 .append( this.constructor.static.windowManager
.$element
);
172 this.constructor.static.windowManager
.openWindow(
173 this.constructor.static.dialog
,
175 title
: mw
.msg( this.dialogTitleMessageKey
),
178 title
: this.feedbackPageTitle
,
179 dialogTitleMessageKey
: this.dialogTitleMessageKey
,
180 bugsTaskSubmissionLink
: this.bugsTaskSubmissionLink
,
181 bugsTaskListLink
: this.bugsTaskListLink
,
183 show
: this.useragentCheckboxShow
,
184 mandatory
: this.useragentCheckboxMandatory
,
185 message
: this.useragentCheckboxMessage
197 * @extends OO.ui.ProcessDialog
200 * @param {Object} config Configuration object
202 mw
.Feedback
.Dialog
= function mwFeedbackDialog( config
) {
203 // Parent constructor
204 mw
.Feedback
.Dialog
.super.call( this, config
);
207 this.feedbackPageTitle
= null;
209 this.$element
.addClass( 'mwFeedback-Dialog' );
212 OO
.inheritClass( mw
.Feedback
.Dialog
, OO
.ui
.ProcessDialog
);
214 /* Static properties */
215 mw
.Feedback
.Dialog
.static.name
= 'mwFeedbackDialog';
216 mw
.Feedback
.Dialog
.static.title
= mw
.msg( 'feedback-dialog-title' );
217 mw
.Feedback
.Dialog
.static.size
= 'medium';
218 mw
.Feedback
.Dialog
.static.actions
= [
221 label
: mw
.msg( 'feedback-submit' ),
222 flags
: [ 'primary', 'constructive' ]
226 label
: mw
.msg( 'feedback-external-bug-report-button' ),
227 flags
: 'constructive'
231 label
: mw
.msg( 'feedback-cancel' ),
239 mw
.Feedback
.Dialog
.prototype.initialize = function () {
240 var feedbackSubjectFieldLayout
, feedbackMessageFieldLayout
,
241 feedbackFieldsetLayout
;
244 mw
.Feedback
.Dialog
.super.prototype.initialize
.call( this );
246 this.feedbackPanel
= new OO
.ui
.PanelLayout( {
252 this.$spinner
= $( '<div>' )
253 .addClass( 'feedback-spinner' );
256 this.feedbackMessageLabel
= new OO
.ui
.LabelWidget( {
257 classes
: [ 'mw-feedbackDialog-welcome-message' ]
259 this.feedbackSubjectInput
= new OO
.ui
.TextInputWidget( {
262 this.feedbackMessageInput
= new OO
.ui
.TextInputWidget( {
266 feedbackSubjectFieldLayout
= new OO
.ui
.FieldLayout( this.feedbackSubjectInput
, {
267 label
: mw
.msg( 'feedback-subject' )
269 feedbackMessageFieldLayout
= new OO
.ui
.FieldLayout( this.feedbackMessageInput
, {
270 label
: mw
.msg( 'feedback-message' )
272 feedbackFieldsetLayout
= new OO
.ui
.FieldsetLayout( {
273 items
: [ feedbackSubjectFieldLayout
, feedbackMessageFieldLayout
],
274 classes
: [ 'mw-feedbackDialog-feedback-form' ]
277 // Useragent terms of use
278 this.useragentCheckbox
= new OO
.ui
.CheckboxInputWidget();
279 this.useragentFieldLayout
= new OO
.ui
.FieldLayout( this.useragentCheckbox
, {
280 classes
: [ 'mw-feedbackDialog-feedback-terms' ],
284 this.feedbackPanel
.$element
.append(
285 this.feedbackMessageLabel
.$element
,
286 feedbackFieldsetLayout
.$element
,
287 this.useragentFieldLayout
.$element
291 this.feedbackSubjectInput
.connect( this, { change
: 'validateFeedbackForm' } );
292 this.feedbackMessageInput
.connect( this, { change
: 'validateFeedbackForm' } );
293 this.feedbackMessageInput
.connect( this, { change
: 'updateSize' } );
294 this.useragentCheckbox
.connect( this, { change
: 'validateFeedbackForm' } );
296 this.$body
.append( this.feedbackPanel
.$element
);
300 * Validate the feedback form
302 mw
.Feedback
.Dialog
.prototype.validateFeedbackForm = function () {
305 !this.useragentMandatory
||
306 this.useragentCheckbox
.isSelected()
309 !!this.feedbackMessageInput
.getValue() ||
310 !!this.feedbackSubjectInput
.getValue()
314 this.actions
.setAbilities( { submit
: isValid
} );
320 mw
.Feedback
.Dialog
.prototype.getBodyHeight = function () {
321 return this.feedbackPanel
.$element
.outerHeight( true );
327 mw
.Feedback
.Dialog
.prototype.getSetupProcess = function ( data
) {
328 return mw
.Feedback
.Dialog
.super.prototype.getSetupProcess
.call( this, data
)
330 var plainMsg
, parsedMsg
,
331 settings
= data
.settings
;
332 data
.contents
= data
.contents
|| {};
334 // Prefill subject/message
335 this.feedbackSubjectInput
.setValue( data
.contents
.subject
);
336 this.feedbackMessageInput
.setValue( data
.contents
.message
);
339 this.api
= settings
.api
;
340 this.setBugReportLink( settings
.bugsTaskSubmissionLink
);
341 this.feedbackPageTitle
= settings
.title
;
342 this.feedbackPageName
= settings
.title
.getNameText();
343 this.feedbackPageUrl
= settings
.title
.getUrl();
345 // Useragent checkbox
346 if ( settings
.useragentCheckbox
.show
) {
347 this.useragentFieldLayout
.setLabel( settings
.useragentCheckbox
.message
);
349 this.useragentMandatory
= settings
.useragentCheckbox
.mandatory
;
350 this.useragentFieldLayout
.toggle( settings
.useragentCheckbox
.show
);
352 // HACK: Setting a link in the messages doesn't work. There is already a report
353 // about this, and the bug report offers a somewhat hacky work around that
354 // includes setting a separate message to be parsed.
355 // We want to make sure the user can configure both the title of the page and
356 // a separate url, so this must be allowed to parse correctly.
357 // See https://phabricator.wikimedia.org/T49395#490610
359 'feedback-dialog-temporary-message':
360 '<a href="' + this.feedbackPageUrl
+ '" target="_blank">' + this.feedbackPageName
+ '</a>'
362 plainMsg
= mw
.message( 'feedback-dialog-temporary-message' ).plain();
363 mw
.messages
.set( { 'feedback-dialog-temporary-message-parsed': plainMsg
} );
364 parsedMsg
= mw
.message( 'feedback-dialog-temporary-message-parsed' );
365 this.feedbackMessageLabel
.setLabel(
368 .append( mw
.message( 'feedback-dialog-intro', parsedMsg
).parse() )
371 this.validateFeedbackForm();
378 mw
.Feedback
.Dialog
.prototype.getReadyProcess = function ( data
) {
379 return mw
.Feedback
.Dialog
.super.prototype.getReadyProcess
.call( this, data
)
381 this.feedbackSubjectInput
.focus();
388 mw
.Feedback
.Dialog
.prototype.getActionProcess = function ( action
) {
389 if ( action
=== 'cancel' ) {
390 return new OO
.ui
.Process( function () {
391 this.close( { action
: action
} );
393 } else if ( action
=== 'external' ) {
394 return new OO
.ui
.Process( function () {
395 // Open in a new window
396 window
.open( this.getBugReportLink(), '_blank' );
400 } else if ( action
=== 'submit' ) {
401 return new OO
.ui
.Process( function () {
403 userAgentMessage
= ':' +
405 mw
.msg( 'feedback-useragent' ) +
407 mw
.html
.escape( navigator
.userAgent
) +
409 subject
= this.feedbackSubjectInput
.getValue(),
410 message
= this.feedbackMessageInput
.getValue();
412 // Add user agent if checkbox is selected
413 if ( this.useragentCheckbox
.isSelected() ) {
414 message
= userAgentMessage
+ message
;
417 // Add signature if needed
418 if ( message
.indexOf( '~~~' ) === -1 ) {
419 message
+= '\n\n~~~~';
422 // Post the message, resolving redirects
425 this.feedbackPageTitle
,
430 .done( function ( result
) {
431 if ( result
.edit
.result
=== 'Success' ) {
432 fb
.status
= 'submitted';
434 fb
.status
= 'error1';
439 .fail( function ( code
, result
) {
440 if ( code
=== 'http' ) {
441 fb
.status
= 'error3';
442 // ajax request failed
443 mw
.log
.warn( 'Feedback report failed with HTTP error: ' + result
.textStatus
);
445 fb
.status
= 'error2';
446 mw
.log
.warn( 'Feedback report failed with API error: ' + code
);
453 // Fallback to parent handler
454 return mw
.Feedback
.Dialog
.super.prototype.getActionProcess
.call( this, action
);
460 mw
.Feedback
.Dialog
.prototype.getTeardownProcess = function ( data
) {
461 return mw
.Feedback
.Dialog
.super.prototype.getTeardownProcess
.call( this, data
)
462 .first( function () {
463 this.emit( 'submit', this.status
, this.feedbackPageName
, this.feedbackPageUrl
);
466 this.feedbackPageTitle
= null;
467 this.feedbackSubjectInput
.setValue( '' );
468 this.feedbackMessageInput
.setValue( '' );
469 this.useragentCheckbox
.setSelected( false );
474 * Set the bug report link
475 * @param {string} link Link to the external bug report form
477 mw
.Feedback
.Dialog
.prototype.setBugReportLink = function ( link
) {
478 this.bugReportLink
= link
;
482 * Get the bug report link
483 * @returns {string} Link to the external bug report form
485 mw
.Feedback
.Dialog
.prototype.getBugReportLink = function () {
486 return this.bugReportLink
;
489 }( mediaWiki
, jQuery
) );