Mattflaschen has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/200801

Change subject: Add pluggable talk page poster and use it for mediawiki.feedback
......................................................................

Add pluggable talk page poster and use it for mediawiki.feedback

The core implementation will only support wikitext.
Flow will add its own implementation, and it can be used for any talk
page system identifiable by content model.

Bug: T91805
Change-Id: Ic69acafb24aa737536fe3a074e1958690732f0a7
---
M jsduck.json
M languages/i18n/en.json
M languages/i18n/qqq.json
M maintenance/jsduck/categories.json
M resources/Resources.php
A resources/src/mediawiki.messagePoster/mediawiki.messagePoster.MessagePoster.js
A 
resources/src/mediawiki.messagePoster/mediawiki.messagePoster.WikitextMessagePoster.js
A 
resources/src/mediawiki.messagePoster/mediawiki.messagePoster.messagePosterFactory.js
M resources/src/mediawiki/mediawiki.feedback.js
M tests/qunit/QUnitTestResources.php
A tests/qunit/suites/resources/mediawiki/mediawiki.messagePosterFactory.test.js
11 files changed, 313 insertions(+), 37 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/core 
refs/changes/01/200801/1

diff --git a/jsduck.json b/jsduck.json
index 53c6913..2b0c8fb 100644
--- a/jsduck.json
+++ b/jsduck.json
@@ -15,6 +15,7 @@
                "resources/src/mediawiki.action",
                "resources/src/mediawiki.api",
                "resources/src/mediawiki.language",
+               "resources/src/mediawiki.messagePoster",
                "resources/src/mediawiki.page",
                "resources/src/mediawiki.special",
                "resources/src/mediawiki.toolbar",
diff --git a/languages/i18n/en.json b/languages/i18n/en.json
index bf65202..67629de 100644
--- a/languages/i18n/en.json
+++ b/languages/i18n/en.json
@@ -3538,6 +3538,7 @@
        "feedback-error1": "Error: Unrecognized result from API",
        "feedback-error2": "Error: Edit failed",
        "feedback-error3": "Error: No response from API",
+       "feedback-error4": "Error: Unable to create MessagePoster for given 
feedback title",
        "feedback-message": "Message:",
        "feedback-subject": "Subject:",
        "feedback-submit": "Submit",
diff --git a/languages/i18n/qqq.json b/languages/i18n/qqq.json
index f573d45..cc85d4f 100644
--- a/languages/i18n/qqq.json
+++ b/languages/i18n/qqq.json
@@ -3703,6 +3703,7 @@
        "feedback-error1": "Error message, appears when an unknown error occurs 
submitting feedback",
        "feedback-error2": "Error message, appears when we could not add 
feedback",
        "feedback-error3": "Error message, appears when we lose our connection 
to the wiki",
+       "feedback-error4": "Error message, appears when mediawiki.feedback or 
one of its dependencies is misconfigured or there is a problem fetching one of 
the modules",
        "feedback-message": "Label for a textarea; signature refers to a 
Wikitext signature.\n{{Identical|Message}}",
        "feedback-subject": "Label for a text input\n{{Identical|Subject}}",
        "feedback-submit": "Button label\n{{Identical|Submit}}",
