mw.Feedback: Remove weird unnecessary switch statements
[lhc/web/wiklou.git] / resources / src / mediawiki / mediawiki.feedback.js
1 /*!
2 * mediawiki.feedback
3 *
4 * @author Ryan Kaldari, 2010
5 * @author Neil Kandalgaonkar, 2010-11
6 * @author Moriel Schottlender, 2015
7 * @since 1.19
8 */
9 ( function ( mw, $ ) {
10 /**
11 * This is a way of getting simple feedback from users. It's useful
12 * for testing new features -- users can give you feedback without
13 * the difficulty of opening a whole new talk page. For this reason,
14 * it also tends to collect a wider range of both positive and negative
15 * comments. However you do need to tend to the feedback page. It will
16 * get long relatively quickly, and you often get multiple messages
17 * reporting the same issue.
18 *
19 * It takes the form of thing on your page which, when clicked, opens a small
20 * dialog box. Submitting that dialog box appends its contents to a
21 * wiki page that you specify, as a new section.
22 *
23 * This feature works with any content model that defines a
24 * `mw.messagePoster.MessagePoster`.
25 *
26 * Minimal usage example:
27 *
28 * var feedback = new mw.Feedback();
29 * $( '#myButton' ).click( function () { feedback.launch(); } );
30 *
31 * You can also launch the feedback form with a prefilled subject and body.
32 * See the docs for the #launch() method.
33 *
34 * @class
35 * @constructor
36 * @param {Object} [config] Configuration object
37 * @cfg {mw.Title} [title="Feedback"] The title of the page where you collect
38 * feedback.
39 * @cfg {string} [apiUrl] api.php URL if the feedback page is on another wiki
40 * @cfg {string} [dialogTitleMessageKey="feedback-dialog-title"] Message key for the
41 * title of the dialog box
42 * @cfg {mw.Uri|string} [bugsLink="//phabricator.wikimedia.org/maniphest/task/edit/form/1/"] URL where
43 * bugs can be posted
44 * @cfg {mw.Uri|string} [bugsListLink="//phabricator.wikimedia.org/maniphest/query/advanced"] URL
45 * where bugs can be listed
46 * @cfg {boolean} [showUseragentCheckbox=false] Show a Useragent agreement checkbox as part of the form.
47 * @cfg {boolean} [useragentCheckboxMandatory=false] Make the Useragent checkbox mandatory.
48 * @cfg {string|jQuery} [useragentCheckboxMessage] Supply a custom message for the useragent checkbox.
49 * defaults to the message 'feedback-terms'.
50 */
51 mw.Feedback = function MwFeedback( config ) {
52 config = config || {};
53
54 this.dialogTitleMessageKey = config.dialogTitleMessageKey || 'feedback-dialog-title';
55
56 // Feedback page title
57 this.feedbackPageTitle = config.title || new mw.Title( 'Feedback' );
58
59 this.messagePosterPromise = mw.messagePoster.factory.create( this.feedbackPageTitle, config.apiUrl );
60
61 // Links
62 this.bugsTaskSubmissionLink = config.bugsLink || '//phabricator.wikimedia.org/maniphest/task/edit/form/1/';
63 this.bugsTaskListLink = config.bugsListLink || '//phabricator.wikimedia.org/maniphest/query/advanced';
64
65 // Terms of use
66 this.useragentCheckboxShow = !!config.showUseragentCheckbox;
67 this.useragentCheckboxMandatory = !!config.useragentCheckboxMandatory;
68 this.useragentCheckboxMessage = config.useragentCheckboxMessage ||
69 $( '<p>' ).append( mw.msg( 'feedback-terms' ) );
70
71 // Message dialog
72 this.thankYouDialog = new OO.ui.MessageDialog();
73 };
74
75 /* Initialize */
76 OO.initClass( mw.Feedback );
77
78 /* Static Properties */
79 mw.Feedback.static.windowManager = null;
80 mw.Feedback.static.dialog = null;
81
82 /* Methods */
83
84 /**
85 * Respond to dialog submit event. If the information was
86 * submitted successfully, open a MessageDialog to thank the user.
87 *
88 * @param {string} [status] A status of the end of operation
89 * of the main feedback dialog. Empty if the dialog was
90 * dismissed with no action or the user followed the button
91 * to the external task reporting site.
92 */
93 mw.Feedback.prototype.onDialogSubmit = function ( status ) {
94 var dialogConfig;
95
96 if ( status !== 'submitted' ) {
97 return;
98 }
99
100 dialogConfig = {
101 title: mw.msg( 'feedback-thanks-title' ),
102 message: $( '<span>' ).msg(
103 'feedback-thanks',
104 this.feedbackPageTitle.getNameText(),
105 $( '<a>' ).attr( {
106 target: '_blank',
107 href: this.feedbackPageTitle.getUrl()
108 } )
109 ),
110 actions: [
111 {
112 action: 'accept',
113 label: mw.msg( 'feedback-close' ),
114 flags: 'primary'
115 }
116 ]
117 };
118
119 // Show the message dialog
120 this.constructor.static.windowManager.openWindow(
121 this.thankYouDialog,
122 dialogConfig
123 );
124 };
125
126 /**
127 * Modify the display form, and then open it, focusing interface on the subject.
128 *
129 * @param {Object} [contents] Prefilled contents for the feedback form.
130 * @param {string} [contents.subject] The subject of the feedback, as plaintext
131 * @param {string} [contents.message] The content of the feedback, as wikitext
132 */
133 mw.Feedback.prototype.launch = function ( contents ) {
134 // Dialog
135 if ( !this.constructor.static.dialog ) {
136 this.constructor.static.dialog = new mw.Feedback.Dialog();
137 this.constructor.static.dialog.connect( this, { submit: 'onDialogSubmit' } );
138 }
139 if ( !this.constructor.static.windowManager ) {
140 this.constructor.static.windowManager = new OO.ui.WindowManager();
141 this.constructor.static.windowManager.addWindows( [
142 this.constructor.static.dialog,
143 this.thankYouDialog
144 ] );
145 $( 'body' )
146 .append( this.constructor.static.windowManager.$element );
147 }
148 // Open the dialog
149 this.constructor.static.windowManager.openWindow(
150 this.constructor.static.dialog,
151 {
152 title: mw.msg( this.dialogTitleMessageKey ),
153 settings: {
154 messagePosterPromise: this.messagePosterPromise,
155 title: this.feedbackPageTitle,
156 dialogTitleMessageKey: this.dialogTitleMessageKey,
157 bugsTaskSubmissionLink: this.bugsTaskSubmissionLink,
158 bugsTaskListLink: this.bugsTaskListLink,
159 useragentCheckbox: {
160 show: this.useragentCheckboxShow,
161 mandatory: this.useragentCheckboxMandatory,
162 message: this.useragentCheckboxMessage
163 }
164 },
165 contents: contents
166 }
167 );
168 };
169
170 /**
171 * mw.Feedback Dialog
172 *
173 * @class
174 * @extends OO.ui.ProcessDialog
175 *
176 * @constructor
177 * @param {Object} config Configuration object
178 */
179 mw.Feedback.Dialog = function mwFeedbackDialog( config ) {
180 // Parent constructor
181 mw.Feedback.Dialog.parent.call( this, config );
182
183 this.status = '';
184 this.feedbackPageTitle = null;
185 // Initialize
186 this.$element.addClass( 'mwFeedback-Dialog' );
187 };
188
189 OO.inheritClass( mw.Feedback.Dialog, OO.ui.ProcessDialog );
190
191 /* Static properties */
192 mw.Feedback.Dialog.static.name = 'mwFeedbackDialog';
193 mw.Feedback.Dialog.static.title = mw.msg( 'feedback-dialog-title' );
194 mw.Feedback.Dialog.static.size = 'medium';
195 mw.Feedback.Dialog.static.actions = [
196 {
197 action: 'submit',
198 label: mw.msg( 'feedback-submit' ),
199 flags: [ 'primary', 'progressive' ]
200 },
201 {
202 action: 'external',
203 label: mw.msg( 'feedback-external-bug-report-button' ),
204 flags: 'progressive'
205 },
206 {
207 action: 'cancel',
208 label: mw.msg( 'feedback-cancel' ),
209 flags: 'safe'
210 }
211 ];
212
213 /**
214 * @inheritdoc
215 */
216 mw.Feedback.Dialog.prototype.initialize = function () {
217 var feedbackSubjectFieldLayout, feedbackMessageFieldLayout,
218 feedbackFieldsetLayout, termsOfUseLabel;
219
220 // Parent method
221 mw.Feedback.Dialog.parent.prototype.initialize.call( this );
222
223 this.feedbackPanel = new OO.ui.PanelLayout( {
224 scrollable: false,
225 expanded: false,
226 padded: true
227 } );
228
229 this.$spinner = $( '<div>' )
230 .addClass( 'feedback-spinner' );
231
232 // Feedback form
233 this.feedbackMessageLabel = new OO.ui.LabelWidget( {
234 classes: [ 'mw-feedbackDialog-welcome-message' ]
235 } );
236 this.feedbackSubjectInput = new OO.ui.TextInputWidget( {
237 indicator: 'required'
238 } );
239 this.feedbackMessageInput = new OO.ui.MultilineTextInputWidget( {
240 autosize: true
241 } );
242 feedbackSubjectFieldLayout = new OO.ui.FieldLayout( this.feedbackSubjectInput, {
243 label: mw.msg( 'feedback-subject' )
244 } );
245 feedbackMessageFieldLayout = new OO.ui.FieldLayout( this.feedbackMessageInput, {
246 label: mw.msg( 'feedback-message' )
247 } );
248 feedbackFieldsetLayout = new OO.ui.FieldsetLayout( {
249 items: [ feedbackSubjectFieldLayout, feedbackMessageFieldLayout ],
250 classes: [ 'mw-feedbackDialog-feedback-form' ]
251 } );
252
253 // Useragent terms of use
254 this.useragentCheckbox = new OO.ui.CheckboxInputWidget();
255 this.useragentFieldLayout = new OO.ui.FieldLayout( this.useragentCheckbox, {
256 classes: [ 'mw-feedbackDialog-feedback-terms' ],
257 align: 'inline'
258 } );
259
260 termsOfUseLabel = new OO.ui.LabelWidget( {
261 classes: [ 'mw-feedbackDialog-feedback-termsofuse' ],
262 label: $( '<p>' ).append( mw.msg( 'feedback-termsofuse' ) )
263 } );
264
265 this.feedbackPanel.$element.append(
266 this.feedbackMessageLabel.$element,
267 feedbackFieldsetLayout.$element,
268 this.useragentFieldLayout.$element,
269 termsOfUseLabel.$element
270 );
271
272 // Events
273 this.feedbackSubjectInput.connect( this, { change: 'validateFeedbackForm' } );
274 this.feedbackMessageInput.connect( this, { change: 'validateFeedbackForm' } );
275 this.feedbackMessageInput.connect( this, { change: 'updateSize' } );
276 this.useragentCheckbox.connect( this, { change: 'validateFeedbackForm' } );
277
278 this.$body.append( this.feedbackPanel.$element );
279 };
280
281 /**
282 * Validate the feedback form
283 */
284 mw.Feedback.Dialog.prototype.validateFeedbackForm = function () {
285 var isValid = (
286 (
287 !this.useragentMandatory ||
288 this.useragentCheckbox.isSelected()
289 ) &&
290 this.feedbackSubjectInput.getValue()
291 );
292
293 this.actions.setAbilities( { submit: isValid } );
294 };
295
296 /**
297 * @inheritdoc
298 */
299 mw.Feedback.Dialog.prototype.getBodyHeight = function () {
300 return this.feedbackPanel.$element.outerHeight( true );
301 };
302
303 /**
304 * @inheritdoc
305 */
306 mw.Feedback.Dialog.prototype.getSetupProcess = function ( data ) {
307 return mw.Feedback.Dialog.parent.prototype.getSetupProcess.call( this, data )
308 .next( function () {
309 var plainMsg, parsedMsg,
310 settings = data.settings;
311 data.contents = data.contents || {};
312
313 // Prefill subject/message
314 this.feedbackSubjectInput.setValue( data.contents.subject );
315 this.feedbackMessageInput.setValue( data.contents.message );
316
317 this.status = '';
318 this.messagePosterPromise = settings.messagePosterPromise;
319 this.setBugReportLink( settings.bugsTaskSubmissionLink );
320 this.feedbackPageTitle = settings.title;
321 this.feedbackPageName = settings.title.getNameText();
322 this.feedbackPageUrl = settings.title.getUrl();
323
324 // Useragent checkbox
325 if ( settings.useragentCheckbox.show ) {
326 this.useragentFieldLayout.setLabel( settings.useragentCheckbox.message );
327 }
328
329 this.useragentMandatory = settings.useragentCheckbox.mandatory;
330 this.useragentFieldLayout.toggle( settings.useragentCheckbox.show );
331
332 // HACK: Setting a link in the messages doesn't work. There is already a report
333 // about this, and the bug report offers a somewhat hacky work around that
334 // includes setting a separate message to be parsed.
335 // We want to make sure the user can configure both the title of the page and
336 // a separate url, so this must be allowed to parse correctly.
337 // See https://phabricator.wikimedia.org/T49395#490610
338 mw.messages.set( {
339 'feedback-dialog-temporary-message':
340 '<a href="' + this.feedbackPageUrl + '" target="_blank">' + this.feedbackPageName + '</a>'
341 } );
342 plainMsg = mw.message( 'feedback-dialog-temporary-message' ).plain();
343 mw.messages.set( { 'feedback-dialog-temporary-message-parsed': plainMsg } );
344 parsedMsg = mw.message( 'feedback-dialog-temporary-message-parsed' );
345 this.feedbackMessageLabel.setLabel(
346 // Double-parse
347 $( '<span>' )
348 .append( mw.message( 'feedback-dialog-intro', parsedMsg ).parse() )
349 );
350
351 this.validateFeedbackForm();
352 }, this );
353 };
354
355 /**
356 * @inheritdoc
357 */
358 mw.Feedback.Dialog.prototype.getReadyProcess = function ( data ) {
359 return mw.Feedback.Dialog.parent.prototype.getReadyProcess.call( this, data )
360 .next( function () {
361 this.feedbackSubjectInput.focus();
362 }, this );
363 };
364
365 /**
366 * @inheritdoc
367 */
368 mw.Feedback.Dialog.prototype.getActionProcess = function ( action ) {
369 if ( action === 'cancel' ) {
370 return new OO.ui.Process( function () {
371 this.close( { action: action } );
372 }, this );
373 } else if ( action === 'external' ) {
374 return new OO.ui.Process( function () {
375 // Open in a new window
376 window.open( this.getBugReportLink(), '_blank' );
377 // Close the dialog
378 this.close();
379 }, this );
380 } else if ( action === 'submit' ) {
381 return new OO.ui.Process( function () {
382 var fb = this,
383 userAgentMessage = ':' +
384 '<small>' +
385 mw.msg( 'feedback-useragent' ) +
386 ' ' +
387 mw.html.escape( navigator.userAgent ) +
388 '</small>\n\n',
389 subject = this.feedbackSubjectInput.getValue(),
390 message = this.feedbackMessageInput.getValue();
391
392 // Add user agent if checkbox is selected
393 if ( this.useragentCheckbox.isSelected() ) {
394 message = userAgentMessage + message;
395 }
396
397 // Post the message
398 return this.messagePosterPromise.then( function ( poster ) {
399 return fb.postMessage( poster, subject, message );
400 }, function () {
401 fb.status = 'error4';
402 mw.log.warn( 'Feedback report failed because MessagePoster could not be fetched' );
403 } ).then( function () {
404 fb.close();
405 }, function () {
406 return fb.getErrorMessage();
407 } );
408 }, this );
409 }
410 // Fallback to parent handler
411 return mw.Feedback.Dialog.parent.prototype.getActionProcess.call( this, action );
412 };
413
414 /**
415 * Returns an error message for the current status.
416 *
417 * @private
418 *
419 * @return {OO.ui.Error}
420 */
421 mw.Feedback.Dialog.prototype.getErrorMessage = function () {
422 // Messages: feedback-error1, feedback-error2, feedback-error3, feedback-error4
423 return new OO.ui.Error( mw.msg( 'feedback-' + this.status ) );
424 };
425
426 /**
427 * Posts the message
428 *
429 * @private
430 *
431 * @param {mw.messagePoster.MessagePoster} poster Poster implementation used to leave feedback
432 * @param {string} subject Subject of message
433 * @param {string} message Body of message
434 * @return {jQuery.Promise} Promise representing success of message posting action
435 */
436 mw.Feedback.Dialog.prototype.postMessage = function ( poster, subject, message ) {
437 var fb = this;
438
439 return poster.post(
440 subject,
441 message
442 ).then( function () {
443 fb.status = 'submitted';
444 }, function ( mainCode, secondaryCode, details ) {
445 if ( mainCode === 'api-fail' ) {
446 if ( secondaryCode === 'http' ) {
447 fb.status = 'error3';
448 // ajax request failed
449 mw.log.warn( 'Feedback report failed with HTTP error: ' + details.textStatus );
450 } else {
451 fb.status = 'error2';
452 mw.log.warn( 'Feedback report failed with API error: ' + secondaryCode );
453 }
454 } else {
455 fb.status = 'error1';
456 }
457 } );
458 };
459
460 /**
461 * @inheritdoc
462 */
463 mw.Feedback.Dialog.prototype.getTeardownProcess = function ( data ) {
464 return mw.Feedback.Dialog.parent.prototype.getTeardownProcess.call( this, data )
465 .first( function () {
466 this.emit( 'submit', this.status, this.feedbackPageName, this.feedbackPageUrl );
467 // Cleanup
468 this.status = '';
469 this.feedbackPageTitle = null;
470 this.feedbackSubjectInput.setValue( '' );
471 this.feedbackMessageInput.setValue( '' );
472 this.useragentCheckbox.setSelected( false );
473 }, this );
474 };
475
476 /**
477 * Set the bug report link
478 *
479 * @param {string} link Link to the external bug report form
480 */
481 mw.Feedback.Dialog.prototype.setBugReportLink = function ( link ) {
482 this.bugReportLink = link;
483 };
484
485 /**
486 * Get the bug report link
487 *
488 * @return {string} Link to the external bug report form
489 */
490 mw.Feedback.Dialog.prototype.getBugReportLink = function () {
491 return this.bugReportLink;
492 };
493
494 }( mediaWiki, jQuery ) );