Localisation updates from https://translatewiki.net.
[mediawiki.git] / resources / src / mediawiki.feedback / FeedbackDialog.js
blobf73df43452b30668d9d1a51b28a5b2293680bb7c
1 /**
2  * @class Dialog
3  * @classdesc Feedback dialog for use within the context mw.Feedback. Typically
4  * constructed using {@link mw.Feedback#launch} instead of directly using the constructor.
5  * @memberof mw.Feedback
6  * @extends OO.ui.ProcessDialog
7  *
8  * @constructor
9  * @description Create an instance of `mw.Feedback.Dialog`.
10  * @param {Object} config Configuration object
11  */
12 function FeedbackDialog( config ) {
13         // Parent constructor
14         FeedbackDialog.super.call( this, config );
16         this.status = '';
17         this.feedbackPageTitle = null;
18         // Initialize
19         this.$element.addClass( 'mwFeedback-Dialog' );
22 OO.inheritClass( FeedbackDialog, OO.ui.ProcessDialog );
24 /* Static properties */
25 FeedbackDialog.static.name = 'mwFeedbackDialog';
26 FeedbackDialog.static.title = mw.msg( 'feedback-dialog-title' );
27 FeedbackDialog.static.size = 'medium';
28 FeedbackDialog.static.actions = [
29         {
30                 action: 'submit',
31                 label: mw.msg( 'feedback-submit' ),
32                 flags: [ 'primary', 'progressive' ]
33         },
34         {
35                 action: 'external',
36                 label: mw.msg( 'feedback-external-bug-report-button' ),
37                 flags: 'progressive'
38         },
39         {
40                 action: 'cancel',
41                 label: mw.msg( 'feedback-cancel' ),
42                 flags: 'safe'
43         }
46 /**
47  * Initializes the dialog.
48  *
49  * @ignore
50  * @inheritdoc
51  */
52 FeedbackDialog.prototype.initialize = function () {
53         // Parent method
54         FeedbackDialog.super.prototype.initialize.call( this );
56         this.feedbackPanel = new OO.ui.PanelLayout( {
57                 scrollable: false,
58                 expanded: false,
59                 padded: true
60         } );
62         // Feedback form
63         this.feedbackMessageLabel = new OO.ui.LabelWidget( {
64                 classes: [ 'mw-feedbackDialog-welcome-message' ]
65         } );
66         this.feedbackSubjectInput = new OO.ui.TextInputWidget( {
67                 indicator: 'required'
68         } );
69         this.feedbackMessageInput = new OO.ui.MultilineTextInputWidget( {
70                 autosize: true
71         } );
72         const feedbackSubjectFieldLayout = new OO.ui.FieldLayout( this.feedbackSubjectInput, {
73                 label: mw.msg( 'feedback-subject' )
74         } );
75         const feedbackMessageFieldLayout = new OO.ui.FieldLayout( this.feedbackMessageInput, {
76                 label: mw.msg( 'feedback-message' )
77         } );
78         const feedbackFieldsetLayout = new OO.ui.FieldsetLayout( {
79                 items: [ feedbackSubjectFieldLayout, feedbackMessageFieldLayout ],
80                 classes: [ 'mw-feedbackDialog-feedback-form' ]
81         } );
83         // Useragent terms of use
84         this.useragentCheckbox = new OO.ui.CheckboxInputWidget();
85         this.useragentFieldLayout = new OO.ui.FieldLayout( this.useragentCheckbox, {
86                 classes: [ 'mw-feedbackDialog-feedback-terms' ],
87                 align: 'inline'
88         } );
90         const $termsOfUseLabelText = $( '<p>' ).append( mw.message( 'feedback-termsofuse' ).parseDom() );
91         $termsOfUseLabelText.find( 'a' ).attr( 'target', '_blank' );
92         const termsOfUseLabel = new OO.ui.LabelWidget( {
93                 classes: [ 'mw-feedbackDialog-feedback-termsofuse' ],
94                 label: $termsOfUseLabelText
95         } );
97         this.feedbackPanel.$element.append(
98                 this.feedbackMessageLabel.$element,
99                 feedbackFieldsetLayout.$element,
100                 this.useragentFieldLayout.$element,
101                 termsOfUseLabel.$element
102         );
104         // Events
105         this.feedbackSubjectInput.connect( this, { change: 'validateFeedbackForm' } );
106         this.feedbackMessageInput.connect( this, { change: 'validateFeedbackForm' } );
107         this.feedbackMessageInput.connect( this, { change: 'updateSize' } );
108         this.useragentCheckbox.connect( this, { change: 'validateFeedbackForm' } );
110         this.$body.append( this.feedbackPanel.$element );
114  * Validate the feedback form.
116  * @method validateFeedbackForm
117  * @memberof mw.Feedback.Dialog
118  */
119 FeedbackDialog.prototype.validateFeedbackForm = function () {
120         const isValid = (
121                 (
122                         !this.useragentMandatory ||
123                         this.useragentCheckbox.isSelected()
124                 ) &&
125                 this.feedbackSubjectInput.getValue()
126         );
128         this.actions.setAbilities( { submit: isValid } );
132  * @inheritdoc
133  * @ignore
134  */
135 FeedbackDialog.prototype.getBodyHeight = function () {
136         return this.feedbackPanel.$element.outerHeight( true );
140  * @inheritdoc
141  * @ignore
142  */
143 FeedbackDialog.prototype.getSetupProcess = function ( data ) {
144         return FeedbackDialog.super.prototype.getSetupProcess.call( this, data )
145                 .next( () => {
146                         // Get the URL of the target page, we want to use that in links in the intro
147                         // and in the success dialog
148                         if ( data.foreignApi ) {
149                                 return data.foreignApi.get( {
150                                         action: 'query',
151                                         prop: 'info',
152                                         inprop: 'url',
153                                         formatversion: 2,
154                                         titles: data.settings.title.getPrefixedText()
155                                 } ).then( ( response ) => {
156                                         this.feedbackPageUrl = OO.getProp( response, 'query', 'pages', 0, 'canonicalurl' );
157                                 } );
158                         } else {
159                                 this.feedbackPageUrl = data.settings.title.getUrl();
160                         }
161                 } )
162                 .next( () => {
163                         const settings = data.settings;
164                         data.contents = data.contents || {};
166                         // Prefill subject/message
167                         this.feedbackSubjectInput.setValue( data.contents.subject );
168                         this.feedbackMessageInput.setValue( data.contents.message );
170                         this.status = '';
171                         this.messagePosterPromise = settings.messagePosterPromise;
172                         this.setBugReportLink( settings.bugsTaskSubmissionLink );
173                         this.feedbackPageTitle = settings.title;
174                         this.feedbackPageName = settings.title.getMainText();
176                         // Useragent checkbox
177                         if ( settings.useragentCheckbox.show ) {
178                                 this.useragentFieldLayout.setLabel( settings.useragentCheckbox.message );
179                         }
181                         this.useragentMandatory = settings.useragentCheckbox.mandatory;
182                         this.useragentFieldLayout.toggle( settings.useragentCheckbox.show );
184                         const $link = $( '<a>' )
185                                 .attr( 'href', this.feedbackPageUrl )
186                                 .attr( 'target', '_blank' )
187                                 .text( this.feedbackPageName );
188                         this.feedbackMessageLabel.setLabel(
189                                 mw.message( 'feedback-dialog-intro', $link ).parseDom()
190                         );
192                         this.validateFeedbackForm();
193                 } );
197  * @inheritdoc
198  * @ignore
199  */
200 FeedbackDialog.prototype.getReadyProcess = function ( data ) {
201         return FeedbackDialog.super.prototype.getReadyProcess.call( this, data )
202                 .next( () => {
203                         this.feedbackSubjectInput.focus();
204                 } );
208  * @inheritdoc
209  * @ignore
210  */
211 FeedbackDialog.prototype.getActionProcess = function ( action ) {
212         if ( action === 'cancel' ) {
213                 return new OO.ui.Process( () => {
214                         this.close( { action: action } );
215                 } );
216         } else if ( action === 'external' ) {
217                 return new OO.ui.Process( () => {
218                         // Open in a new window
219                         window.open( this.getBugReportLink(), '_blank' );
220                         // Close the dialog
221                         this.close();
222                 } );
223         } else if ( action === 'submit' ) {
224                 return new OO.ui.Process( () => {
225                         const userAgentMessage = ':' +
226                                         '<small>' +
227                                         mw.msg( 'feedback-useragent' ) +
228                                         ' ' +
229                                         mw.html.escape( navigator.userAgent ) +
230                                         '</small>\n\n',
231                                 subject = this.feedbackSubjectInput.getValue();
232                         let message = this.feedbackMessageInput.getValue();
234                         // Add user agent if checkbox is selected
235                         if ( this.useragentCheckbox.isSelected() ) {
236                                 message = userAgentMessage + message;
237                         }
239                         // Post the message
240                         return this.messagePosterPromise.then( ( poster ) => this.postMessage( poster, subject, message ), () => {
241                                 this.status = 'error4';
242                                 mw.log.warn( 'Feedback report failed because MessagePoster could not be fetched' );
243                         } ).then( () => {
244                                 this.close();
245                         }, () => this.getErrorMessage() );
246                 } );
247         }
248         // Fallback to parent handler
249         return FeedbackDialog.super.prototype.getActionProcess.call( this, action );
253  * Returns an error message for the current status.
255  * @private
257  * @return {OO.ui.Error}
258  */
259 FeedbackDialog.prototype.getErrorMessage = function () {
260         if ( this.$statusFromApi ) {
261                 return new OO.ui.Error( this.$statusFromApi );
262         }
263         // The following messages can be used here:
264         // * feedback-error1
265         // * feedback-error4
266         return new OO.ui.Error( mw.msg( 'feedback-' + this.status ) );
270  * Posts the message
272  * @private
274  * @param {mw.messagePoster.MessagePoster} poster Poster implementation used to leave feedback
275  * @param {string} subject Subject of message
276  * @param {string} message Body of message
277  * @return {jQuery.Promise} Promise representing success of message posting action
278  */
279 FeedbackDialog.prototype.postMessage = function ( poster, subject, message ) {
280         return poster.post(
281                 subject,
282                 message
283         ).then( () => {
284                 this.status = 'submitted';
285         }, ( mainCode, secondaryCode, details ) => {
286                 if ( mainCode === 'api-fail' ) {
287                         if ( secondaryCode === 'http' ) {
288                                 this.status = 'error3';
289                                 // ajax request failed
290                                 mw.log.warn( 'Feedback report failed with HTTP error: ' + details.textStatus );
291                         } else {
292                                 this.status = 'error2';
293                                 mw.log.warn( 'Feedback report failed with API error: ' + secondaryCode );
294                         }
295                         this.$statusFromApi = ( new mw.Api() ).getErrorMessage( details );
296                 } else {
297                         this.status = 'error1';
298                 }
299         } );
303  * @ignore
304  * @inheritdoc
305  */
306 FeedbackDialog.prototype.getTeardownProcess = function ( data ) {
307         return FeedbackDialog.super.prototype.getTeardownProcess.call( this, data )
308                 .first( () => {
309                         this.emit( 'submit', this.status, this.feedbackPageName, this.feedbackPageUrl );
310                         // Cleanup
311                         this.status = '';
312                         this.feedbackPageTitle = null;
313                         this.feedbackSubjectInput.setValue( '' );
314                         this.feedbackMessageInput.setValue( '' );
315                         this.useragentCheckbox.setSelected( false );
316                 } );
320  * Set the bug report link.
322  * @method setBugReportLink
323  * @param {string} link Link to the external bug report form
324  * @memberof mw.Feedback.Dialog
325  */
326 FeedbackDialog.prototype.setBugReportLink = function ( link ) {
327         this.bugReportLink = link;
331  * Get the bug report link.
333  * @method getBugReportLink
334  * @return {string} Link to the external bug report form
335  * @memberof mw.Feedback.Dialog
336  */
337 FeedbackDialog.prototype.getBugReportLink = function () {
338         return this.bugReportLink;
341 module.exports = FeedbackDialog;