diff --git a/maintenance/jsduck/categories.json 
b/maintenance/jsduck/categories.json
index c0d0499..d15de64 100644
--- a/maintenance/jsduck/categories.json
+++ b/maintenance/jsduck/categories.json
@@ -22,6 +22,9 @@
                                "classes": [
                                        "mw.Title",
                                        "mw.Uri",
+                                       "mw.messagePoster.factory",
+                                       "mw.messagePoster.MessagePoster",
+                                       
"mw.messagePoster.WikitextMessagePoster",
                                        "mw.notification",
                                        "mw.Notification_",
                                        "mw.user",
diff --git a/resources/Resources.php b/resources/Resources.php
index 4464e4b..498b6df 100644
--- a/resources/Resources.php
+++ b/resources/Resources.php
@@ -875,7 +875,7 @@
                'scripts' => 'resources/src/mediawiki/mediawiki.feedback.js',
                'styles' => 'resources/src/mediawiki/mediawiki.feedback.css',
                'dependencies' => array(
-                       'mediawiki.api.edit',
+                       'mediawiki.messagePoster',
                        'mediawiki.Title',
                        'oojs-ui',
                ),
@@ -894,6 +894,7 @@
                        'feedback-error1',
                        'feedback-error2',
                        'feedback-error3',
+                       'feedback-error4',
                        'feedback-message',
                        'feedback-subject',
                        'feedback-submit',
@@ -953,6 +954,26 @@
                ),
                'targets' => array( 'desktop', 'mobile' ),
        ),
