Mooeypoo has uploaded a new change for review. ( 
https://gerrit.wikimedia.org/r/403768 )

Change subject: [wip^n] Rewrite StructuredDiscussion UI for MVC
......................................................................

[wip^n] Rewrite StructuredDiscussion UI for MVC

Change-Id: I977c7003a280889e78ba2be5cab2ebc9fa1d81e4
---
M extension.json
M includes/View.php
M modules/flow-initialize.js
M modules/mw.flow.Initializer.js
A modules/sd/dm/mw.sd.dm.ViewModel.js
A modules/sd/mw.sd.ApiHandler.js
A modules/sd/mw.sd.Controller.js
A modules/sd/mw.sd.js
A modules/sd/ui/mw.sd.ui.ComponentWrapperWidget.js
A modules/sd/ui/mw.sd.ui.OldSystemBridgeWidget.js
A modules/styles/sd/mw.sd.ui.less
11 files changed, 708 insertions(+), 36 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Flow 
refs/changes/68/403768/1

diff --git a/extension.json b/extension.json
index ce0ab2d..e00c611 100644
--- a/extension.json
+++ b/extension.json
@@ -553,6 +553,36 @@
                                "mobile"
                        ]
                },
+               "ext.StructuredDiscussion.dm": {
+                       "scripts": [
+                               "sd/mw.sd.js",
+                               "sd/mw.sd.Controller.js",
+                               "sd/mw.sd.dm.ViewModel.js"
+                       ],
+                       "dependencies": [
+                               "mediawiki.api",
+                               "oojs"
+                       ],
+               },
+               "ext.StructuredDiscussion.ui": {
+                       "scripts": [
+                               "sd/ui/mw.sd.ComponentWrapperWidget.js",
+                               "sd/ui/mw.sd.OldSystemBridgeWidget.js"
+                       ],
+                       "styles": [
+                               "styles/sd/mw.sd.ui.less"
+                       ],
+                       "dependencies": [
+                               "ext.flow.components",
+                               "ext.StructuredDiscussion.dm",
+                               "oojs-ui"
+                       ],
+               },
+               "ext.StructuredDiscussion": {
+                       "dependencies": [
+                               "ext.StructuredDiscussion.ui"
+                       ]
+               },
                "ext.flow": {
                        "position": "top",
                        "styles": [
diff --git a/includes/View.php b/includes/View.php
index dbb547e..ee48433 100644
--- a/includes/View.php
+++ b/includes/View.php
@@ -82,7 +82,8 @@
                if ( $this->actions->hasValue( $action, 'modules' ) ) {
                        $out->addModules( $this->actions->getValue( $action, 
'modules' ) );
                } else {
-                       $out->addModules( [ 'ext.flow' ] );
+                       // $out->addModules( [ 'ext.flow' ] );
+                       $out->addModules( [ 'ext.StructuredDiscussion' ] );
                }
 
                if ( $this->actions->hasValue( $action, 'moduleStyles' ) ) {
diff --git a/modules/flow-initialize.js b/modules/flow-initialize.js
index c6d9987..3477d1c 100644
--- a/modules/flow-initialize.js
+++ b/modules/flow-initialize.js
@@ -10,27 +10,44 @@
        $( function () {
                var flowBoard,
                        $component = $( '.flow-component' ),
-                       $board = $( '.flow-board' ),
-                       pageTitle = mw.Title.newFromText( mw.config.get( 
'wgPageName' ) ),
-                       initializer = new mw.flow.Initializer( {
-                               pageTitle: pageTitle,
-                               $component: $component,
-                               $board: $board
-                       } );
+                       // $board = $( '.flow-board' ),
+                       $overlay = $( '<div>' )
+                               .addClass( 'mw-sd-ui-overlay' )
+                               .append(  ),
+                       viewModel = new mw.sd.dm.ViewModel( {
+                               pageTitle: mw.Title.newFromText( mw.config.get( 
'wgPageName' ) ),
+                               // Initialization data
+                               flowData: mw.config.get( 'wgFlowData' ) || ( 
mw.flow && mw.flow.data )
+                               // ...
+                       } ),
+                       controller = new mw.sd.Controller( viewModel ),
+                       // TODO: This widget should be initialized after the 
controller
+                       // and model are fully initialized and the data is in 
place
+                       wrapperWidget = new mw.sd.ui.ComponentWrapperWidget(
+                               viewModel,
+                               controller,
+                               {
+                                       $element: $component,
+                                       $overlay: $overlay
+                               }
+                       );
+
+               $( 'body' ).append( $overlay );
+return;
 
                // Set component
-               if ( !initializer.setComponentDom( $component ) ) {
-                       initializer.finishLoading();
-                       return;
-               }
+               // if ( !initializer.setComponentDom( $component ) ) {
+               //      initializer.finishLoading();
+               //      return;
+               // }
 
                // Initialize old system
-               initializer.initOldComponent();
+               // initializer.initOldComponent();
                // Initialize board
                if ( initializer.setBoardDom( $board ) ) {
                        // Set up flowBoard
-                       flowBoard = mw.flow.getPrototypeMethod( 'component', 
'getInstanceByElement' )( $board );
-                       initializer.setBoardObject( flowBoard );
+                       // flowBoard = mw.flow.getPrototypeMethod( 'component', 
'getInstanceByElement' )( $board );
+                       // initializer.setBoardObject( flowBoard );
 
                        // Initialize DM system and board
                        initializer.initDataModel( {
@@ -58,7 +75,7 @@
                                // Fall back to mw.flow.data, which was used 
until September 2015
                                // NOTICE: This block must be after the 
initialization of the ui widgets so
                                // they can populate themselves according to 
the events.
-                               initializer.populateDataModel( mw.config.get( 
'wgFlowData' ) || ( mw.flow && mw.flow.data ) );
+                               // initializer.populateDataModel( 
mw.config.get( 'wgFlowData' ) || ( mw.flow && mw.flow.data ) );
                        }
                } else {
                        // Editing summary in a separate window. That has no
diff --git a/modules/mw.flow.Initializer.js b/modules/mw.flow.Initializer.js
index d22342b..d9e43be 100644
--- a/modules/mw.flow.Initializer.js
+++ b/modules/mw.flow.Initializer.js
@@ -93,15 +93,15 @@
                        }
                } );
        };
-
-       /**
-        * Set up the window overlay
-        */
-       mw.flow.Initializer.prototype.setupWindowOverlay = function () {
-               // Set up window overlay
-               $( 'body' ).append( mw.flow.ui.windowOverlay.$element );
-               mw.flow.ui.windowOverlay.$element.append( 
mw.flow.ui.windowManager.$element );
-       };
+    //
+       // /**
+       //  * Set up the window overlay
+       //  */
+       // mw.flow.Initializer.prototype.setupWindowOverlay = function () {
+       //      // Set up window overlay
+       //      $( 'body' ).append( mw.flow.ui.windowOverlay.$element );
+       //      mw.flow.ui.windowOverlay.$element.append( 
mw.flow.ui.windowManager.$element );
+       // };
 
        /**
         * Set up the sidebar widget if needed
@@ -159,15 +159,15 @@
                this.setupEditTopicSummaryAction();
                this.setupEditTopicTitleAction();
        };
-
-       /**
-        * Initialize the 'old' Flow ui component
-        */
-       mw.flow.Initializer.prototype.initOldComponent = function () {
-               if ( this.$component ) {
-                       mw.flow.initComponent( this.$component );
-               }
-       };
+    //
+       // /**
+       //  * Initialize the 'old' Flow ui component
+       //  */
+       // mw.flow.Initializer.prototype.initOldComponent = function () {
+       //      if ( this.$component ) {
+       //              mw.flow.initComponent( this.$component );
+       //      }
+       // };
 
        /**
         * Initialize the data model objects
@@ -290,7 +290,7 @@
                        },
                        resetBoardEnd: function ( data ) {
                                var $rendered;
-
+debugger;
                                // populateBoardFromApi uses the larger TOC 
limit so the TOC can
                                // be fully populated on re-sort.  To avoid two 
requests
                                // (TOC and full topics) with different limits, 
we do a single
@@ -317,7 +317,6 @@
                                // TODO: Using a timeout doesn't seem like the 
right way to do this.
                                setTimeout( function () {
                                        var boardEl = $rendered[ 1 ];
-
                                        // Since we've replaced the entire 
board, we need to reinitialize
                                        // it. This also takes away the 
original navWidget, so we need to
                                        // make sure it's reinitialized too
diff --git a/modules/sd/dm/mw.sd.dm.ViewModel.js 
b/modules/sd/dm/mw.sd.dm.ViewModel.js
new file mode 100644
index 0000000..9c6fdcb
--- /dev/null
+++ b/modules/sd/dm/mw.sd.dm.ViewModel.js
@@ -0,0 +1,32 @@
+( function ( mw, $ ) {
+       /**
+        * View model for Structured Discussion system
+        *
+        * @class
+        * @mixins OO.EventEmitter
+        * @mixins OO.EmitterList
+        *
+        * @constructor
+        * @param {Object} config Configuration object
+        */
+       mw.sd.dm.ViewModel = function MwSdDmViewModel( config ) {
+               // Mixin constructor
+               OO.EventEmitter.call( this );
+               OO.EmitterList.call( this );
+
+               this.pageTitle = config.pageTitle;
+       };
+
+       /* Initialization */
+       OO.initClass( mw.sd.dm.ViewModel );
+       OO.mixinClass( mw.sd.dm.ViewModel, OO.EventEmitter );
+
+       /**
+        * Get the page title
+        *
+        * @return {mw.Title} Page title
+        */
+       mw.sd.dm.ViewModel.prototype.getPageTitle = function () {
+               return this.pageTitle;
+       };
+}( mediaWiki, jQuery ) );
diff --git a/modules/sd/mw.sd.ApiHandler.js b/modules/sd/mw.sd.ApiHandler.js
new file mode 100644
index 0000000..6ace2c4
--- /dev/null
+++ b/modules/sd/mw.sd.ApiHandler.js
@@ -0,0 +1,443 @@
+( function ( $ ) {
+       /**
+        * API handler for Structured discussion
+        *
+        * @class
+        *
+        * @constructor
+        * @param {string} page Full page name with its namespace;
+        *  for example: "User_talk:Foo"
+        * @param {Object} [config] Configuration options
+        * @cfg {Object} [currentRevision] Current revision Id. Mostly used
+        *  for edit conflict check.
+        * @cfg {Object} [apiConstructorParams] Parameters for mw.Api()
+        * @cfg {Object} [requestParams] Parameters for the request
+        */
+       mw.sd.APIHandler = function MwSDAPIHandler( page, config ) {
+               config = config || {};
+
+               this.apiConstructorParams = $.extend( {
+                       ajax: {
+                               timeout: 5 * 1000, // 5 seconds
+                               cache: false
+                       }
+               }, config.apiConstructorParams );
+
+               this.page = page;
+               this.setCurrentRevision( config.currentRevision );
+
+               this.requestParams = $.extend( {
+                       action: 'flow',
+                       uselang: mw.config.get( 'wgUserLanguage' )
+               }, config.requestParams );
+       };
+
+       OO.initClass( mw.sd.APIHandler );
+
+       /**
+        * Set the current revision Id. This is mostly used for edit actions, 
to check
+        * for edit conflicts.
+        *
+        * @param {string} revisionId Current revision id
+        */
+       mw.sd.APIHandler.prototype.setCurrentRevision = function ( revisionId ) 
{
+               this.currentRevision = revisionId;
+       };
+
+       /**
+        * General get request
+        * @param {string} submodule The requested submodule
+        * @param {Object} requestParams API request parameters
+        * @return {jQuery.Promise} Promise that is resolved when the API 
request
+        *  is done, with the API result.
+        */
+       mw.sd.APIHandler.prototype.get = function ( submodule, requestParams ) {
+               var xhr,
+                       params = $.extend( { submodule: submodule }, 
this.requestParams, requestParams );
+
+               xhr = ( new mw.Api() ).get( params );
+               return xhr
+                       .then( function ( data ) {
+                               return data.flow[ submodule ].result;
+                       } )
+                       .promise( { abort: xhr.abort } );
+       };
+
+       /**
+        * Post with edit token request
+        *
+        * @param {string} submodule The requested submodule
+        * @param {Object} requestParams API request parameters
+        * @return {jQuery.Promise} Promise that is resolved when the API 
request
+        *  is done, with the API result.
+        */
+       mw.sd.APIHandler.prototype.postEdit = function ( submodule, 
requestParams ) {
+               var params = $.extend( { submodule: submodule }, 
this.requestParams, requestParams );
+
+               return ( new mw.Api() ).postWithToken( 'csrf', params );
+       };
+
+       /**
+        * Get page categories. This will recursively continue to fetch results
+        * until all page categories are fetched.
+        *
+        * @return {jQuery.Promise} Promise that is resolved when the API 
request
+        *  is done, with the API result.
+        */
+       mw.sd.APIHandler.prototype.getCategories = function () {
+               var params = {
+                       action: 'query',
+                       titles: this.page,
+                       generator: 'categories',
+                       gcllimit: 'max'
+               };
+
+               return ( new mw.Api() ).get( $.extend( {}, this.requestParams, 
params ) )
+                       .then( function ( response ) {
+                               return OO.getProp( response, 'query', 'pages' );
+                       } );
+       };
+
+       /**
+        * Gets the reason the page was protected
+        *
+        * @return {jQuery.Promise}
+        * @return {Function} return.done
+        * @return {string} return.done.reason Reason, as HTML
+        */
+       mw.sd.APIHandler.prototype.getProtectionReason = function () {
+               var params = {
+                       action: 'query',
+                       list: 'logevents',
+                       leprop: 'parsedcomment',
+                       leaction: 'protect/protect',
+                       letitle: this.page,
+                       lelimit: 1
+               };
+
+               return ( new mw.Api() ).get( $.extend( {}, this.requestParams, 
params ) )
+                       .then( function ( response ) {
+                               return OO.getProp( response, 'query', 
'logevents', 0, 'parsedcomment' );
+                       } );
+       };
+
+       /**
+        * Send a request to get topic list
+        *
+        * @param {string} orderType Sort order type, 'newest' or 'updated'
+        * @param {Object} config Configuration
+        * @cfg {string} [offset] Topic offset id or timestamp offset
+        *  if given, the topic list will be returned with topics that
+        *  are after (and including) the topic with the given uuid or
+        *  after the given timestamp.
+        * @cfg {string} [toconly] Receive a stripped reply that fits the ToC. 
For more information
+        *  see 'toconly' in the API documentation.
+        * @return {jQuery.Promise} Promise that is resolved with the topiclist 
response
+        */
+       mw.sd.APIHandler.prototype.getTopicList = function ( orderType, config 
) {
+               var params = {
+                       page: this.page
+               };
+
+               config = config || {};
+
+               params.vtltoconly = !!config.toconly;
+               params.vtllimit = config.toconly ? 50 : 10;
+               params.vtlsortby = orderType;
+
+               if ( orderType === 'newest' ) {
+                       params[ 'vtloffset-id' ] = config.offset;
+               } else if ( orderType === 'updated' ) {
+                       // Translate api/object-given offset to MW offset for 
the API request
+                       params.vtloffset = moment.utc( config.offset ).format( 
'YYYYMMDDHHmmss' );
+               }
+
+               return this.get( 'view-topiclist', params )
+                       .then( function ( data ) {
+                               return data.topiclist;
+                       } );
+       };
+
+       /**
+        * Adds CAPTCHA to parameters if applicable
+        *
+        * @param {Object} params API Parameters to add CAPTCHA information to
+        * @param {Object|null} captcha CAPTCHA object
+        * @param {string} captcha.id CAPTCHA ID
+        * @param {string} captcha.answer CAPTCHA answer (user-provided)
+        */
+       mw.sd.APIHandler.prototype.addCaptcha = function ( params, captcha ) {
+               // TODO: Find a better way to plug this in.
+               if ( captcha ) {
+                       params.wpCaptchaId = captcha.id;
+                       params.wpCaptchaWord = captcha.answer;
+               }
+       };
+
+       /**
+        * Get topic title from topic id
+        *
+        * @param {string} topicId Topic id
+        * @return {string} Topic title
+        */
+       mw.sd.APIHandler.prototype.getTopicTitle = function ( topicId ) {
+               return ( new mw.Title( topicId, 2600 ) ).getPrefixedDb();
+       };
+
+       /**
+        * Send an edit request to the API to save a reply.
+        *
+        * @param {string} topicId Topic Id
+        * @param {string} replyTo The parent of this reply
+        * @param {string} content Reply content
+        * @param {string} format Reply content format
+        * @param {Object} [captcha] CAPTCHA information
+        * @return {jQuery.Promise} Promise that is resolved with the id of the 
workflow
+        *  that this reply belongs to
+        */
+       mw.sd.APIHandler.prototype.saveReply = function ( topicId, replyTo, 
content, format, captcha ) {
+               var params = {
+                       action: 'flow',
+                       submodule: 'reply',
+                       page: 'Topic:' + topicId,
+                       repreplyTo: replyTo,
+                       repcontent: content,
+                       repformat: format
+               };
+
+               this.addCaptcha( params, captcha );
+
+               return ( new mw.Api() ).postWithToken( 'csrf', $.extend( {}, 
this.requestParams, params ) )
+                       .then( function ( data ) {
+                               return data.flow.reply.workflow;
+                       } );
+       };
+
+       /**
+        * Save new topic in the board
+        *
+        * @param {string} title Topic title
+        * @param {string} content Topic content
+        * @param {string} format Content format
+        * @param {Object} [captcha] CAPTCHA information
+        * @return {jQuery.Promise} Promise that is resolved with the new topic 
id
+        */
+       mw.sd.APIHandler.prototype.saveNewTopic = function ( title, content, 
format, captcha ) {
+               var params = {
+                       submodule: 'new-topic',
+                       page: this.page,
+                       nttopic: title,
+                       ntcontent: content,
+                       ntformat: format
+               };
+
+               this.addCaptcha( params, captcha );
+
+               return ( new mw.Api() ).postWithToken( 'csrf', $.extend( {}, 
this.requestParams, params ) )
+                       .then( function ( response ) {
+                               return OO.getProp( response.flow, 'new-topic', 
'committed', 'topiclist', 'topic-id' );
+                       } );
+       };
+
+       /**
+        * Get the board description from the API.
+        *
+        * @param {string} [contentFormat='fixed-html'] Content format for 
board description
+        * @return {jQuery.Promise} Promise that is resolved with the header 
revision data
+        */
+       mw.sd.APIHandler.prototype.getDescription = function ( contentFormat ) {
+               var params = {
+                       page: this.page,
+                       vhformat: contentFormat || 'fixed-html'
+               };
+
+               return this.get( 'view-header', params )
+                       .then( function ( data ) {
+                               return data.header.revision;
+                       } );
+       };
+
+       /**
+        * Save header information.
+        *
+        * @param {string} content Header content
+        * @param {string} format Content format for board description
+        * @param {Object} [captcha] CAPTCHA information
+        * @return {jQuery.Promise} Promise that is resolved with the saved 
header revision id
+        */
+       mw.sd.APIHandler.prototype.saveDescription = function ( content, 
format, captcha ) {
+               var xhr,
+                       params = {
+                               page: this.page,
+                               ehcontent: content,
+                               ehformat: format,
+                               ehprev_revision: this.currentRevision
+                       };
+
+               this.addCaptcha( params, captcha );
+
+               xhr = this.postEdit( 'edit-header', params )
+                       .then( function ( data ) {
+                               return OO.getProp( data.flow, 'edit-header', 
'committed', 'header', 'header-revision-id' );
+                       } );
+
+               return xhr.promise( { abort: xhr.abort } );
+       };
+
+       /**
+        * Get a post.
+        *
+        * @param {string} topicId
+        * @param {string} postId
+        * @param {string} format
+        * @return {jQuery.Promise} Promise that is resolved with the post 
revision data
+        */
+       mw.sd.APIHandler.prototype.getPost = function ( topicId, postId, format 
) {
+               var params = {
+                       page: this.getTopicTitle( topicId ),
+                       vppostId: postId,
+                       vpformat: format || 'html'
+               };
+
+               return this.get( 'view-post', params )
+                       .then( function ( data ) {
+                               return data.topic.revisions[ data.topic.posts[ 
postId ] ];
+                       } );
+       };
+
+       /**
+        * Save a post.
+        *
+        * @param {string} topicId
+        * @param {string} postId
+        * @param {string} content
+        * @param {string} format
+        * @param {string} [captcha] CAPTCHA information
+        * @return {jQuery.Promise} Promise that is resolved with the saved 
post revision id
+        */
+       mw.sd.APIHandler.prototype.savePost = function ( topicId, postId, 
content, format, captcha ) {
+               var params = {
+                       page: this.getTopicTitle( topicId ),
+                       epcontent: content,
+                       epformat: format,
+                       epprev_revision: this.currentRevision,
+                       eppostId: postId
+               };
+
+               this.addCaptcha( params, captcha );
+
+               return this.postEdit( 'edit-post', params )
+                       .then( function ( data ) {
+                               return OO.getProp( data.flow, 'edit-post', 
'workflow' );
+                       } );
+       };
+
+       /**
+        * Get a topic summary.
+        *
+        * @param {string} topicId
+        * @param {string} format
+        * @return {jQuery.Promise} Promise that is resolved with the topic 
summary revision
+        */
+       mw.sd.APIHandler.prototype.getTopicSummary = function ( topicId, format 
) {
+               var params = {
+                       page: this.getTopicTitle( topicId ),
+                       vtsformat: format || 'html'
+               };
+
+               return this.get( 'view-topic-summary', params )
+                       .then( function ( data ) {
+                               return data.topicsummary.revision;
+                       } );
+       };
+
+       /**
+        * Save a topic summary.
+        *
+        * @param {string} topicId
+        * @param {string} content
+        * @param {string} format
+        * @param {Object} captcha
+        * @return {jQuery.Promise} Promise that is resolved with workflow id
+        */
+       mw.sd.APIHandler.prototype.saveTopicSummary = function ( topicId, 
content, format, captcha ) {
+               var params = {
+                       page: this.getTopicTitle( topicId ),
+                       etssummary: content,
+                       etsformat: format,
+                       etsprev_revision: this.currentRevision
+               };
+
+               this.addCaptcha( params, captcha );
+
+               return this.postEdit( 'edit-topic-summary', params )
+                       .then( function ( data ) {
+                               return OO.getProp( data.flow, 
'edit-topic-summary', 'workflow' );
+                       } );
+       };
+
+       /**
+        * Save a topic title.
+        *
+        * @param {string} topicId
+        * @param {string} content
+        * @param {Object} captcha
+        * @return {jQuery.Promise} Promise that is resolved with workflow id
+        */
+       mw.sd.APIHandler.prototype.saveTopicTitle = function ( topicId, 
content, captcha ) {
+               var params = {
+                       page: this.getTopicTitle( topicId ),
+                       etcontent: content,
+                       etprev_revision: this.currentRevision
+               };
+
+               this.addCaptcha( params, captcha );
+
+               return this.postEdit( 'edit-title', params )
+                       .then( function ( data ) {
+                               return OO.getProp( data.flow, 'edit-title', 
'workflow' );
+                       } );
+       };
+
+       /**
+        * Execute the 'lock-topic' moderation action against a topic. Can be 
used to resolve or reopen a topic.
+        *
+        * @param {string} topicId Id of the topic to moderate
+        * @param {string} moderationState Can be 'lock' or 'unlock'
+        * @param {string} reasonMsgKey Message key for the moderation reason
+        * @return {jQuery.Promise} Promise that is resolved with workflow id
+        */
+       mw.sd.APIHandler.prototype.lockTopic = function ( topicId, 
moderationState, reasonMsgKey ) {
+               var params = {
+                       page: this.getTopicTitle( topicId ),
+                       cotmoderationState: moderationState,
+                       cotreason: mw.msg( reasonMsgKey )
+               };
+
+               return this.postEdit( 'lock-topic', params )
+                       .then( function ( data ) {
+                               return OO.getProp( data.flow, 'lock-topic', 
'workflow' );
+                       } );
+       };
+
+       /**
+        * Resolve a topic.
+        *
+        * @param {string} topicId
+        * @return {jQuery.Promise} Promise that is resolved with workflow id
+        */
+       mw.sd.APIHandler.prototype.resolveTopic = function ( topicId ) {
+               return this.lockTopic( topicId, 'lock', 
'flow-rev-message-lock-topic-reason' );
+       };
+
+       /**
+        * Reopen a topic.
+        *
+        * @param {string} topicId
+        * @return {jQuery.Promise} Promise that is resolved with workflow id
+        */
+       mw.sd.APIHandler.prototype.reopenTopic = function ( topicId ) {
+               return this.lockTopic( topicId, 'unlock', 
'flow-rev-message-restore-topic-reason' );
+       };
+
+}( jQuery ) );
diff --git a/modules/sd/mw.sd.Controller.js b/modules/sd/mw.sd.Controller.js
new file mode 100644
index 0000000..e6d9b63
--- /dev/null
+++ b/modules/sd/mw.sd.Controller.js
@@ -0,0 +1,21 @@
+( function ( mw, $ ) {
+       /**
+        * Controller for Structured Discussion system
+        * @class
+        *
+        * @constructor
+        * @param {Object} config Configuration object
+        */
+       mw.sd.Controller = function MwSdController( viewModel, config ) {
+               config = config || {}:
+
+               this.model = viewModel;
+               this.api = new mw.sd.ApiHandler(
+                       this.model.getPageTitle().getPrefixedDb(),
+                       config.api || {}
+               );
+       };
+
+       /* Initialization */
+       OO.initClass( mw.sd.Controller );
+}( mediaWiki, jQuery ) );
diff --git a/modules/sd/mw.sd.js b/modules/sd/mw.sd.js
new file mode 100644
index 0000000..55eccdd
--- /dev/null
+++ b/modules/sd/mw.sd.js
@@ -0,0 +1 @@
+mw.sd = mw.sd || {};
diff --git a/modules/sd/ui/mw.sd.ui.ComponentWrapperWidget.js 
b/modules/sd/ui/mw.sd.ui.ComponentWrapperWidget.js
new file mode 100644
index 0000000..8d95f5a
--- /dev/null
+++ b/modules/sd/ui/mw.sd.ui.ComponentWrapperWidget.js
@@ -0,0 +1,75 @@
+( function ( $ ) {
+       /**
+        * Wrapper widget for StructuredDiscussion UI
+        * @class
+        *
+        * @constructor
+        * @param {Object} config Configuration object
+        */
+       mw.sd.ui.ComponentWrapperWidget = function ( viewModel, controller, 
config ) {
+               config = config || {}:
+
+               // Parent method
+               // Note that we send $element as the flow-component, which will 
be set
+               // as this.$element
+               mw.sd.ui.ComponentWrapperWidget.parent.call( this, config );
+
+               this.model = viewModel;
+               this.controller = controller;
+
+               this.$overlay = config.$overlay || this.$element;
+               // TODO: The widget should be the only thing managing the 
factory and window manager
+               // and the sub-widgets (including all dialogs) should use this 
internally.
+               // At the moment, there are still dialogs that use the global 
mw.flow.windowManager
+               // and mw.flow.windowFactory, and those should be removed when 
the entire widget
+               // set is managed from this wrapper widget
+               this.windowFactory = config.windowFactory || new OO.Factory();
+               this.windowManager = config.windowManager || new 
OO.ui.WindowManager( {
+                       factory: this.windowFactory
+               } );
+               this.$overlay.append( this.windowManager.$element );
+
+               this.bridgeWidget = new mw.sd.ui.OldSystemBridgeWidget( 
this.$element );
+               this.processWidgetsOnBoard();
+
+               this.model.connect( this, {
+                       reinitializeFromTemplate: 
'onModelReinitializeFromTemplate'
+               } );
+
+               this.$element
+                       .addClass( 'mw-sd-ui-componentWrapperWidget' );
+       };
+
+       /**
+        * Process the widgets that are attached to the board after it is 
initialized
+        */
+       mw.sd.ui.ComponentWrapperWidget.prototype.processWidgetsOnBoard = 
function () {
+               // Use this.$element.find( ... ) so that we always do this to 
the updated
+               // DOM even after the sub-piece that is the FlowBoard was 
reinitialized
+
+               // - Attach the navigation widget
+               // - Replace reply forms
+               // - Set up the 'new topic' widget
+               // - stop pending
+       };
+
+       /**
+        * Respond to reinitializeFromTemplate event, where the board was 
rebuilt from
+        * a template and we are now reinserting and replacing the board DOM
+        *
+        * @param  {[type]} $templateRenderArray Template array result
+        */
+       
mw.sd.ui.ComponentWrapperWidget.prototype.onModelReinitializeFromTemplate = 
function ( $templateRenderArray ) {
+               this.bridgeWidget.reinitializeFromTemplate( 
$templateRenderArray );
+               this.processWidgetsOnBoard();
+       };
+
+       /**
+        * Set the pending state of the component
+        *
+        * @param {boolean} isPending State is pending
+        */
+       mw.sd.ui.ComponentWrapperWidget.prototype.setPending = function ( 
isPending ) {
+               this.$element.toggleClass( 'mw-sd-ui-pending', isPending );
+       };
+}( jQuery ) );
diff --git a/modules/sd/ui/mw.sd.ui.OldSystemBridgeWidget.js 
b/modules/sd/ui/mw.sd.ui.OldSystemBridgeWidget.js
new file mode 100644
index 0000000..6adcf7b
--- /dev/null
+++ b/modules/sd/ui/mw.sd.ui.OldSystemBridgeWidget.js
@@ -0,0 +1,39 @@
+( function ( $ ) {
+       /**
+        * Old system controller for StructuredDiscussions operations
+        * @class
+        *
+        * @constructor
+        * @param {Object} config Configuration object
+        */
+       mw.sd.ui.OldSystemBridgeWidget = function ( $component, config ) {
+               config = config || {};
+
+               this.$component = $component;
+               this.$board = null;
+               this.flowBoard = null;
+
+               // TODO: Utilize a different initialization when we are in edit 
summary mode
+               this.$board = this.$component.find( '.flow-board' );
+
+               // flowBoard emits 'loadmore' and 'refreshTopic'
+               this.flowBoard = mw.flow.getPrototypeMethod( 'component', 
'getInstanceByElement' )( this.$board );
+
+               // First initialization for the entire component
+               mw.flow.initComponent( this.$component );
+       };
+
+       /* Inheritance */
+       OO.inheritClass( mw.sd.ui.OldSystemBridgeWidget, OO.ui.Widget );
+
+       /**
+        * Reinitialize the board based on a new structure from the template
+        *
+        * @param {jQuery[]} $templateRenderArray A jQuery collection array that
+        *  is the result of the template rendering
+        */
+       mw.sd.ui.OldSystemBridgeWidget.prototype.reinitializeFromTemplate = 
function ( $templateRenderArray ) {
+               this.$board = $( $templateRenderArray[ 1 ] );
+               this.flowBoard.reinitializeContainer( $templateRenderArray );
+       };
+}( jQuery ) );
diff --git a/modules/styles/sd/mw.sd.ui.less b/modules/styles/sd/mw.sd.ui.less
new file mode 100644
index 0000000..9c000bc3
--- /dev/null
+++ b/modules/styles/sd/mw.sd.ui.less
@@ -0,0 +1,14 @@
+.mw-sd-ui {
+       &-overlay {
+       font-family: sans-serif;
+       position: absolute;
+       top: 0;
+       right: 0;
+       left: 0;
+       z-index: 1;
+       font-size: 0.8em;
+               & > * {
+                       z-index: 1;
+               }
+       }
+}

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

Gerrit-MessageType: newchange
Gerrit-Change-Id: I977c7003a280889e78ba2be5cab2ebc9fa1d81e4
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/Flow
Gerrit-Branch: master
Gerrit-Owner: Mooeypoo <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to