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