+       'mediawiki.messagePoster' => array(
+               'scripts' => array(
+                       
'resources/src/mediawiki.messagePoster/mediawiki.messagePoster.messagePosterFactory.js',
+                       
'resources/src/mediawiki.messagePoster/mediawiki.messagePoster.MessagePoster.js',
+               ),
+               'dependencies' => array(
+                       'oojs',
+                       'mediawiki.api',
+               ),
+               'targets' => array( 'desktop', 'mobile' ),
+       ),
+       'mediawiki.messagePoster.wikitext' => array(
+               'scripts' => array(
+                       
'resources/src/mediawiki.messagePoster/mediawiki.messagePoster.WikitextMessagePoster.js',
+               ),
+               'dependencies' => array(
+                       'mediawiki.api.edit',
+               ),
+               'targets' => array( 'desktop', 'mobile' ),
+       ),
        'mediawiki.notification' => array(
                'styles' => array(
                        'resources/src/mediawiki/mediawiki.notification.css',
diff --git 
a/resources/src/mediawiki.messagePoster/mediawiki.messagePoster.MessagePoster.js
 
b/resources/src/mediawiki.messagePoster/mediawiki.messagePoster.MessagePoster.js
new file mode 100644
index 0000000..7489f1c
--- /dev/null
+++ 
b/resources/src/mediawiki.messagePoster/mediawiki.messagePoster.MessagePoster.js
@@ -0,0 +1,35 @@
+( function ( mw ) {
+       /**
+        * This is the abstract base class for MessagePoster implementations.
+        *
+        * @abstract
+        *
+        * @param {mw.Title} title Title to post to
+        */
+       mw.messagePoster.MessagePoster = function MwMessagePoster( title ) {};
+
+       OO.initClass( mw.messagePoster.MessagePoster );
+
+       /**
+        * Posts a message (with subject and body) to a talk page.
+        *
+        * @param {string} subject Subject/topic title; plaintext only (no 
wikitext or HTML)
+        * @param {string} body Body, as wikitext.  Signature code will 
automatically be added
+        *   by MessagePosters that require one, unless the message already 
contains the string
+        *   ~~~.
+        * @return {jQuery.Promise} Promise completing when the post succeeds 
or fails.
+        * @return {Function} return.done
+        * @return {Function} return.fail
+        * @return {string} return.fail.primaryError Primary error code.  For a 
mw.Api failure,
+        *   this, should be 'api-fail'.
+        * @return {string} return.fail.secondaryError Secondary error code.  
For a mw.Api failure,
+        *   this, should be mw.Api's code, e.g. 'http', 'ok-but-empty', or the 
error passed through
+        *   from the server.
+        * @return {Mixed} return.fail.details Further details about the error
+        *
+        * @localdoc
+        * The base class currently does nothing, but could be used for shared 
analytics or
+        * something.
+        */
+       mw.messagePoster.MessagePoster.prototype.post = function ( subject, 
body ) {};
+}( mediaWiki ) );
diff --git 
a/resources/src/mediawiki.messagePoster/mediawiki.messagePoster.WikitextMessagePoster.js
 
b/resources/src/mediawiki.messagePoster/mediawiki.messagePoster.WikitextMessagePoster.js
new file mode 100644
index 0000000..0bcb2dc
--- /dev/null
+++ 
b/resources/src/mediawiki.messagePoster/mediawiki.messagePoster.WikitextMessagePoster.js
@@ -0,0 +1,56 @@
+( function ( mw, $ ) {
+       /**
+        * This is an implementation of MessagePoster for wikitext talk pages.
+        *
+        * @abstract
+        * @class
+        *
+        * @extends mw.messagePoster.MessagePoster
+        *
+        * @param {mw.Title} title Wikitext page in a talk namespace, to post to
+        */
+       mw.messagePoster.WikitextMessagePoster = function 
MwWikitextMessagePoster( title ) {
+               this.api = new mw.Api();
+               this.title = title;
+       };
+
+       OO.inheritClass(
+               mw.messagePoster.WikitextMessagePoster,
+               mw.messagePoster.MessagePoster
+       );
+
+       /**
+        * @inheritdoc
+        */
+       mw.messagePoster.WikitextMessagePoster.prototype.post = function ( 
subject, body ) {
+               var dfd = $.Deferred(), promise = dfd.promise();
+
+               
mw.messagePoster.WikitextMessagePoster.super.prototype.post.call( this, 
subject, body );
+
+               // Add signature if needed
+               if ( body.indexOf( '~~~' ) === -1 ) {
+                       body += '\n\n~~~~';
+               }
+
+               this.api.newSection(
+                       this.title,
+                       subject,
+                       body,
+                       { redirect: true }
+               ).done( function ( resp, jqXHR ) {
+                       if ( resp.edit.result === 'Success' ) {
+                               dfd.resolve( resp, jqXHR );
+                       } else {
+                               // mediawiki.api.js checks for resp.error.  Are 
there actually cases where the
+                               // request fails, but it's not caught there?
+                               dfd.reject( 'api-unexpected' );
+                       }
+               } ).fail( function ( code, details ) {
+                       dfd.reject( 'api-fail', code, details );
+               } );
+
+               return promise;
+       };
+
+       mw.messagePoster.factory.register( 'wikitext', 
mw.messagePoster.WikitextMessagePoster );
+}( mediaWiki, jQuery ) );
diff --git 
a/resources/src/mediawiki.messagePoster/mediawiki.messagePoster.messagePosterFactory.js
 
b/resources/src/mediawiki.messagePoster/mediawiki.messagePoster.messagePosterFactory.js
new file mode 100644
index 0000000..a3b7ad4
--- /dev/null
+++ 
b/resources/src/mediawiki.messagePoster/mediawiki.messagePoster.messagePosterFactory.js
@@ -0,0 +1,116 @@
+( function ( mw, $ ) {
+       /**
+        * This is a factory for MessagePoster objects, which allows a 
pluggable to way to script leaving a
+        * talk page message.
+        *
+        * @class mw.messagePoster.factory
+        * @singleton
+        */
+       function MwMessagePosterFactory() {
+               this.api = new mw.Api();
+               this.contentModelToClass = {};
+       }
+
+       OO.initClass( MwMessagePosterFactory );
+
+       // Note: This registration scheme is currently not compatible with LQT, 
since that doesn't
+       // have its own content model, just islqttalkpage.  LQT pages will be 
passed to the wikitext
+       // MessagePoster.
+       /**
+        * Registers a MessagePoster subclass for a given content model.
+        *
+        * @param {string} contentModel Content model of pages this 
MessagePoster can post to
+        * @param {Function} messagePosterConstructor Constructor for 
MessagePoster
+        */
+       MwMessagePosterFactory.prototype.register = function ( contentModel, 
messagePosterConstructor ) {
+               if ( this.contentModelToClass[contentModel] !== undefined ) {
+                       throw new Error( 'The content model \'' + contentModel 
+ '\' is already registered.' );
+               }
+
+               this.contentModelToClass[contentModel] = 
messagePosterConstructor;
+       };
+
+       /**
+        * Unregisters a given content model
+        * This is exposed for testing and should not normally be needed.
+        *
+        * @param {string} contentModel Content model to unregister
+        */
+       MwMessagePosterFactory.prototype.unregister = function ( contentModel ) 
{
+               delete this.contentModelToClass[contentModel];
+       };
+
+       /**
+        * Creates a MessagePoster, given a title.  A promise for this is 
returned.
+        * This works by determining the content model, then loading the 
corresponding
+        * module (which will register the MessagePoster class), and finally 
constructing it.
+        *
+        * This does not require the message and should be called as soon as 
possible, so it does the
+        * API and ResourceLoader requests in the background.
+        *
+        * @param {mw.Title} title Title that will be posted to
+        * @return {jQuery.Promise} Promise for the MessagePoster
+        * @return {Function} return.done Called if MessagePoster is retrieved
+        * @return {mw.messagePoster.MessagePoster} return.done.poster 
MessagePoster
+        * @return {Function} return.fail Called if MessagePoster could not be 
constructed
+        * @return {string} return.fail.errorCode String error code
+        */
+       MwMessagePosterFactory.prototype.create = function ( title ) {
+               var pageId, page, contentModel, moduleName, dfd = $.Deferred(),
+                       promise = dfd.promise(), factory = this;
+
+               this.api.get( {
+                       action: 'query',
+                       prop: 'info',
+                       indexpageids: 1,
+                       titles: title.getPrefixedDb()
+               } ).done( function ( result ) {
+                       var messagePoster;
+
+                       if ( result &&
+                            result.query &&
+                            result.query.pageids &&
+                            result.query.pageids.length > 0 ) {
+
+                               pageId = result.query.pageids[0];
+                               page = result.query.pages[pageId];
+
+                               contentModel = page.contentmodel;
+                               moduleName = 'mediawiki.messagePoster.' + 
contentModel;
+                               mw.loader.using( moduleName ).done( function () 
{
+                                       messagePoster = 
factory.createForContentModel(
+                                               contentModel,
+                                               title
+                                       );
+                                       dfd.resolve( messagePoster );
+                               } ).fail( function () {
+                                       dfd.reject( 'failed-to-load-module', 
'Failed to load the \'' + moduleName + '\' module' );
+                               } );
+                       } else {
+                               dfd.reject( 'unexpected-response', 'Unexpected 
API response' );
+                       }
+               } ).fail( function ( errorCode, details ) {
+                       dfd.reject( 'mw.Api content model query: ' + errorCode, 
details );
+               } );
+
+               return promise;
+       };
+
+       /**
+        * Creates a MessagePoster instance, given a title and content model
+        *
+        * @param {string} contentModel Content model of title
+        * @param {mw.Title} title Title being posted to
+        *
+        * @return
+        *
+        * @private
+        */
+       MwMessagePosterFactory.prototype.createForContentModel = function ( 
contentModel, title ) {
+               return new this.contentModelToClass[contentModel]( title );
+       };
+
+       mw.messagePoster = {
+               factory: new MwMessagePosterFactory()
+       };
+}( mediaWiki, jQuery ) );
diff --git a/resources/src/mediawiki/mediawiki.feedback.js 
b/resources/src/mediawiki/mediawiki.feedback.js
index 9a671c0..c090a45 100644
--- a/resources/src/mediawiki/mediawiki.feedback.js
+++ b/resources/src/mediawiki/mediawiki.feedback.js
@@ -36,7 +36,6 @@
         * @class
         * @constructor
         * @param {Object} [config] Configuration object
-        * @cfg {mw.Api} [api] if omitted, will just create a standard API
         * @cfg {mw.Title} [title="Feedback"] The title of the page where you 
collect
         *  feedback.
         * @cfg {string} [dialogTitleMessageKey="feedback-dialog-title"] 
Message key for the
@@ -53,11 +52,12 @@
        mw.Feedback = function MwFeedback( config ) {
                config = config || {};
 
-               this.api = config.api || new mw.Api();
                this.dialogTitleMessageKey = config.dialogTitleMessageKey || 
'feedback-dialog-title';
 
                // Feedback page title
                this.feedbackPageTitle = config.title || new mw.Title( 
'Feedback' );
+
+               this.messagePosterPromise = mw.messagePoster.factory.create( 
this.feedbackPageTitle );
 
                // Links
                this.bugsTaskSubmissionLink = config.bugsLink || 
'//phabricator.wikimedia.org/maniphest/task/create/';
@@ -120,6 +120,7 @@
                        case 'error1':
                        case 'error2':
                        case 'error3':
+                       case 'error4':
                                dialogConfig = {
                                        title: mw.msg( 'feedback-error-title' ),
                                        message: mw.msg( 'feedback-' + status ),
@@ -147,8 +148,8 @@
         * Modify the display form, and then open it, focusing interface on the 
subject.
         *
         * @param {Object} [contents] Prefilled contents for the feedback form.
-        * @param {string} [contents.subject] The subject of the feedback
-        * @param {string} [contents.message] The content of the feedback
+        * @param {string} [contents.subject] The subject of the feedback, as 
plaintext
+        * @param {string} [contents.message] The content of the feedback, as 
wikitext
         */
        mw.Feedback.prototype.launch = function ( contents ) {
                // Dialog
@@ -171,7 +172,7 @@
                        {
                                title: mw.msg( this.dialogTitleMessageKey ),
                                settings: {
-                                       api: this.api,
+                                       messagePosterPromise: 
this.messagePosterPromise,
                                        title: this.feedbackPageTitle,
                                        dialogTitleMessageKey: 
this.dialogTitleMessageKey,
                                        bugsTaskSubmissionLink: 
this.bugsTaskSubmissionLink,
@@ -339,7 +340,7 @@
                                this.feedbackMessageInput.setValue( 
data.contents.message );
 
                                this.status = '';
-                               this.api = settings.api;
+                               this.messagePosterPromise = 
settings.messagePosterPromise;
                                this.setBugReportLink( 
settings.bugsTaskSubmissionLink );
                                this.feedbackPageTitle = settings.title;
                                this.feedbackPageName = 
settings.title.getNameText();
@@ -418,37 +419,13 @@
                                        message = userAgentMessage + message;
                                }
 
-                               // Add signature if needed
-                               if ( message.indexOf( '~~~' ) === -1 ) {
-                                       message += '\n\n~~~~';
-                               }
-
-                               // Post the message, resolving redirects
+                               // Post the message
                                this.pushPending();
-                               this.api.newSection(
-                                       this.feedbackPageTitle,
-                                       subject,
-                                       message,
-                                       { redirect: true }
-                               )
-                               .done( function ( result ) {
-                                       if ( result.edit.result === 'Success' ) 
{
-                                               fb.status = 'submitted';
-                                       } else {
-                                               fb.status = 'error1';
-                                       }
-                                       fb.popPending();
-                                       fb.close();
-                               } )
-                               .fail( function ( code, result ) {
-                                       if ( code === 'http' ) {
-                                               fb.status = 'error3';
-                                               // ajax request failed
-                                               mw.log.warn( 'Feedback report 
failed with HTTP error: ' +  result.textStatus );
-                                       } else {
-                                               fb.status = 'error2';
-                                               mw.log.warn( 'Feedback report 
failed with API error: ' +  code );
-                                       }
+                               this.messagePosterPromise.done( function ( 
poster ) {
+                                       fb.postMessage( poster, subject, 
message );
+                               } ).fail( function () {
+                                       fb.status = 'error4';
+                                       mw.log.warn( 'Feedback report failed 
because MessagePoster could not be fetched' );
                                        fb.popPending();
                                        fb.close();
                                } );
@@ -459,6 +436,42 @@
        };
 
        /**
+        * Posts the message, then pops the pending state
+        *
+        * @private
+        *
+        * @param {mw.messagePoster.MessagePoster} poster Poster implementation 
used to leave feedback
+        * @param {string} subject Subject of message
+        * @param {string} message Body of message
+        */
+       mw.Feedback.Dialog.prototype.postMessage = function ( poster, subject, 
message ) {
+               var fb = this;
+
+               poster.post(
+                       subject,
+                       message
+               ).done( function () {
+                       fb.status = 'submitted';
+               } ).fail( function ( mainCode, secondaryCode, details ) {
+                       if ( mainCode === 'api-fail' ) {
+                               if ( secondaryCode === 'http' ) {
+                                       fb.status = 'error3';
+                                       // ajax request failed
+                                       mw.log.warn( 'Feedback report failed 
with HTTP error: ' +  details.textStatus );
+                               } else {
+                                       fb.status = 'error2';
+                                       mw.log.warn( 'Feedback report failed 
with API error: ' +  secondaryCode );
+                               }
+                       } else {
+                               fb.status = 'error1';
+                       }
+               } ).always( function () {
+                       fb.popPending();
+                       fb.close();
+               } );
+       };
+
+       /**
         * @inheritdoc
         */
        mw.Feedback.Dialog.prototype.getTeardownProcess = function ( data ) {
diff --git a/tests/qunit/QUnitTestResources.php 
b/tests/qunit/QUnitTestResources.php
index 8430413..61fb970 100644
--- a/tests/qunit/QUnitTestResources.php
+++ b/tests/qunit/QUnitTestResources.php
@@ -64,6 +64,7 @@
                        'tests/qunit/data/mediawiki.jqueryMsg.data.js',
                        
'tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js',
                        
'tests/qunit/suites/resources/mediawiki/mediawiki.jscompat.test.js',
+                       
'tests/qunit/suites/resources/mediawiki/mediawiki.messagePosterFactory.test.js',
                        
'tests/qunit/suites/resources/mediawiki/mediawiki.test.js',
                        
'tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js',
                        
'tests/qunit/suites/resources/mediawiki/mediawiki.template.test.js',
diff --git 
a/tests/qunit/suites/resources/mediawiki/mediawiki.messagePosterFactory.test.js 
b/tests/qunit/suites/resources/mediawiki/mediawiki.messagePosterFactory.test.js
new file mode 100644
index 0000000..4ed087a
--- /dev/null
+++ 
b/tests/qunit/suites/resources/mediawiki/mediawiki.messagePosterFactory.test.js
@@ -0,0 +1,28 @@
+( function ( mw ) {
+       var TEST_MODEL = 'test-content-model';
+
+       QUnit.module( 'mediawiki', QUnit.newMwEnvironment( {
+               teardown: function () {
+                       mw.messagePosterFactory.unregister( TEST_MODEL );
+               }
+       } ) );
+
+       QUnit.test( 'register', 2, function ( assert ) {
+               var testMessagePosterConstructor = function () {};
+
+               mw.messagePosterFactory.register( TEST_MODEL, 
testMessagePosterConstructor );
+               assert.strictEqual(
+                       mw.messagePosterFactory.contentModelToClass[TEST_MODEL],
+                       testMessagePosterConstructor,
+                       'Constructor is registered'
+               );
+
+               assert.throws(
+                       function () {
+                               mw.messagePosterFactory.register( TEST_MODEL, 
testMessagePosterConstructor );
+                       },
+                       new RegExp( 'The content model \'' + TEST_MODEL + '\' 
is already registered.' ),
+                       'Throws exception is same model is registered a second 
time'
+               );
+       } );
+}( mediaWiki ) );

-- 
To view, visit https://gerrit.wikimedia.org/r/200801
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Ic69acafb24aa737536fe3a074e1958690732f0a7
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/core
Gerrit-Branch: master
Gerrit-Owner: Mattflaschen <mflasc...@wikimedia.org>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to