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

Change subject: [WIP] Remove editor switching infrastructure in favor of VE's 
source mode
......................................................................

[WIP] Remove editor switching infrastructure in favor of VE's source mode

This simplifies a lot of code.

TODO: add a textarea fallback for when VE is not available/supported

Bug: T155861
Change-Id: Ie8014a64eae5f1b24495d6108e720464a8e75d9c
---
M extension.json
M modules/editor/editors/visualeditor/mw.flow.ve.CommandRegistry.js
M modules/editor/editors/visualeditor/mw.flow.ve.Target.js
M modules/editor/editors/visualeditor/mw.flow.ve.Target.less
D 
modules/editor/editors/visualeditor/ui/actions/mw.flow.ve.ui.SwitchEditorAction.js
M modules/flow/ui/tools/mw.flow.ui.MWEditModeTool.js
D modules/flow/ui/widgets/editor/editors/mw.flow.ui.AbstractEditorWidget.js
D modules/flow/ui/widgets/editor/editors/mw.flow.ui.VisualEditorWidget.js
D modules/flow/ui/widgets/editor/editors/mw.flow.ui.WikitextEditorWidget.js
D modules/flow/ui/widgets/editor/mw.flow.ui.EditorSwitcherWidget.js
M modules/flow/ui/widgets/editor/mw.flow.ui.EditorWidget.js
M modules/flow/ui/widgets/mw.flow.ui.BoardDescriptionWidget.js
M modules/flow/ui/widgets/mw.flow.ui.EditPostWidget.js
M modules/flow/ui/widgets/mw.flow.ui.EditTopicSummaryWidget.js
M modules/flow/ui/widgets/mw.flow.ui.NewTopicWidget.js
M modules/mw.flow.Initializer.js
M modules/styles/flow.variables.less
D 
modules/styles/flow/widgets/editor/editors/mw.flow.ui.WikitextEditorWidget.less
D modules/styles/flow/widgets/editor/mw.flow.ui.EditorSwitcherWidget.less
M modules/styles/flow/widgets/editor/mw.flow.ui.EditorWidget.less
M modules/styles/flow/widgets/editor/mw.flow.ui.editor-monobook.less
M modules/styles/flow/widgets/editor/mw.flow.ui.editor-vector.less
M modules/styles/flow/widgets/mw.flow.ui.NewTopicWidget.less
23 files changed, 521 insertions(+), 1,672 deletions(-)


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

diff --git a/extension.json b/extension.json
index 948e992..32a0192 100644
--- a/extension.json
+++ b/extension.json
@@ -487,12 +487,8 @@
                                
"flow/ui/widgets/mw.flow.ui.SidebarExpandWidget.js",
                                "flow/ui/widgets/mw.flow.ui.NewTopicWidget.js",
                                
"flow/ui/widgets/mw.flow.ui.TopicTitleWidget.js",
-                               
"flow/ui/widgets/editor/editors/mw.flow.ui.AbstractEditorWidget.js",
-                               
"flow/ui/widgets/editor/editors/mw.flow.ui.WikitextEditorWidget.js",
-                               
"flow/ui/widgets/editor/editors/mw.flow.ui.VisualEditorWidget.js",
                                
"flow/ui/widgets/editor/mw.flow.ui.AnonWarningWidget.js",
                                
"flow/ui/widgets/editor/mw.flow.ui.CanNotEditWidget.js",
-                               
"flow/ui/widgets/editor/mw.flow.ui.EditorSwitcherWidget.js",
                                
"flow/ui/widgets/editor/mw.flow.ui.EditorControlsWidget.js",
                                
"flow/ui/widgets/editor/mw.flow.ui.EditorWidget.js",
                                
"flow/ui/widgets/mw.flow.ui.BoardDescriptionWidget.js",
@@ -510,9 +506,7 @@
                                
"styles/flow/widgets/editor/mw.flow.ui.AnonWarningWidget.less",
                                
"styles/flow/widgets/editor/mw.flow.ui.CanNotEditWidget.less",
                                
"styles/flow/widgets/editor/mw.flow.ui.EditorControlsWidget.less",
-                               
"styles/flow/widgets/editor/mw.flow.ui.EditorSwitcherWidget.less",
                                
"styles/flow/widgets/editor/mw.flow.ui.EditorWidget.less",
-                               
"styles/flow/widgets/editor/editors/mw.flow.ui.WikitextEditorWidget.less",
                                
"styles/flow/widgets/mw.flow.ui.CategoryItemWidget.less",
                                
"styles/flow/widgets/mw.flow.ui.CategoriesWidget.less",
                                
"styles/flow/widgets/mw.flow.ui.TopicTitleWidget.less"
@@ -653,14 +647,6 @@
                                "mobile"
                        ]
                },
-               "ext.flow.switching": {
-                       "scripts": [
-                               "flow/ui/tools/mw.flow.ui.MWEditModeTool.js"
-                       ],
-                       "dependencies": [
-                               "ext.visualEditor.switching"
-                       ]
-               },
                "ext.flow.visualEditor": {
                        "scripts": [
                                
"editor/editors/visualeditor/mw.flow.ve.Target.js",
@@ -669,9 +655,9 @@
                                
"editor/editors/visualeditor/ui/tools/mw.flow.ve.ui.MentionInspectorTool.js",
                                
"editor/editors/visualeditor/ui/contextitem/mw.flow.ve.ui.MentionContextItem.js",
                                
"editor/editors/visualeditor/ui/widgets/mw.flow.ve.ui.MentionTargetInputWidget.js",
-                               
"editor/editors/visualeditor/ui/actions/mw.flow.ve.ui.SwitchEditorAction.js",
                                
"editor/editors/visualeditor/mw.flow.ve.CommandRegistry.js",
-                               
"editor/editors/visualeditor/mw.flow.ve.SequenceRegistry.js"
+                               
"editor/editors/visualeditor/mw.flow.ve.SequenceRegistry.js",
+                               "flow/ui/tools/mw.flow.ui.MWEditModeTool.js"
                        ],
                        "styles": [
                                
"editor/editors/visualeditor/mw.flow.ve.Target.less",
@@ -683,7 +669,8 @@
                                "ext.visualEditor.mediawiki",
                                "ext.visualEditor.desktopTarget",
                                "ext.visualEditor.mwextensions.desktop",
-                               "ext.flow.switching",
+                               "ext.visualEditor.mwwikitext",
+                               "mediawiki.editfont.styles",
                                "oojs-ui.styles.icons-editing-advanced",
                                "site",
                                "user",
diff --git a/modules/editor/editors/visualeditor/mw.flow.ve.CommandRegistry.js 
b/modules/editor/editors/visualeditor/mw.flow.ve.CommandRegistry.js
index 6a8c73d..e7e3940 100644
--- a/modules/editor/editors/visualeditor/mw.flow.ve.CommandRegistry.js
+++ b/modules/editor/editors/visualeditor/mw.flow.ve.CommandRegistry.js
@@ -18,13 +18,4 @@
                        { args: [ 'flowMention', { selectAt: true } ], 
supportedSelections: [ 'linear' ] }
                )
        );
-
-       ve.ui.commandRegistry.register(
-               new ve.ui.Command(
-                       'flowSwitchEditor',
-                       'flowSwitchEditor',
-                       'switch', // method to call on action
-                       { args: [] } // arguments to pass to action
-               )
-       );
 }( ve ) );
diff --git a/modules/editor/editors/visualeditor/mw.flow.ve.Target.js 
b/modules/editor/editors/visualeditor/mw.flow.ve.Target.js
index 62d5c58..dc98dc6 100644
--- a/modules/editor/editors/visualeditor/mw.flow.ve.Target.js
+++ b/modules/editor/editors/visualeditor/mw.flow.ve.Target.js
@@ -16,15 +16,25 @@
                        toolbarConfig: { actions: true, position: 'bottom' }
                } );
 
+               this.switchingPromise = null;
+
                // HACK: stop VE's education popups from appearing (T116643)
                this.dummyToolbar = true;
        };
 
        OO.inheritClass( mw.flow.ve.Target, ve.init.mw.Target );
 
+       /**
+        * @event switchMode
+        * @param {jQuery.Promise} promise Promise resolved when switch is 
complete
+        * @param {string} newMode Mode being switched to ('visual' or 'source')
+        */
+
        // Static
 
        mw.flow.ve.Target.static.name = 'flow';
+
+       mw.flow.ve.Target.static.modes = [ 'visual', 'source' ];
 
        mw.flow.ve.Target.static.toolbarGroups = [
                {
@@ -47,29 +57,21 @@
                [ 'link/mwExternal' ]
        );
 
-       // Static Methods
-       mw.flow.ve.Target.static.setSwitchable = function ( switchable ) {
-               // FIXME this isn't supposed to be a global state thing, it's 
supposed to be
-               // variable per EditorWidget instance
-
-               if ( switchable ) {
-                       ve.ui.toolFactory.register( 
mw.flow.ui.MWEditModeVisualTool );
-                       ve.ui.toolFactory.register( 
mw.flow.ui.MWEditModeSourceTool );
-                       mw.flow.ve.Target.static.actionGroups = [ {
-                               type: 'list',
-                               icon: 'edit',
-                               title: mw.msg( 
'visualeditor-mweditmode-tooltip' ),
-                               include: [ 'editModeVisual', 'editModeSource' ]
-                       } ];
-               } else {
-                       mw.flow.ve.Target.static.actionGroups = [];
-               }
-       };
+       mw.flow.ve.Target.static.actionGroups = [ {
+               type: 'list',
+               icon: 'edit',
+               title: mw.msg( 'visualeditor-mweditmode-tooltip' ),
+               include: [ 'editModeVisual', 'editModeSource' ]
+       } ];
 
        // Methods
 
-       mw.flow.ve.Target.prototype.loadHtml = function ( html ) {
-               var doc = this.constructor.static.parseDocument( html );
+       /**
+        * Load content into the editor
+        * @param {string} content HTML or wikitext
+        */
+       mw.flow.ve.Target.prototype.loadContent = function ( content ) {
+               var doc = this.constructor.static.parseDocument( content, 
this.getDefaultMode() );
                this.documentReady( doc );
        };
 
@@ -87,6 +89,86 @@
                }
        };
 
+       /**
+        * Switch between visual and source mode.
+        *
+        * If a switch is already in progress, the promise for that switch is 
returned,
+        * and no new switch is initiated.
+        *
+        * @return {jQuery.Promise} Promise that is resolved when the switch is 
complete
+        */
+       mw.flow.ve.Target.prototype.switchMode = function () {
+               var newMode, oldFormat, newFormat, doc, content;
+
+               if ( this.switchingDeferred ) {
+                       return this.switchingDeferred;
+               }
+
+               newMode = this.getDefaultMode() === 'visual' ? 'source' : 
'visual';
+               oldFormat = newMode === 'visual' ? 'wikitext' : 'html';
+               newFormat = newMode === 'visual' ? 'html' : 'wikitext';
+               doc = this.getSurface().getDom();
+               // When coming from visual mode, getDom() returns an 
HTMLDocument, otherwise a string
+               content = oldFormat === 'html' ? this.getHtml( doc ) : doc;
+
+               this.setDefaultMode( newMode );
+               this.switchingDeferred = $.Deferred();
+               this.convertContent( content, oldFormat, newFormat )
+                       .then( this.loadContent.bind( this ) )
+                       .fail( this.switchingDeferred.reject );
+
+               this.emit( 'switchMode', this.switchingDeferred, newMode );
+               return this.switchingDeferred;
+       };
+
+       mw.flow.ve.Target.prototype.surfaceReady = function () {
+               var deferred;
+
+               // Parent method
+               mw.flow.ve.Target.parent.prototype.surfaceReady.apply( this, 
arguments );
+
+               if ( this.switchingDeferred ) {
+                       deferred = this.switchingDeferred;
+                       this.switchingDeferred = null;
+                       deferred.resolve();
+               }
+       };
+
+       /**
+        * Convert content from one format to another.
+        *
+        * It's safe to call this function with fromFormat=toFormat: in that 
case, the returned promise
+        * will be resolved immediately with the original content.
+        *
+        * @param {string} content Content in old format
+        * @param {string} fromFormat Old format name
+        * @param {string} toFormat New format name
+        * @return {jQuery.Promise} Promise resolved with content converted to 
new format
+        */
+       mw.flow.ve.Target.prototype.convertContent = function ( content, 
fromFormat, toFormat ) {
+               if ( fromFormat === toFormat || content === '' ) {
+                       return $.Deferred().resolve( content ).promise();
+               }
+
+               return new mw.Api().post( {
+                       action: 'flow-parsoid-utils',
+                       from: fromFormat,
+                       to: toFormat,
+                       content: content,
+                       title: mw.config.get( 'wgPageName' )
+               } )
+                       .then( function ( data ) {
+                               var content = data[ 'flow-parsoid-utils' 
].content;
+                               if ( toFormat === 'html' ) {
+                                       // loadContent() expects a full 
document, but the API only gives us the body content
+                                       content = '<body>' + content + 
'</body>';
+                               }
+                               return content;
+                       }, function () {
+                               return mw.msg( 'flow-error-parsoid-failure' );
+                       } );
+       };
+
        // Registration
 
        ve.init.mw.targetFactory.register( mw.flow.ve.Target );
diff --git a/modules/editor/editors/visualeditor/mw.flow.ve.Target.less 
b/modules/editor/editors/visualeditor/mw.flow.ve.Target.less
index 6431952..f200caf 100644
--- a/modules/editor/editors/visualeditor/mw.flow.ve.Target.less
+++ b/modules/editor/editors/visualeditor/mw.flow.ve.Target.less
@@ -2,7 +2,7 @@
 @import 'mediawiki.ui/variables';
 @import '../../../styles/flow.variables';
 
-// TODO most of this should move to VisualEditorWidget.less once the old 
editor system
+// TODO most of this should move to EditorWidget.less once the old editor 
system
 // has been removed
 .flow-component {
        .ve-init-target {
diff --git 
a/modules/editor/editors/visualeditor/ui/actions/mw.flow.ve.ui.SwitchEditorAction.js
 
b/modules/editor/editors/visualeditor/ui/actions/mw.flow.ve.ui.SwitchEditorAction.js
deleted file mode 100644
index b611ae5..0000000
--- 
a/modules/editor/editors/visualeditor/ui/actions/mw.flow.ve.ui.SwitchEditorAction.js
+++ /dev/null
@@ -1,60 +0,0 @@
-( function ( mw, OO, ve ) {
-
-       /**
-        * Action to switch from VisualEditor to the Wikitext editing interface
-        * within Flow.
-        *
-        * @class
-        * @extends ve.ui.Action
-        *
-        * @constructor
-        * @param {ve.ui.Surface} surface Surface to act on
-        */
-       mw.flow.ve.ui.SwitchEditorAction = function 
MwFlowVeUiSwitchEditorAction( surface ) {
-               // Parent constructor
-               ve.ui.Action.call( this, surface );
-       };
-
-       /* Inheritance */
-
-       OO.inheritClass( mw.flow.ve.ui.SwitchEditorAction, ve.ui.Action );
-
-       /* Static Properties */
-
-       /**
-        * Name of this action
-        *
-        * @static
-        * @property
-        */
-       mw.flow.ve.ui.SwitchEditorAction.static.name = 'flowSwitchEditor';
-
-       /**
-        * List of allowed methods for the action.
-        *
-        * @static
-        * @property
-        */
-       mw.flow.ve.ui.SwitchEditorAction.static.methods = [ 'switch' ];
-
-       /* Methods */
-
-       /**
-        * Switch to wikitext editing.
-        *
-        * @method
-        */
-       mw.flow.ve.ui.SwitchEditorAction.prototype.switch = function () {
-               var $editor = this.surface.$element.closest( '.flow-editor' );
-               if ( $editor.length ) {
-                       // Old editor
-                       mw.flow.editor.switchEditor( $editor.find( 'textarea' 
), 'none' );
-               } else {
-                       // New editor
-                       this.surface.emit( 'switchEditor' );
-               }
-       };
-
-       ve.ui.actionFactory.register( mw.flow.ve.ui.SwitchEditorAction );
-
-}( mediaWiki, OO, ve ) );
diff --git a/modules/flow/ui/tools/mw.flow.ui.MWEditModeTool.js 
b/modules/flow/ui/tools/mw.flow.ui.MWEditModeTool.js
index 9a5437b..b3b028b 100644
--- a/modules/flow/ui/tools/mw.flow.ui.MWEditModeTool.js
+++ b/modules/flow/ui/tools/mw.flow.ui.MWEditModeTool.js
@@ -6,31 +6,10 @@
  */
 
 /**
- * MediaWiki UserInterface edit mode tool.
- *
- * @class
- * @abstract
- */
-mw.flow.ui.MWEditModeTool = function VeUiMWEditModeTool() {
-};
-
-/* Inheritance */
-
-OO.initClass( mw.flow.ui.MWEditModeTool );
-
-/* Methods */
-
-// Inherits from mw.libs.ve.MWEditModeTool
-mw.flow.ui.MWEditModeTool.prototype.getMode = function () {
-       return this.toolbar.surface ? 'visual' : 'source';
-};
-
-/**
  * MediaWiki UserInterface edit mode source tool.
  *
  * @class
  * @extends mw.libs.ve.MWEditModeSourceTool
- * @mixins mw.flow.ui.MWEditModeTool
  * @constructor
  * @param {OO.ui.ToolGroup} toolGroup
  * @param {Object} [config] Config options
@@ -38,23 +17,30 @@
 mw.flow.ui.MWEditModeSourceTool = function VeUiMWEditModeSourceTool() {
        // Parent constructor
        mw.flow.ui.MWEditModeSourceTool.parent.apply( this, arguments );
-       // Mixin constructor
-       mw.flow.ui.MWEditModeTool.call( this );
 };
-OO.inheritClass( mw.flow.ui.MWEditModeSourceTool, 
mw.libs.ve.MWEditModeSourceTool );
-OO.mixinClass( mw.flow.ui.MWEditModeSourceTool, mw.flow.ui.MWEditModeTool );
-mw.flow.ui.MWEditModeSourceTool.prototype.onSelect = function () {
+
+OO.inheritClass( mw.flow.ui.MWEditModeSourceTool, ve.ui.MWEditModeSourceTool );
+
+mw.flow.ui.MWEditModeSourceTool.prototype.switch = function () {
+       var $editor = this.toolbar.getSurface().$element.closest( 
'.flow-editor' );
        if ( this.getMode() === 'visual' ) {
-               this.toolbar.surface.executeCommand( 'flowSwitchEditor' );
+               if ( $editor.length ) {
+                       // Old editor (FIXME kill the old editor and remove 
this)
+                       mw.flow.editor.switchEditor( $editor.find( 'textarea' 
), 'none' );
+               } else {
+                       // New editor
+                       this.toolbar.getTarget().switchMode();
+               }
        }
 };
+
+ve.ui.toolFactory.register( mw.flow.ui.MWEditModeSourceTool );
 
 /**
  * MediaWiki UserInterface edit mode visual tool.
  *
  * @class
  * @extends mw.libs.ve.MWEditModeVisualTool
- * @mixins mw.flow.ui.MWEditModeTool
  * @constructor
  * @param {OO.ui.ToolGroup} toolGroup
  * @param {Object} [config] Config options
@@ -62,8 +48,12 @@
 mw.flow.ui.MWEditModeVisualTool = function VeUiMWEditModeVisualTool() {
        // Parent constructor
        mw.flow.ui.MWEditModeVisualTool.parent.apply( this, arguments );
-       // Mixin constructor
-       mw.flow.ui.MWEditModeTool.call( this );
 };
-OO.inheritClass( mw.flow.ui.MWEditModeVisualTool, 
mw.libs.ve.MWEditModeVisualTool );
-OO.mixinClass( mw.flow.ui.MWEditModeVisualTool, mw.flow.ui.MWEditModeTool );
+
+OO.inheritClass( mw.flow.ui.MWEditModeVisualTool, ve.ui.MWEditModeVisualTool );
+
+mw.flow.ui.MWEditModeVisualTool.prototype.switch = function () {
+       this.toolbar.getTarget().switchMode();
+};
+
+ve.ui.toolFactory.register( mw.flow.ui.MWEditModeVisualTool );
diff --git 
a/modules/flow/ui/widgets/editor/editors/mw.flow.ui.AbstractEditorWidget.js 
b/modules/flow/ui/widgets/editor/editors/mw.flow.ui.AbstractEditorWidget.js
deleted file mode 100644
index 31daaef..0000000
--- a/modules/flow/ui/widgets/editor/editors/mw.flow.ui.AbstractEditorWidget.js
+++ /dev/null
@@ -1,209 +0,0 @@
-( function () {
-       /**
-        * Flow abstract editor widget.
-        *
-        * Subclasses must:
-        * - Set #static-name
-        * - Implement #getRawContent and #reloadContent
-        * - Emit #event-change and #event-switch
-        * - Respect static.switchable
-        *
-        * Subclasses probably need to:
-        * - Set #static-format
-        * - Implement #static-isSupported
-        * - Implement #destroy
-        * - Respect config.placeholder
-        *
-        * @class
-        * @extends OO.ui.Widget
-        * @abstract
-        *
-        * @constructor
-        * @param {Object} [config] Configuration options
-        * @cfg {string} [placeholder] Placeholder text for the edit area
-        */
-       mw.flow.ui.AbstractEditorWidget = function 
mwFlowUiAbstractEditorWidget( config ) {
-               config = config || {};
-
-               this.initialContent = null;
-
-               // Parent constructor
-               mw.flow.ui.AbstractEditorWidget.parent.call( this, config );
-       };
-
-       /* Initialization */
-
-       OO.inheritClass( mw.flow.ui.AbstractEditorWidget, OO.ui.Widget );
-
-       /* Events */
-
-       /**
-        * @event change
-        * Fired when the contents of the editor change.
-        */
-
-       /**
-        * @event switch
-        * @param {string} [editor] Symbolic name of the specific editor to 
switch to.
-        *
-        * Fired when the user expresses a desire to switch editors. If no 
specific editor
-        * name is passed in, or if there is no editor by the given name, 
EditorSwitcherWidget
-        * will decide which editor to switch to.
-        *
-        * Should not be emitted if static.switchable is false.
-        */
-
-       /* Static Methods */
-
-       /**
-        * Sets whether the user can switch to another editor
-        *
-        * @param {boolean} switchable
-        */
-       mw.flow.ui.AbstractEditorWidget.static.setSwitchable = function ( 
switchable ) {
-               this.switchable = switchable;
-       };
-
-       /* Static Properties */
-
-       /**
-        * Whether the user can switch to another editor type
-        */
-       mw.flow.ui.AbstractEditorWidget.static.switchable = false;
-
-       /**
-        * Type of content to use
-        *
-        * @static
-        * @inheritable
-        * @type {string}
-        */
-       mw.flow.ui.AbstractEditorWidget.static.format = null;
-
-       /**
-        * Name of this editor
-        *
-        * @static
-        * @inheritable
-        * @type {string}
-        */
-       mw.flow.ui.AbstractEditorWidget.static.name = null;
-
-       /**
-        * Check if the editor is supported
-        *
-        * @return {boolean} Editor is supported
-        */
-       mw.flow.ui.AbstractEditorWidget.static.isSupported = function () {
-               return true;
-       };
-
-       /* Methods */
-
-       /**
-        * Set up the editor.
-        *
-        * @method
-        * @abstract
-        * @param {string} content Content
-        * @return {jQuery.Promise} Promise resolved when setup is done, or 
rejected if it failed,
-        *   optionally with an error message.
-        */
-       mw.flow.ui.AbstractEditorWidget.prototype.setup = null;
-
-       /**
-        * Called immediately after the editor is attached to the DOM and made 
visible.
-        * Use this method only for things that rely on the editor being 
attached and visible;
-        * put other things in #setup.
-        */
-       mw.flow.ui.AbstractEditorWidget.prototype.afterAttach = function () {
-       };
-
-       /**
-        * Tear down the editor.
-        *
-        * @method
-        * @abstract
-        * @return {jQuery.Promise} Promise resolved when teardown is done, or 
rejected if it failed,
-        *   optionally with an error message.
-        */
-       mw.flow.ui.AbstractEditorWidget.prototype.teardown = null;
-
-       /**
-        * Get the content of the editor.
-        *
-        * @abstract
-        * @method
-        * @return {string} Content of the editor
-        */
-       mw.flow.ui.AbstractEditorWidget.prototype.getContent = null;
-
-       /**
-        * Change the content of the editor.
-        *
-        * @method
-        * @param {string} content New content
-        */
-       mw.flow.ui.AbstractEditorWidget.prototype.setContent = function ( 
content ) {
-               // Cache content for comparison
-               this.initialContent = content;
-       };
-
-       /**
-        * Focus on the editor.
-        *
-        * @abstract
-        * @method
-        */
-       mw.flow.ui.AbstractEditorWidget.prototype.focus = null;
-
-       /**
-        * Move the cursor to the end of the editor.
-        *
-        * @abstract
-        * @method
-        */
-       mw.flow.ui.AbstractEditorWidget.prototype.moveCursorToEnd = null;
-
-       /**
-        * Destroy the editor widget.
-        */
-       mw.flow.ui.AbstractEditorWidget.prototype.destroy = function () {
-       };
-
-       /**
-        * Get the format of the content in this editor
-        *
-        * @return {string} Content format
-        */
-       mw.flow.ui.AbstractEditorWidget.prototype.getFormat = function () {
-               return this.constructor.static.format;
-       };
-
-       /**
-        * Get the name of this editor
-        *
-        * @return {string} Editor name
-        */
-       mw.flow.ui.AbstractEditorWidget.prototype.getName = function () {
-               return this.constructor.static.name;
-       };
-
-       /**
-        * Check whether the editor is empty.
-        * @return {boolean} Editor is empty
-        * @localdoc Subclasses may override this with a more efficient 
implementation.
-        */
-       mw.flow.ui.AbstractEditorWidget.prototype.isEmpty = function () {
-               return !this.getContent();
-       };
-
-       /**
-        * Check if the information in the editor changed
-        * @return {boolean} Information has changed
-        */
-       mw.flow.ui.AbstractEditorWidget.prototype.hasBeenChanged = function () {
-               return this.initialContent !== this.getContent();
-       };
-
-}() );
diff --git 
a/modules/flow/ui/widgets/editor/editors/mw.flow.ui.VisualEditorWidget.js 
b/modules/flow/ui/widgets/editor/editors/mw.flow.ui.VisualEditorWidget.js
deleted file mode 100644
index 4f42383..0000000
--- a/modules/flow/ui/widgets/editor/editors/mw.flow.ui.VisualEditorWidget.js
+++ /dev/null
@@ -1,209 +0,0 @@
-( function ( $ ) {
-       /**
-        * Flow visualeditor editor widget
-        *
-        * @class
-        * @extends mw.flow.ui.AbstractEditorWidget
-        *
-        * @constructor
-        * @param {Object} [config] Configuration options
-        */
-       mw.flow.ui.VisualEditorWidget = function mwFlowUiVisualEditorWidget( 
config ) {
-               config = config || {};
-
-               // Parent constructor
-               mw.flow.ui.VisualEditorWidget.parent.call( this, config );
-
-               this.placeholder = config.placeholder;
-               this.loadPromise = null;
-
-               this.$element.addClass( 'flow-ui-visualEditorWidget' );
-       };
-
-       /* Initialization */
-
-       OO.inheritClass( mw.flow.ui.VisualEditorWidget, 
mw.flow.ui.AbstractEditorWidget );
-
-       /* Static Methods */
-
-       /**
-        * @inheritdoc
-        */
-       mw.flow.ui.VisualEditorWidget.static.format = 'html';
-
-       /**
-        * @inheritdoc
-        */
-       mw.flow.ui.VisualEditorWidget.static.name = 'visualeditor';
-
-       /**
-        * @inheritdoc
-        */
-       mw.flow.ui.VisualEditorWidget.static.isSupported = function () {
-               var isMobileTarget = ( mw.config.get( 'skin' ) === 'minerva' );
-
-               /* global VisualEditorSupportCheck */
-               return !!(
-                       !isMobileTarget &&
-                       mw.loader.getState( 'ext.visualEditor.core' ) &&
-                       mw.config.get( 'wgFlowEditorList' ).indexOf( 
'visualeditor' ) !== -1 &&
-                       window.VisualEditorSupportCheck && 
VisualEditorSupportCheck()
-               );
-       };
-
-       /* Methods */
-
-       /**
-        * Load the VisualEditor code and create this.target.
-        *
-        * It's safe to call this method multiple times, or to call it when 
loading is already
-        * complete: the same promise will be returned every time.
-        *
-        * @return {jQuery.Promise} Promise resolved when this.target has been 
created.
-        */
-       mw.flow.ui.VisualEditorWidget.prototype.load = function () {
-               var modules, widget = this;
-               if ( !this.loadPromise ) {
-                       modules = [ 'ext.flow.visualEditor' ].concat(
-                               mw.config.get( 'wgVisualEditorConfig' 
).pluginModules.filter( mw.loader.getState )
-                       );
-                       this.loadPromise = mw.loader.using( modules )
-                               .then( function () {
-                                       // HACK add i18n messages to VE
-                                       ve.init.platform.addMessages( 
mw.messages.get() );
-
-                                       mw.flow.ve.Target.static.setSwitchable( 
mw.flow.ui.VisualEditorWidget.static.switchable );
-
-                                       widget.target = 
ve.init.mw.targetFactory.create( 'flow' );
-                                       widget.$element.append( 
widget.target.$element );
-                               } );
-               }
-               return this.loadPromise;
-       };
-
-       /**
-        * Create a VE surface with the provided content in it.
-        *
-        * @param {string} content HTML to put in the surface (body only)
-        * @return {jQuery.Promise} Promise which resolves when the surface is 
ready
-        */
-       mw.flow.ui.VisualEditorWidget.prototype.createSurface = function ( 
content ) {
-               var widget = this,
-                       deferred = $.Deferred();
-
-               // loadHtml expects a full document, but we were only given the 
body content
-               this.target.loadHtml( '<body>' + content + '</body>' );
-               this.target.once( 'surfaceReady', function () {
-                       var surface = widget.target.getSurface();
-
-                       surface.setPlaceholder( widget.placeholder );
-                       // Relay events
-                       surface.getModel().connect( widget, { documentUpdate: [ 
'emit', 'change' ] } );
-                       surface.connect( widget, { switchEditor: [ 'emit', 
'switch' ] } );
-                       deferred.resolve();
-               } );
-               return deferred.promise();
-       };
-
-       /**
-        * @inheritdoc
-        */
-       mw.flow.ui.VisualEditorWidget.prototype.setup = function ( content ) {
-               return this.load().then( this.createSurface.bind( this, content 
) );
-       };
-
-       /**
-        * @inheritdoc
-        */
-       mw.flow.ui.VisualEditorWidget.prototype.teardown = function () {
-               this.target.clearSurfaces();
-               return $.Deferred().resolve().promise();
-       };
-
-       /**
-        * @inheritdoc
-        */
-       mw.flow.ui.VisualEditorWidget.prototype.focus = function () {
-               if ( this.target && this.target.getSurface() ) {
-                       this.target.getSurface().getView().focus();
-               }
-       };
-
-       /**
-        * @inheritdoc
-        */
-       mw.flow.ui.VisualEditorWidget.prototype.moveCursorToEnd = function () {
-               if ( this.target && this.target.getSurface() ) {
-                       
this.target.getSurface().getModel().selectLastContentOffset();
-               }
-       };
-
-       /**
-        * @inheritdoc
-        */
-       mw.flow.ui.VisualEditorWidget.prototype.getContent = function () {
-               var doc, html;
-
-               // If we haven't fully loaded yet, just return nothing.
-               if ( !this.target || !this.target.getSurface() ) {
-                       return '';
-               }
-
-               // get document from ve
-               doc = ve.dm.converter.getDomFromModel( 
this.target.getSurface().getModel().getDocument() );
-
-               // document content will include html, head & body nodes; get 
only content inside body node
-               html = ve.properInnerHtml( doc.body );
-               return html;
-       };
-
-       /**
-        * @inheritdoc
-        */
-       mw.flow.ui.VisualEditorWidget.prototype.setContent = function ( content 
) {
-               this.target.clearSurfaces();
-               this.createSurface( content );
-       };
-
-       /**
-        * @inheritdoc
-        */
-       mw.flow.ui.VisualEditorWidget.prototype.isEmpty = function () {
-               if ( !this.target || !this.target.getSurface() ) {
-                       return true;
-               }
-               return 
!this.target.getSurface().getModel().getDocument().data.hasContent();
-       };
-
-       /**
-        * Check if there are any changes made to the data in the editor
-        *
-        * @return {boolean} The original content has changed
-        */
-       mw.flow.ui.VisualEditorWidget.prototype.hasBeenChanged = function () {
-               // If we haven't fully loaded yet, just return false
-               if ( !this.target || !this.target.getSurface() ) {
-                       return false;
-               }
-
-               return this.target.getSurface().getModel().hasBeenModified();
-       };
-
-       mw.flow.ui.VisualEditorWidget.prototype.setDisabled = function ( 
disabled ) {
-               // Parent method
-               
mw.flow.ui.VisualEditorWidget.parent.prototype.setDisabled.call( this, disabled 
);
-
-               if ( this.target ) {
-                       this.target.setDisabled( !!disabled );
-               }
-       };
-
-       /**
-        * @inheritdoc
-        */
-       mw.flow.ui.VisualEditorWidget.prototype.destroy = function () {
-               if ( this.target ) {
-                       this.target.destroy();
-               }
-       };
-}( jQuery ) );
diff --git 
a/modules/flow/ui/widgets/editor/editors/mw.flow.ui.WikitextEditorWidget.js 
b/modules/flow/ui/widgets/editor/editors/mw.flow.ui.WikitextEditorWidget.js
deleted file mode 100644
index 7195efd..0000000
--- a/modules/flow/ui/widgets/editor/editors/mw.flow.ui.WikitextEditorWidget.js
+++ /dev/null
@@ -1,204 +0,0 @@
-( function ( $ ) {
-       /**
-        * Flow wikitext editor widget
-        *
-        * @class
-        * @extends mw.flow.ui.AbstractEditorWidget
-        *
-        * @constructor
-        * @param {Object} [config] Configuration options
-        */
-       mw.flow.ui.WikitextEditorWidget = function 
mwFlowUiWikitextEditorWidget( config ) {
-               var label, $message, $usesWikitext, $preview,
-                       widget = this;
-
-               config = config || {};
-
-               // Parent constructor
-               mw.flow.ui.WikitextEditorWidget.parent.call( this, config );
-
-               // Main textarea
-               this.input = new OO.ui.MultilineTextInputWidget( {
-                       autosize: true,
-                       maxRows: 999,
-                       placeholder: config.placeholder,
-                       // The following classes can be used here:
-                       // * mw-editfont-default
-                       // * mw-editfont-monospace
-                       // * mw-editfont-sans-serif
-                       // * mw-editfont-serif
-                       classes: [ 'flow-ui-wikitextEditorWidget-input', 
'mw-editfont-' + mw.user.options.get( 'editfont' ) ]
-               } );
-
-               // Label and switcher
-               $usesWikitext = $( '<div>' )
-                       .html( mw.message( 
'flow-wikitext-editor-help-uses-wikitext' ).parse() )
-                       .find( 'a' )
-                       .attr( 'target', '_blank' )
-                       .end();
-
-               if ( mw.flow.ui.WikitextEditorWidget.static.switchable ) {
-                       $preview = $( '<a>' )
-                               .attr( 'href', '#' )
-                               .addClass( 
'flow-ui-wikitextEditorWidget-label-preview' )
-                               .text( mw.message( 
'flow-wikitext-editor-help-preview-the-result' ).text() );
-
-                       $message = $( '<span>' ).append(
-                               mw.message( 
'flow-wikitext-editor-help-and-preview' ).params( [
-                                       $usesWikitext.html(),
-                                       $preview[ 0 ].outerHTML
-                               ] ).parse()
-                       );
-                       $message.find( 
'.flow-ui-wikitextEditorWidget-label-preview' )
-                               .on( 'click', function () {
-                                       widget.emit( 'switch' );
-                                       return false;
-                               } );
-
-                       mw.loader.using( 'ext.flow.switching', function () {
-                               // Toolbar
-                               var toolFactory = new OO.ui.ToolFactory(),
-                                       toolGroupFactory = new 
OO.ui.ToolGroupFactory();
-
-                               toolFactory.register( 
mw.flow.ui.MWEditModeVisualTool );
-                               toolFactory.register( 
mw.flow.ui.MWEditModeSourceTool );
-
-                               widget.toolbar = new OO.ui.Toolbar( 
toolFactory, toolGroupFactory, { position: 'bottom' } );
-                               // HACK: Disable narrow mode
-                               widget.toolbar.narrowThreshold = 0;
-                               widget.toolbar.setup( [ {
-                                       type: 'list',
-                                       icon: 'edit',
-                                       title: mw.msg( 
'visualeditor-mweditmode-tooltip' ),
-                                       include: [ 'editModeVisual', 
'editModeSource' ]
-                               } ] );
-                               widget.toolbar.emit( 'updateState' );
-
-                               widget.$element.append( widget.toolbar.$element 
);
-
-                               // Events
-                               widget.toolbar.on( 'switchEditor', function ( 
mode ) {
-                                       if ( mode === 'visual' ) {
-                                               widget.emit( 'switch' );
-                                       }
-                               } );
-                       } );
-               } else {
-                       $message = $( '<span>' ).append(
-                               mw.message( 'flow-wikitext-editor-help' 
).params( [
-                                       $usesWikitext.html()
-                               ] ).parse()
-                       );
-               }
-
-               // Label
-               label = new OO.ui.LabelWidget( {
-                       label: $message,
-                       classes: [ 'flow-ui-wikitextEditorWidget-label' ]
-               } );
-
-               // Events
-               this.input.connect( this, { change: [ 'emit', 'change' ] } );
-
-               // Initialize
-               this.$element
-                       .addClass( 'flow-ui-wikitextEditorWidget' )
-                       .prepend(
-                               this.input.$element,
-                               label.$element
-                       );
-       };
-
-       /* Initialization */
-
-       OO.inheritClass( mw.flow.ui.WikitextEditorWidget, 
mw.flow.ui.AbstractEditorWidget );
-
-       /* Static Methods */
-
-       /**
-        * @inheritdoc
-        */
-       mw.flow.ui.WikitextEditorWidget.static.format = 'wikitext';
-
-       /**
-        * @inheritdoc
-        */
-       mw.flow.ui.WikitextEditorWidget.static.name = 'wikitext';
-
-       /* Methods */
-
-       /**
-        * @inheritdoc
-        */
-       mw.flow.ui.WikitextEditorWidget.prototype.focus = function () {
-               this.input.focus();
-       };
-
-       /**
-        * @inheritdoc
-        */
-       mw.flow.ui.WikitextEditorWidget.prototype.moveCursorToEnd = function () 
{
-               this.input.moveCursorToEnd();
-       };
-
-       /**
-        * @inheritdoc
-        */
-       mw.flow.ui.WikitextEditorWidget.prototype.getContent = function () {
-               return this.input.getValue();
-       };
-
-       /**
-        * @inheritdoc
-        */
-       mw.flow.ui.WikitextEditorWidget.prototype.setContent = function ( 
content ) {
-               // Parent method
-               
mw.flow.ui.WikitextEditorWidget.parent.prototype.setContent.call( this, content 
);
-
-               this.input.setValue( content );
-       };
-
-       mw.flow.ui.WikitextEditorWidget.prototype.setDisabled = function ( 
disabled ) {
-               // Parent method
-               
mw.flow.ui.WikitextEditorWidget.parent.prototype.setDisabled.call( this, 
disabled );
-
-               if ( this.input ) {
-                       this.input.setDisabled( this.isDisabled() );
-               }
-       };
-
-       /**
-        * @inheritdoc
-        */
-       mw.flow.ui.WikitextEditorWidget.prototype.setup = function ( content ) {
-               this.setContent( content );
-               return $.Deferred().resolve().promise();
-       };
-
-       /**
-        * @inheritdoc
-        */
-       mw.flow.ui.WikitextEditorWidget.prototype.afterAttach = function () {
-               if ( this.toolbar ) {
-                       this.toolbar.initialize();
-                       this.toolbar.emit( 'updateState' );
-               }
-       };
-
-       /**
-        * @inheritdoc
-        */
-       mw.flow.ui.WikitextEditorWidget.prototype.teardown = function () {
-               this.input.setValue( '' );
-               return $.Deferred().resolve().promise();
-       };
-
-       /**
-        * @inheritdoc
-        */
-       mw.flow.ui.WikitextEditorWidget.prototype.destroy = function () {
-               if ( this.toolbar ) {
-                       this.toolbar.destroy();
-               }
-       };
-}( jQuery ) );
diff --git a/modules/flow/ui/widgets/editor/mw.flow.ui.EditorSwitcherWidget.js 
b/modules/flow/ui/widgets/editor/mw.flow.ui.EditorSwitcherWidget.js
deleted file mode 100644
index 5362c06..0000000
--- a/modules/flow/ui/widgets/editor/mw.flow.ui.EditorSwitcherWidget.js
+++ /dev/null
@@ -1,632 +0,0 @@
-( function ( $ ) {
-       /**
-        * Flow editor switcher widget
-        *
-        * @class
-        * @extends OO.ui.Widget
-        *
-        * @constructor
-        * @param {Object} [config] Configuration options
-        * @cfg {string} [placeholder] Placeholder text for the edit area
-        * @cfg {string} [content] Initial content
-        * @cfg {string} [contentFormat='wikitext'] Format of initial contents
-        * @cfg {string} [initialEditor] First editor to load. If not set, the 
first editor
-        *   will be the first one in wgFlowEditorList that matches the format 
of the provided
-        *   content (if any)
-        * @cfg {boolean} [autoFocus=true] Automatically focus after switching 
editors
-        * @cfg {boolean} [saveable=true] Initial state of saveable flag
-        */
-       mw.flow.ui.EditorSwitcherWidget = function 
mwFlowUiEditorSwitcherWidget( config ) {
-               var widget = this;
-               config = config || {};
-
-               // Parent constructor
-               mw.flow.ui.EditorSwitcherWidget.parent.call( this, config );
-
-               // Mixin constructors
-               OO.ui.mixin.PendingElement.call( this, config );
-
-               this.placeholder = config.placeholder || '';
-               this.initialContent = config.content || '';
-               this.contentFormat = config.contentFormat || 'wikitext';
-               this.toggleAutoFocus( config.autoFocus === undefined ? true : 
!!config.autoFocus );
-
-               this.activeEditorName = null;
-               this.editors = {};
-               this.allEditors = mw.config.get( 'wgFlowEditorList' )
-                       .map( function ( editor ) {
-                               // Map 'none' to 'wikitext' for backwards 
compatibility
-                               return editor === 'none' ? 'wikitext' : editor;
-                       } );
-               this.availableEditors = this.allEditors
-                       .filter( function ( editor, i, arr ) {
-                               return 
widget.constructor.static.editorDefinitions[ editor ].static.isSupported() &&
-                                       // remove all but the first occurence
-                                       arr.indexOf( editor ) === i;
-                       } );
-               this.initialEditorName =
-                       ( config.initialEditor === 'none' ? 'wikitext' : 
config.initialEditor ) ||
-                       this.getPreferredEditorName();
-               this.switchingPromise = null;
-               this.settingPromise = null;
-
-               this.placeholderInput = new OO.ui.MultilineTextInputWidget( {
-                       placeholder: this.placeholder,
-                       classes: [ 
'flow-ui-editorSwitcherWidget-placeholder-input' ]
-               } );
-               // The placeholder input is always pending, because it represnts
-               // a loading state
-               this.placeholderInput.pushPending();
-               this.placeholderInput.setDisabled( true );
-
-               this.error = new OO.ui.LabelWidget( {
-                       classes: [ 'flow-ui-editorSwitcherWidget-error 
flow-errors errorbox' ]
-               } );
-               this.error.toggle( false );
-
-               this.$element
-                       .on( 'focusin', this.onEditorFocusIn.bind( this ) )
-                       .on( 'focusout', this.onEditorFocusOut.bind( this ) );
-
-               // Initialize
-               this.$element
-                       .append( this.error.$element, 
this.placeholderInput.$element )
-                       .addClass( 'flow-ui-editorSwitcherWidget' );
-
-               this.toggleSaveable( config.saveable !== undefined ? 
config.saveable : true );
-       };
-
-       /* Initialization */
-
-       OO.inheritClass( mw.flow.ui.EditorSwitcherWidget, OO.ui.Widget );
-       OO.mixinClass( mw.flow.ui.EditorSwitcherWidget, 
OO.ui.mixin.PendingElement );
-
-       /* Events */
-
-       /**
-        * @event switch
-        *
-        * A `switch` event is emitted when the widget switches between 
different editors.
-        *
-        * @param {jQuery.Promise} switched Promise that is resolved when the 
switch is complete,
-        *   or rejected if the switch was aborted with an error.
-        * @param {string} newEditorName Name of the editor the widget is 
switching to
-        * @param {mw.flow.ui.AbstractEditorWidget|null} oldEditor Editor 
instance we're switching away from
-        */
-
-       /**
-        * @event change
-        * The contents of the editor changed.
-        */
-
-       /* Static Methods */
-
-       /**
-        * Define the available editors and their widget constructors
-        *
-        * @property {Object}
-        */
-       mw.flow.ui.EditorSwitcherWidget.static.editorDefinitions = {
-               wikitext: mw.flow.ui.WikitextEditorWidget,
-               visualeditor: mw.flow.ui.VisualEditorWidget
-       };
-
-       /* Methods */
-
-       /**
-        * Activate the switcher with the chosen editor.
-        *
-        * @return {jQuery.Promise} Promise resolved when editor switch is done
-        * @fires switch
-        */
-       mw.flow.ui.EditorSwitcherWidget.prototype.activate = function () {
-               return this.switchEditor( this.initialEditorName );
-       };
-
-       /**
-        * Get the editor that is currently active.
-        * @return {mw.flow.ui.AbstractEditorWidget|null} Active editor, if any
-        */
-       mw.flow.ui.EditorSwitcherWidget.prototype.getActiveEditor = function () 
{
-               return this.activeEditorName && this.editors[ 
this.activeEditorName ] || null;
-       };
-
-       /**
-        * Check whether an editor is currently active. This returns false 
before the first editor
-        * is loaded, and true after that. It also returns true if and editor 
switch is in progress.
-        * @return {boolean} Editor is active
-        */
-       mw.flow.ui.EditorSwitcherWidget.prototype.isActive = function () {
-               return !!this.activeEditorName || !!this.switchingPromise;
-       };
-
-       /**
-        * Check whether an editor is available.
-        * @param {string} editorName Name of editor
-        * @return {boolean} Editor is available
-        */
-       mw.flow.ui.EditorSwitcherWidget.prototype.isEditorAvailable = function 
( editorName ) {
-               return this.availableEditors.indexOf( editorName ) !== -1;
-       };
-
-       /**
-        * Get the next editor after the currently active one.
-        * @return {string} Name of the next editor
-        */
-       mw.flow.ui.EditorSwitcherWidget.prototype.getNextEditorName = function 
() {
-               return this.availableEditors[
-                       ( this.availableEditors.indexOf( this.activeEditorName 
) + 1 ) %
-                       this.availableEditors.length
-               ];
-       };
-
-       mw.flow.ui.EditorSwitcherWidget.prototype.getPreferredEditorName = 
function () {
-               var i, len;
-               if ( this.initialContent !== '' ) {
-                       // Find the first editor that matches this.contentFormat
-                       for ( i = 0, len = this.availableEditors.length; i < 
len; i++ ) {
-                               if ( this.constructor.static.editorDefinitions[ 
this.availableEditors[ i ] ].static.format === this.contentFormat ) {
-                                       return this.availableEditors[ i ];
-                               }
-                       }
-               }
-               // Either we didn't find an editor matching this.contentFormat, 
or we don't have any
-               // initial content and so we don't care. Use the first editor.
-               return this.availableEditors[ 0 ];
-       };
-
-       /**
-        * Get the format used by a given editor type.
-        * @param {string} name Editor name
-        * @return {string|null} Format used by that editor, or null if no such 
editor exists
-        */
-       mw.flow.ui.EditorSwitcherWidget.prototype.getEditorFormat = function ( 
name ) {
-               var definition = this.constructor.static.editorDefinitions[ 
name ];
-               return definition ? definition.static.format : null;
-       };
-
-       /**
-        * Create an editor and add it to this.editors.
-        *
-        * This method is safe to call multiple times. If the requested editor 
has already been
-        * created, the existing instance is returned.
-        *
-        * @param {string} name Editor name
-        * @return {mw.flow.ui.AbstractEditorWidget} Editor instance
-        */
-       mw.flow.ui.EditorSwitcherWidget.prototype.getEditor = function ( name ) 
{
-               if ( !this.editors[ name ] ) {
-                       this.constructor.static.editorDefinitions[ name 
].static.setSwitchable( this.isSwitchable() );
-                       this.editors[ name ] = new 
this.constructor.static.editorDefinitions[ name ]( {
-                               placeholder: this.placeholder
-                       } );
-                       this.editors[ name ].connect( this, {
-                               'switch': 'onEditorSwitch',
-                               change: [ 'onEditorChange', this.editors[ name 
] ]
-                       } );
-               }
-               return this.editors[ name ];
-       };
-
-       /**
-        * Switch to a different editor.
-        *
-        * This initializes the new editor (if needed), hides and detaches the 
old editor,
-        * attaches and shows the new editor, and loads the content from the 
old editor
-        * into the new editor.
-        *
-        * @param {string} name Name of editor to switch to.
-        * @return {jQuery.Promise} Promise resolved when editor switch is done
-        * @fires switch
-        */
-       mw.flow.ui.EditorSwitcherWidget.prototype.switchEditor = function ( 
name ) {
-               var promise, switchingDeferred,
-                       oldName, oldEditor, oldContent, oldFormat,
-                       newEditor, newFormat,
-                       widget = this;
-
-               if ( !this.isEditorAvailable( name ) ) {
-                       return $.Deferred().reject().promise();
-               }
-
-               if ( this.activeEditorName === name ) {
-                       return $.Deferred().resolve().promise();
-               }
-
-               if ( this.switchingPromise ) {
-                       if ( this.switchingPromise.newEditorName === name ) {
-                               return this.switchingPromise;
-                       } else {
-                               // TODO handle this more gracefully
-                               return $.Deferred().reject().promise();
-                       }
-               }
-
-               if ( this.settingPromise ) {
-                       // TODO handle this more gracefully
-                       return $.Deferred().reject().promise();
-               }
-
-               switchingDeferred = $.Deferred();
-               oldName = this.activeEditorName;
-               oldEditor = this.getActiveEditor();
-               oldContent = oldEditor && oldEditor.getContent() || 
this.initialContent;
-               oldFormat = this.contentFormat;
-               newEditor = this.getEditor( name );
-               newFormat = newEditor.getFormat();
-
-               this.switchingPromise = promise = switchingDeferred.promise();
-               this.switchingPromise.newEditorName = name;
-
-               this.pushPending();
-               this.error.toggle( false );
-               if ( oldEditor ) {
-                       oldEditor.setDisabled( true );
-               }
-               this.activeEditorName = null;
-               this.convertContent( oldContent, oldFormat, newFormat )
-                       .then( function ( newContent ) {
-                               // Set up the new editor, but keep the old 
editor visible
-                               return newEditor.setup( newContent );
-                       } )
-                       .then( function () {
-                               // Detach the current editor if it exists
-                               if ( oldEditor ) {
-                                       return oldEditor.teardown()
-                                               .then( function () {
-                                                       
oldEditor.$element.detach();
-                                               } );
-                               }
-                       } )
-                       .then( function () {
-                               // Check .autoFocus now, in case handlers for 
switchingDeferred change it
-                               var shouldFocus = widget.autoFocus;
-
-                               widget.activeEditorName = name;
-                               widget.contentFormat = newFormat;
-
-                               widget.popPending();
-                               newEditor.setDisabled( widget.isDisabled() );
-
-                               widget.placeholderInput.$element.detach();
-                               widget.$element.append( newEditor.$element );
-
-                               newEditor.afterAttach();
-
-                               switchingDeferred.resolve();
-                               widget.switchingPromise = null;
-
-                               if ( shouldFocus ) {
-                                       // Defer in case user event which 
triggered switch is
-                                       // a focus-stealing one.
-                                       setTimeout( function () {
-                                               newEditor.focus();
-                                               newEditor.moveCursorToEnd();
-                                       } );
-                               }
-                       } )
-                       .fail( function ( error ) {
-                               widget.activeEditorName = oldName;
-                               switchingDeferred.reject();
-                               widget.switchingPromise = null;
-                               widget.popPending();
-                               if ( oldEditor ) {
-                                       oldEditor.setDisabled( 
widget.isDisabled() );
-                               }
-
-                               widget.error.setLabel( $( '<span>' ).text( 
error || mw.msg( 'flow-error-default' ) ) );
-                               widget.error.toggle( true );
-                       } );
-
-               this.emit( 'switch', promise, name, oldEditor );
-               return promise;
-       };
-
-       /**
-        * Convert content from one format to another.
-        *
-        * It's safe to call this function with fromFormat=toFormat: in that 
case, the returned promise
-        * will be resolved immediately with the original content.
-        *
-        * @param {string} content Content in old format
-        * @param {string} fromFormat Old format name
-        * @param {string} toFormat New format name
-        * @return {jQuery.Promise} Promise resolved with content converted to 
new format
-        */
-       mw.flow.ui.EditorSwitcherWidget.prototype.convertContent = function ( 
content, fromFormat, toFormat ) {
-               if ( fromFormat === toFormat || content === '' ) {
-                       return $.Deferred().resolve( content ).promise();
-               }
-
-               return new mw.Api().post( {
-                       action: 'flow-parsoid-utils',
-                       from: fromFormat,
-                       to: toFormat,
-                       content: content,
-                       title: mw.config.get( 'wgPageName' )
-               } )
-                       .then( function ( data ) {
-                               return data[ 'flow-parsoid-utils' ].content;
-                       }, function () {
-                               return mw.msg( 'flow-error-parsoid-failure' );
-                       } );
-       };
-
-       /**
-        * Get the content of the editor.
-        * @return {string|null} Content of the editor, or null if no editor is 
active.
-        */
-       mw.flow.ui.EditorSwitcherWidget.prototype.getContent = function () {
-               return this.getActiveEditor() ? 
this.getActiveEditor.getContent() : null;
-       };
-
-       /**
-        * Get the format of the content returned by #getContent.
-        * @return {string|null} Content format, or null if no editor is active.
-        */
-       mw.flow.ui.EditorSwitcherWidget.prototype.getContentFormat = function 
() {
-               return this.getActiveEditor() ? 
this.getActiveEditor().getFormat() : null;
-       };
-
-       /**
-        * Check whether the editor is empty. If no editor is active, that is 
also considered empty.
-        * @return {boolean} Editor is empty
-        */
-       mw.flow.ui.EditorSwitcherWidget.prototype.isEmpty = function () {
-               return this.getActiveEditor() ? 
this.getActiveEditor().isEmpty() : true;
-       };
-
-       /**
-        * Change the content in the editor.
-        *
-        * If there is an active editor, the provided content will be converted 
to its format
-        * and then be put into that editor.
-        *
-        * If an editor switch is in progress, this method will
-        * wait for that switch to complete, then convert the provided content 
to the new editor's
-        * format and put the content into the new editor.
-        *
-        * If no editor has been loaded yet, this method will cause the 
provided content to be used
-        * when the first editor is loaded.
-        *
-        * @param {string} content New content to set
-        * @param {string} contentFormat Format of new content
-        * @return {jQuery.Promise} Promise resolved when new content has been 
set
-        */
-       mw.flow.ui.EditorSwitcherWidget.prototype.setContent = function ( 
content, contentFormat ) {
-               var promise, settingDeferred,
-                       widget = this;
-
-               if ( this.settingPromise ) {
-                       // TODO handle this more gracefully
-                       return $.Deferred().reject();
-               }
-
-               if ( !this.switchingPromise && !this.getActiveEditor() ) {
-                       // There is no active editor, and no editor is loading, 
so we can simply change
-                       // our state variables and they'll be picked up when 
the first editor is loaded.
-                       this.initialContent = content;
-                       this.contentFormat = contentFormat;
-                       return $.Deferred().resolve().promise();
-               }
-
-               settingDeferred = $.Deferred();
-
-               // Setting this.settingPromise prevents switchEditor() from 
changing the active editor
-               // while we convert
-               this.settingPromise = promise = settingDeferred.promise();
-               $.when( this.switchingPromise )
-                       .then( function () {
-                               return widget.convertContent( content, 
contentFormat, widget.contentFormat );
-                       } )
-                       .then( function ( newContent ) {
-                               widget.getActiveEditor().setContent( newContent 
);
-                               settingDeferred.resolve();
-                               widget.settingPromise = null;
-                       } )
-                       .fail( function ( error ) {
-                               settingDeferred.reject();
-                               widget.settingPromise = null;
-
-                               widget.error.setLabel( $( '<span>' ).text( 
error || mw.msg( 'flow-error-default' ) ) );
-                               widget.error.toggle( true );
-                       } );
-               return promise;
-       };
-
-       /**
-        * Clear the content of the current editor without triggering
-        * a change to the 'flow-editor' user preference.
-        */
-       mw.flow.ui.EditorSwitcherWidget.prototype.clearContent = function () {
-               var editor = this.getActiveEditor(),
-                       oldActiveEditorName = this.activeEditorName;
-
-               // hack: blanking activeEditorName so the change event is 
ignored
-               this.activeEditorName = null;
-               editor.setContent( '' );
-               this.activeEditorName = oldActiveEditorName;
-       };
-
-       /**
-        * Toggle whether the editor is automatically focused after switching.
-        * @param {boolean} [autoFocus] Whether to focus automatically; if 
unset, flips current value
-        */
-       mw.flow.ui.EditorSwitcherWidget.prototype.toggleAutoFocus = function ( 
autoFocus ) {
-               this.autoFocus = autoFocus === undefined ? !this.autoFocus : 
!!autoFocus;
-       };
-
-       /**
-        * Respond to change events from an editor
-        *
-        * @private
-        * @param {mw.flow.ui.AbstractEditorWidget} editor Editor that fired 
the change event
-        */
-       mw.flow.ui.EditorSwitcherWidget.prototype.onEditorChange = function ( 
editor ) {
-               var name = editor.getName(),
-                       currentPref = mw.user.options.get( 'flow-editor' );
-               if ( name !== this.activeEditorName ) {
-                       // Ignore change events from non-active editors
-                       return;
-               }
-
-               this.emit( 'change' );
-
-               // Normalize 'none' to 'wikitext'
-               if ( currentPref === 'none' ) {
-                       currentPref = 'wikitext';
-               }
-
-               // If the user's preferred editor exists but is not supported 
right now,
-               // don't change their preference (T110706)
-               if (
-                       this.availableEditors.indexOf( currentPref ) === -1 &&
-                       this.allEditors.indexOf( currentPref ) !== -1
-               ) {
-                       return;
-               }
-
-               if ( currentPref !== name ) {
-                       if ( !mw.user.isAnon() ) {
-                               new mw.Api().saveOption( 'flow-editor', name );
-                       }
-                       // Ensure we also see that preference in the current 
page
-                       mw.user.options.set( 'flow-editor', name );
-               }
-       };
-
-       /**
-        * Respond to editor switch event. Switch either to the requested 
editor or
-        * to the next editor available.
-        *
-        * @private
-        * @param {string} [name] The key of the requested editor
-        */
-       mw.flow.ui.EditorSwitcherWidget.prototype.onEditorSwitch = function ( 
name ) {
-               if ( !this.isSwitchable() ) {
-                       return;
-               }
-               if ( !name ) {
-                       name = this.getNextEditorName();
-               }
-               this.switchEditor( name );
-       };
-
-       /**
-        * Respond to focusin event
-        */
-       mw.flow.ui.EditorSwitcherWidget.prototype.onEditorFocusIn = function () 
{
-               this.$element.addClass( 'flow-ui-editorSwitcherWidget-focused' 
);
-       };
-
-       /**
-        * Respond to focusout event
-        */
-       mw.flow.ui.EditorSwitcherWidget.prototype.onEditorFocusOut = function 
() {
-               this.$element.removeClass( 
'flow-ui-editorSwitcherWidget-focused' );
-       };
-
-       /**
-        * Focus on the editor.
-        */
-       mw.flow.ui.EditorSwitcherWidget.prototype.focus = function () {
-               if ( this.getActiveEditor() ) {
-                       this.getActiveEditor().focus();
-               }
-       };
-
-       /**
-        * Check if the widget is switchable. If there is only one editor 
available
-        * then switching is not possible.
-        *
-        * @return {boolean} Editors are switchable
-        */
-       mw.flow.ui.EditorSwitcherWidget.prototype.isSwitchable = function () {
-               return mw.config.get( 'wgIsProbablyEditable' ) && 
this.availableEditors.length > 1;
-       };
-
-       mw.flow.ui.EditorSwitcherWidget.prototype.isDisabled = function () {
-               // Auto-disable when pending or not saveable
-               return this.isPending() ||
-                       !this.isSaveable() ||
-                       // Parent method
-                       
mw.flow.ui.EditorSwitcherWidget.parent.prototype.isDisabled.apply( this, 
arguments );
-       };
-
-       /**
-        * Toggle a disable state for the widget
-        *
-        * @param {boolean} disabled Widget is disabled
-        */
-       mw.flow.ui.EditorSwitcherWidget.prototype.setDisabled = function ( 
disabled ) {
-               var activeEditor = this.getActiveEditor();
-
-               // Parent method
-               
mw.flow.ui.EditorSwitcherWidget.parent.prototype.setDisabled.call( this, 
disabled );
-
-               if ( this.placeholderInput ) {
-                       this.placeholderInput.setDisabled( this.isDisabled() );
-               }
-               if ( activeEditor ) {
-                       activeEditor.setDisabled( this.isDisabled() );
-               }
-       };
-
-       /**
-        * Check whether editor is saveable
-        *
-        * @return {boolean} Whether the user can save their content
-        */
-       mw.flow.ui.EditorSwitcherWidget.prototype.isSaveable = function () {
-               return this.saveable;
-       };
-
-       /**
-        * Toggle whether the editor is saveable
-        *
-        * @param {boolean} [saveable] Whether the editor is saveable
-        */
-       mw.flow.ui.EditorSwitcherWidget.prototype.toggleSaveable = function ( 
saveable ) {
-               this.saveable = saveable === undefined ? !this.saveable : 
!!saveable;
-
-               // Disabled state depends on saveable state
-               this.updateDisabled();
-       };
-
-       mw.flow.ui.EditorSwitcherWidget.prototype.pushPending = function () {
-               // Parent method
-               OO.ui.mixin.PendingElement.prototype.pushPending.apply( this, 
arguments );
-
-               // Disabled state depends on pending state
-               this.updateDisabled();
-       };
-
-       mw.flow.ui.EditorSwitcherWidget.prototype.popPending = function () {
-               // Parent method
-               OO.ui.mixin.PendingElement.prototype.popPending.apply( this, 
arguments );
-
-               // Disabled state depends on pending state
-               this.updateDisabled();
-       };
-
-       /**
-        * Check if there are any changes made to the data in the editor
-        *
-        * @return {boolean} The original content has changed
-        */
-       mw.flow.ui.EditorSwitcherWidget.prototype.hasBeenChanged = function () {
-               var editor = this.getActiveEditor();
-               return editor && editor.hasBeenChanged();
-       };
-       /**
-        * Destroy the widget
-        */
-       mw.flow.ui.EditorSwitcherWidget.prototype.destroy = function () {
-               var editor;
-               for ( editor in this.editors ) {
-                       this.editors[ editor ].disconnect( this );
-                       this.editors[ editor ].destroy();
-               }
-       };
-
-}( jQuery ) );
diff --git a/modules/flow/ui/widgets/editor/mw.flow.ui.EditorWidget.js 
b/modules/flow/ui/widgets/editor/mw.flow.ui.EditorWidget.js
index 7649df7..aa629e4 100644
--- a/modules/flow/ui/widgets/editor/mw.flow.ui.EditorWidget.js
+++ b/modules/flow/ui/widgets/editor/mw.flow.ui.EditorWidget.js
@@ -7,8 +7,6 @@
         *
         * @constructor
         * @param {Object} [config] Configuration options
-        * @cfg {string} [content] An initial content for the textarea
-        * @cfg {string} [contentFormat] Format of config.content
         * @cfg {string} [placeholder] Placeholder text to use for the editor 
when empty
         * @cfg {string} [termsMsgKey='flow-terms-of-use-edit'] i18n message 
key for the footer message
         * @cfg {string} [saveMsgKey='flow-newtopic-save'] i18n message key for 
the save button
@@ -34,11 +32,21 @@
                // Mixin constructors
                OO.ui.mixin.PendingElement.call( this, config );
 
+               this.placeholder = config.placeholder || '';
                this.confirmCancel = !!config.confirmCancel || 
config.cancelOnEscape === undefined;
                this.confirmLeave = !!config.confirmLeave || 
config.confirmLeave === undefined;
                this.leaveCallback = config.leaveCallback;
 
-               this.saveable = config.saveable !== undefined ? config.saveable 
: true;
+               this.loadPromise = null;
+
+               this.error = new OO.ui.LabelWidget( {
+                       classes: [ 'flow-ui-editorWidget-error flow-errors 
errorbox' ]
+               } );
+               this.error.toggle( false );
+
+               this.$element
+                       .on( 'focusin', this.onEditorFocusIn.bind( this ) )
+                       .on( 'focusout', this.onEditorFocusOut.bind( this ) );
 
                this.editorControlsWidget = new 
mw.flow.ui.EditorControlsWidget( {
                        termsMsgKey: config.termsMsgKey || 
'flow-terms-of-use-edit',
@@ -47,21 +55,42 @@
                        saveable: this.saveable
                } );
 
-               this.editorSwitcherWidget = new 
mw.flow.ui.EditorSwitcherWidget( {
-                       autoFocus: config.autoFocus,
-                       content: config.content,
-                       contentFormat: config.contentFormat,
-                       placeholder: config.placeholder,
-                       saveable: this.saveable
+               this.wikitextHelpLabel = new OO.ui.LabelWidget( {
+                       classes: [ 'flow-ui-editorWidget-wikitextHelpLabel' ],
+                       label: $( '<span>' ).append(
+                                       mw.message( 
'flow-wikitext-editor-help-and-preview' ).params( [
+                                               // Link to help page
+                                               $( '<div>' )
+                                                       .html( mw.message( 
'flow-wikitext-editor-help-uses-wikitext' ).parse() )
+                                                       .find( 'a' )
+                                                       .attr( 'target', 
'_blank' )
+                                                       .end()
+                                                       .html(),
+                                               // Preview link
+                                               $( '<a>' )
+                                                       .attr( 'href', '#' )
+                                                       .addClass( 
'flow-ui-wikitextEditorWidget-label-preview' )
+                                                       .text( mw.message( 
'flow-wikitext-editor-help-preview-the-result' ).text() )
+                                                       .get( 0 ).outerHTML
+                                       ] ).parse()
+                               )
+                               .find( 
'.flow-ui-wikitextEditorWidget-label-preview' )
+                               .click( this.onPreviewLinkClick.bind( this ) )
+                               .end()
                } );
+               this.wikitextHelpLabel.toggle( false );
 
-               this.setPendingElement( this.editorSwitcherWidget.$element );
+               this.$editorWrapper = $( '<div>' )
+                       .addClass( 'flow-ui-editorWidget-editor' )
+                       .append( this.wikitextHelpLabel.$element );
+               this.setPendingElement( this.$editorWrapper );
+
+               this.toggleAutoFocus( config.autoFocus === undefined ? true : 
!!config.autoFocus );
+               this.toggleSaveable( config.saveable !== undefined ? 
config.saveable : true );
+
+               // TODO need textarea fallback for when VE is not supported / 
not available
 
                // Events
-               this.editorSwitcherWidget.connect( this, {
-                       'switch': 'onEditorSwitcherSwitch',
-                       change: [ 'emit', 'change' ]
-               } );
                this.editorControlsWidget.connect( this, {
                        cancel: 'onEditorControlsWidgetCancel',
                        save: 'onEditorControlsWidgetSave'
@@ -79,7 +108,8 @@
 
                this.$element
                        .append(
-                               this.editorSwitcherWidget.$element,
+                               this.$editorWrapper,
+                               this.error.$element,
                                this.editorControlsWidget.$element
                        )
                        .addClass( 'flow-ui-editorWidget' );
@@ -90,7 +120,7 @@
        /**
         * @event saveContent
         * @param {string} content Content to save
-        * @param {string} contentFormat Format of content
+        * @param {string} format Format of content ('html' or 'wikitext')
         */
 
        /**
@@ -103,38 +133,210 @@
         * The contents of the editor changed.
         */
 
-       /**
-        * @event switch
-        *
-        * A `switch` event is emitted when the widget switches between 
different editors.
-        *
-        * @param {jQuery.Promise} switched Promise that is resolved when the 
switch is complete,
-        *   or rejected if the switch was aborted with an error.
-        * @param {string} newEditorName Name of the editor the widget is 
switching to
-        * @param {mw.flow.ui.AbstractEditorWidget|null} oldEditor Editor 
instance we're switching away from
-        */
-
        /* Initialization */
 
        OO.inheritClass( mw.flow.ui.EditorWidget, OO.ui.Widget );
        OO.mixinClass( mw.flow.ui.EditorWidget, OO.ui.mixin.PendingElement );
 
-       // TODO figure out a way not to have to wrap so many things in 
EditorSwitcherWidget; maybe
-       // merge EditorSwitcherWidget into EditorWidget?
+       /**
+        * Load the VisualEditor code and create this.target.
+        *
+        * Calling this method externally can be useful to preload 
VisualEditor, but is not functionally
+        * necessary. #activate calls this method as well.
+        *
+        * It's safe to call this method multiple times, or to call it when 
loading is already
+        * complete: the same promise will be returned every time.
+        *
+        * @return {jQuery.Promise} Promise resolved when this.target has been 
created.
+        */
+       mw.flow.ui.EditorWidget.prototype.load = function () {
+               var modules, widget = this;
+               if ( !this.loadPromise ) {
+                       modules = [ 'ext.flow.visualEditor' ].concat(
+                               mw.config.get( 'wgVisualEditorConfig' 
).pluginModules.filter( mw.loader.getState )
+                       );
+                       this.loadPromise = mw.loader.using( modules )
+                               .then( function () {
+                                       // HACK add i18n messages to VE
+                                       ve.init.platform.addMessages( 
mw.messages.get() );
+
+                                       widget.target = 
ve.init.mw.targetFactory.create( 'flow' );
+                                       widget.target.connect( widget, {
+                                               surfaceReady: 
'onTargetSurfaceReady',
+                                               switchMode: 'onTargetSwitchMode'
+                                       } );
+                                       widget.$editorWrapper.prepend( 
widget.target.$element );
+                               } );
+               }
+               return this.loadPromise;
+       };
+
+       /**
+        * Activate the editor.
+        *
+        * @param {Object} [content] Content to preload into the editor
+        * @param {string} content.content Content
+        * @param {string} content.format Format of content ('html' or 
'wikitext')
+        * @return {jQuery.Promise}
+        */
+       mw.flow.ui.EditorWidget.prototype.activate = function ( content ) {
+               var widget = this;
+               this.pushPending();
+               this.error.toggle( false );
+               return this.load()
+                       .then( this.createSurface.bind( this, content ) )
+                       .then( function () {
+                               widget.bindBeforeUnloadHandler();
+                               if ( widget.autoFocus ) {
+                                       widget.focus();
+                                       widget.moveCursorToEnd();
+                               }
+                               widget.wikitextHelpLabel.toggle( 
widget.target.getDefaultMode() === 'source' );
+                       }, function ( error ) {
+                               widget.error.setLabel( $( '<span>' ).text( 
error || mw.msg( 'flow-error-default' ) ) );
+                               widget.error.toggle( true );
+                       } )
+                       .always( function () {
+                               widget.popPending();
+                       } );
+       };
+
+       /**
+        * Create a VE surface with the provided content in it.
+        *
+        * @private
+        * @param {Object} content Content to put into the surface
+        * @param {string} content.content Content
+        * @param {string} content.format Format of content ('html' or 
'wikitext')
+        * @return {jQuery.Promise} Promise which resolves when the surface is 
ready
+        */
+       mw.flow.ui.EditorWidget.prototype.createSurface = function ( content ) {
+               var contentToLoad,
+                       contentFormat,
+                       deferred = $.Deferred();
+
+               if ( content ) {
+                       contentToLoad = content.content;
+                       if ( content.format === 'html' ) {
+                               // loadContent expects a full document, but we 
were only given the body content
+                               contentToLoad = '<body>' + contentToLoad + 
'</body>';
+                       }
+                       contentFormat = content.format;
+               } else {
+                       contentToLoad = '';
+                       contentFormat = this.getPreferredFormat();
+               }
+               this.target.setDefaultMode( contentFormat === 'html' ? 'visual' 
: 'source' );
+               this.target.loadContent( contentToLoad );
+               this.target.once( 'surfaceReady', function () {
+                       deferred.resolve();
+               } );
+               return deferred.promise();
+       };
+
+       /**
+        * Toggle whether the editor is automatically focused after switching.
+        * @param {boolean} [autoFocus] Whether to focus automatically; if 
unset, flips current value
+        */
+       mw.flow.ui.EditorWidget.prototype.toggleAutoFocus = function ( 
autoFocus ) {
+               this.autoFocus = autoFocus === undefined ? !this.autoFocus : 
!!autoFocus;
+       };
+
+       /**
+        * Toggle whether the editor is saveable,
+        *
+        * @param {boolean} [saveable] Whether the editor is saveable
+        */
+       mw.flow.ui.EditorWidget.prototype.toggleSaveable = function ( saveable 
) {
+               this.saveable = saveable === undefined ? !this.saveable : 
!!saveable;
+
+               // Disabled state depends on saveable state
+               this.updateDisabled();
+               // Update controls widget
+               this.editorControlsWidget.toggleSaveable( this.saveable );
+       };
+
+       /**
+        * Check whether the editor is saveable.
+        *
+        * @return {boolean} Whether the user can save their content
+        */
+       mw.flow.ui.EditorWidget.prototype.isSaveable = function () {
+               return this.saveable;
+       };
+
+       /**
+        * Respond to focusin event.
+        *
+        * @private
+        */
+       mw.flow.ui.EditorWidget.prototype.onEditorFocusIn = function () {
+               this.$element.addClass( 'flow-ui-editorWidget-focused' );
+       };
+
+       /**
+        * Respond to focusout event.
+        *
+        * @private
+        */
+       mw.flow.ui.EditorWidget.prototype.onEditorFocusOut = function () {
+               this.$element.removeClass( 'flow-ui-editorWidget-focused' );
+       };
+
+       mw.flow.ui.EditorWidget.prototype.onPreviewLinkClick = function () {
+               this.target.switchMode();
+               return false;
+       };
+
+       /**
+        * Set up event listeners when a new surface is created. This happens 
every time we
+        * switch modes.
+        *
+        * @private
+        */
+       mw.flow.ui.EditorWidget.prototype.onTargetSurfaceReady = function () {
+               var surface = this.target.getSurface();
+
+               surface.setPlaceholder( this.placeholder );
+               surface.getModel().connect( this, { documentUpdate: [ 
'onSurfaceDocumentUpdate' ] } );
+       };
+
+       /**
+        * Every time the editor content changes, update the user's mode 
preference if necessary,
+        * and emit 'change'.
+        *
+        * @private
+        * @fires change
+        */
+       mw.flow.ui.EditorWidget.prototype.onSurfaceDocumentUpdate = function () 
{
+               // Update the user's preferred editor
+               var currentEditor = this.target.getDefaultMode() === 'source' ? 
'wikitext' : 'visualeditor';
+               if ( mw.user.options.get( 'flow-editor' ) !== currentEditor ) {
+                       if ( !mw.user.isAnon() ) {
+                               new mw.Api().saveOption( 'flow-editor', 
currentEditor );
+                       }
+                       // Ensure we also see that preference in the current 
page
+                       mw.user.options.set( 'flow-editor', currentEditor );
+               }
+
+               this.emit( 'change' );
+       };
 
        /**
         * Respond to cancel event. Verify with the user that they want to 
cancel if
         * there is changed data in the editor.
+        *
+        * @private
         * @fires cancel
         */
        mw.flow.ui.EditorWidget.prototype.onEditorControlsWidgetCancel = 
function () {
                var widget = this;
 
-               if ( this.confirmCancel && 
this.editorSwitcherWidget.hasBeenChanged() ) {
+               if ( this.confirmCancel && this.hasBeenChanged() ) {
                        mw.flow.ui.windowManager.openWindow( 'cancelconfirm' 
).closed.then( function ( data ) {
                                if ( data && data.action === 'discard' ) {
                                        // Remove content
-                                       widget.setContent( '', 'wikitext' );
+                                       widget.clearContent();
                                        widget.unbindBeforeUnloadHandler();
                                        widget.emit( 'cancel' );
                                }
@@ -146,109 +348,112 @@
        };
 
        /**
-        * Check whether an editor is currently active. This returns false 
before the first editor
-        * is loaded, and true after that. It also returns true if and editor 
switch is in progress.
-        * @return {boolean} Editor is active
-        */
-       mw.flow.ui.EditorWidget.prototype.isActive = function () {
-               return this.editorSwitcherWidget.isActive();
-       };
-
-       /**
         * Get the content of the editor.
-        * @return {string|null} Content of the editor, or null if no editor is 
active.
+        *
+        * @return {Object}
+        * @return {string} return.content Content of the editor
+        * @return {string} return.format 'html' or 'wikitext'
         */
        mw.flow.ui.EditorWidget.prototype.getContent = function () {
-               return this.editorSwitcherWidget.getContent();
+               var dom, content, format;
+
+               // If we haven't fully loaded yet, just return nothing.
+               if ( !this.target || !this.target.getSurface() ) {
+                       return '';
+               }
+
+               dom = this.target.getSurface().getDom();
+               if ( typeof dom === 'string' ) {
+                       content = dom;
+                       format = 'wikitext';
+               } else {
+                       // Document content will include html, head & body 
nodes; get only content inside body node
+                       content = ve.properInnerHtml( dom.body );
+                       format = 'html';
+               }
+               return { content: content, format: format };
        };
 
        /**
-        * Get the format of the content returned by #getContent.
-        * @return {string|null} Content format, or null if no editor is active.
-        */
-       mw.flow.ui.EditorWidget.prototype.getContentFormat = function () {
-               return this.editorSwitcherWidget.getContentFormat();
-       };
-
-       /**
-        * Check whether the editor is empty. If no editor is active, that is 
also considered empty.
+        * Check whether the editor is empty. Also returns true if the editor 
hasn't been loaded yet.
+        *
         * @return {boolean} Editor is empty
         */
        mw.flow.ui.EditorWidget.prototype.isEmpty = function () {
-               return this.editorSwitcherWidget.isEmpty();
+               if ( !this.target || !this.target.getSurface() ) {
+                       return true;
+               }
+               return 
!this.target.getSurface().getModel().getDocument().data.hasContent();
        };
 
        /**
-        * Get the name of the editor that would be loaded if this widget were 
to be
-        * activated right now.
+        * Check if there are any changes made to the data in the editor.
         *
-        * Note that the return value of this function can change over time as 
the user switches
-        * editors in different EditorWidgets. The editor that is actually 
loaded when activating
-        * is determined by calling this function at activation time, no 
earlier.
-        *
-        * @return {string} Name of initial editor that will be used
+        * @return {boolean} The original content has changed
         */
-       mw.flow.ui.EditorWidget.prototype.getPreferredEditorName = function () {
-               return mw.user.options.get( 'flow-editor' );
+       mw.flow.ui.EditorWidget.prototype.hasBeenChanged = function () {
+               return this.target && 
this.target.getSurface().getModel().hasBeenModified();
        };
 
        /**
-        * Get the format of the editor that would be loaded if this widget 
were to be
-        * activated right now.
-        * @return {string|null} Format used by initial editor, or null if no 
editor is active
-        * @see #getPreferredEditorName
+        * Get the format the user prefers.
+        *
+        * @return {string} 'html' or 'wikitext'
         */
        mw.flow.ui.EditorWidget.prototype.getPreferredFormat = function () {
-               return this.editorSwitcherWidget.getEditorFormat( 
this.getPreferredEditorName() );
+               return mw.user.options.get( 'flow-editor' ) === 'visualeditor' 
? 'html' : 'wikitext';
        };
 
        /**
-        * Toggle whether the editor is automatically focused after switching.
-        * @param {boolean} [autoFocus] Whether to focus automatically; if 
unset, flips current value
-        */
-       mw.flow.ui.EditorWidget.prototype.toggleAutoFocus = function ( 
autoFocus ) {
-               this.editorSwitcherWidget.toggleAutoFocus( autoFocus );
-       };
-
-       /**
-        * Change the content in the editor
-        * @param {string} content New content to set
-        * @param {string} contentFormat Format of new content
-        * @return {jQuery.Promise} Promise resolved when new content has been 
set
-        * @see mw.flow.ui.EditorSwitcherWidget#setContent
-        */
-       mw.flow.ui.EditorWidget.prototype.setContent = function ( content, 
contentFormat ) {
-               return this.editorSwitcherWidget.setContent( content, 
contentFormat );
-       };
-
-       /**
-        * Make this widget pending while switching editors.
+        * Make this widget pending while switching editor modes, and refocus 
the editor when
+        * the switch is complete.
+        *
         * @private
         * @param {jQuery.Promise} promise Promise resolved/rejected when 
switch is completed/aborted
+        * @param {string} newMode 'visual' or 'source'
         * @fires switch
         */
-       mw.flow.ui.EditorWidget.prototype.onEditorSwitcherSwitch = function ( 
promise ) {
+       mw.flow.ui.EditorWidget.prototype.onTargetSwitchMode = function ( 
promise, newMode ) {
+               var widget = this;
                this.pushPending();
-               promise.always( this.popPending.bind( this ) );
-               this.emit.apply( this, [ 'switch' ].concat( 
Array.prototype.slice.apply( arguments ) ) );
+               this.error.toggle( false );
+               promise
+                       .done( function () {
+                               if ( widget.autoFocus ) {
+                                       widget.focus();
+                                       widget.moveCursorToEnd();
+                               }
+                               widget.wikitextHelpLabel.toggle( newMode === 
'source' );
+                       } )
+                       .fail( function ( error ) {
+                               widget.error.setLabel( $( '<span>' ).text( 
error || mw.msg( 'flow-error-default' ) ) );
+                               widget.error.toggle( true );
+                       } )
+                       .always( function () {
+                               widget.popPending();
+                       } );
        };
 
        /**
-        * Relay save event
+        * Relay the save event, adding the content.
+        *
         * @private
         * @fires saveContent
         */
        mw.flow.ui.EditorWidget.prototype.onEditorControlsWidgetSave = function 
() {
+               var content = this.getContent();
                this.unbindBeforeUnloadHandler();
                this.emit(
                        'saveContent',
-                       
this.editorSwitcherWidget.getActiveEditor().getContent(),
-                       this.editorSwitcherWidget.getActiveEditor().getFormat()
+                       content.content,
+                       content.format
                );
        };
 
        /**
         * Bind the beforeunload handler, if needed and if not already bound.
+        *
+        * @private
         */
        mw.flow.ui.EditorWidget.prototype.bindBeforeUnloadHandler = function () 
{
                if ( !this.beforeUnloadHandler && ( this.confirmLeave || 
this.leaveCallback ) ) {
@@ -259,6 +464,8 @@
 
        /**
         * Unbind the beforeunload handler if it is bound.
+        *
+        * @private
         */
        mw.flow.ui.EditorWidget.prototype.unbindBeforeUnloadHandler = function 
() {
                if ( this.beforeUnloadHandler ) {
@@ -282,33 +489,10 @@
                }
        };
 
-       /**
-        * Activate the first editor, if not already active.
-        * @return {jQuery.Promise} Promise resolved when editor switch is done
-        */
-       mw.flow.ui.EditorWidget.prototype.activate = function () {
-               var switchPromise,
-                       editor = this.getPreferredEditorName(),
-                       widget = this;
-
-               if ( editor === 'none' ) {
-                       editor = 'wikitext';
-               }
-
-               if ( this.editorSwitcherWidget.isEditorAvailable( editor ) ) {
-                       switchPromise = this.editorSwitcherWidget.switchEditor( 
editor );
-               } else {
-                       // If the editor we want isn't available, let 
EditorSwitcherWidget decide which editor to use
-                       switchPromise = this.editorSwitcherWidget.activate();
-               }
-               return switchPromise.then( function () {
-                       widget.bindBeforeUnloadHandler();
-               } );
-       };
-
        mw.flow.ui.EditorWidget.prototype.isDisabled = function () {
-               // Auto-disable when pending
+               // Auto-disable when pending or not saveable
                return this.isPending() ||
+                       !this.isSaveable() ||
                        // Parent method
                        
mw.flow.ui.EditorWidget.parent.prototype.isDisabled.apply( this, arguments );
        };
@@ -317,26 +501,13 @@
                // Parent method
                mw.flow.ui.EditorWidget.parent.prototype.setDisabled.call( 
this, disabled );
 
-               if ( this.editorSwitcherWidget && this.editorControlsWidget ) {
-                       this.editorSwitcherWidget.setDisabled( 
this.isDisabled() );
+               if ( this.editorControlsWidget ) {
                        this.editorControlsWidget.setDisabled( 
this.isDisabled() );
                }
-       };
 
-       /**
-        * Toggle whether the editor is saveable
-        *
-        * This is different from setDisabled because that will also disable 
the cancel button.
-        * We want to allow them to click 'cancel' so they can collapse the 
editor again after
-        * seeing that editing is disabled.
-        *
-        * @param {boolean} [saveable] Whether the editor is saveable
-        */
-       mw.flow.ui.EditorWidget.prototype.toggleSaveable = function ( saveable 
) {
-               this.saveable = saveable === undefined ? !this.saveable : 
!!saveable;
-
-               this.editorSwitcherWidget.toggleSaveable( this.saveable );
-               this.editorControlsWidget.toggleSaveable( this.saveable );
+               if ( this.target ) {
+                       this.target.setDisabled( this.isDisabled() );
+               }
        };
 
        mw.flow.ui.EditorWidget.prototype.pushPending = function () {
@@ -356,20 +527,37 @@
        };
 
        /**
-        * Focus the current editor
+        * Focus the editor
         */
        mw.flow.ui.EditorWidget.prototype.focus = function () {
-               this.editorSwitcherWidget.focus();
+               if ( this.target && this.target.getSurface() ) {
+                       this.target.getSurface().getView().focus();
+               }
+       };
+
+       /**
+        * Move the cursor to the end of the editor.
+        */
+       mw.flow.ui.EditorWidget.prototype.moveCursorToEnd = function () {
+               if ( this.target && this.target.getSurface() ) {
+                       
this.target.getSurface().getModel().selectLastContentOffset();
+               }
+       };
+
+       /**
+        * Remove all content from the editor.
+        *
+        */
+       mw.flow.ui.EditorWidget.prototype.clearContent = function () {
+               this.target.clearSurfaces();
        };
 
        /**
         * Destroy the widget.
         */
        mw.flow.ui.EditorWidget.prototype.destroy = function () {
-               this.editorSwitcherWidget.destroy();
-       };
-
-       mw.flow.ui.EditorWidget.prototype.clearContent = function () {
-               this.editorSwitcherWidget.clearContent();
+               if ( this.target ) {
+                       this.target.destroy();
+               }
        };
 }( jQuery ) );
diff --git a/modules/flow/ui/widgets/mw.flow.ui.BoardDescriptionWidget.js 
b/modules/flow/ui/widgets/mw.flow.ui.BoardDescriptionWidget.js
index 10fb39d..27f7754 100644
--- a/modules/flow/ui/widgets/mw.flow.ui.BoardDescriptionWidget.js
+++ b/modules/flow/ui/widgets/mw.flow.ui.BoardDescriptionWidget.js
@@ -151,7 +151,7 @@
         */
        mw.flow.ui.BoardDescriptionWidget.prototype.onEditButtonClick = 
function () {
                var widget = this,
-                       contentFormat = this.editor.getPreferredFormat() || 
'wikitext';
+                       contentFormat = this.editor.getPreferredFormat();
 
                // Hide the edit button, any errors, and the content
                this.button.toggle( false );
@@ -160,11 +160,9 @@
                this.$content.addClass( 'oo-ui-element-hidden' );
 
                this.editor.toggle( true );
-
-               // Load the editor
                this.editor.pushPending();
                this.anonWarning.toggle( true );
-               this.editor.activate();
+               this.editor.load();
 
                // Get the description from the API
                this.api.getDescription( contentFormat )
@@ -174,11 +172,11 @@
                                                format = OO.getProp( desc, 
'content', 'format' );
 
                                        if ( content !== undefined && format 
!== undefined ) {
-                                               // Give it to the editor
-                                               widget.editor.setContent( 
content, format );
-
                                                // Update revisionId in the API
                                                widget.api.setCurrentRevision( 
widget.model.getRevisionId() );
+
+                                               // Load the editor
+                                               return widget.editor.activate( 
{ content: content, format: format } );
                                        }
                                },
                                // Error fetching description
diff --git a/modules/flow/ui/widgets/mw.flow.ui.EditPostWidget.js 
b/modules/flow/ui/widgets/mw.flow.ui.EditPostWidget.js
index 2f3f100..f99cf78 100644
--- a/modules/flow/ui/widgets/mw.flow.ui.EditPostWidget.js
+++ b/modules/flow/ui/widgets/mw.flow.ui.EditPostWidget.js
@@ -85,11 +85,11 @@
                var widget, contentFormat;
 
                this.editor.pushPending();
-               this.editor.activate();
+               this.editor.load();
 
                // Get the post from the API
                widget = this;
-               contentFormat = this.editor.getContentFormat();
+               contentFormat = this.editor.getPreferredFormat();
 
                this.api.getPost( this.topicId, this.postId, contentFormat 
).then(
                        function ( post ) {
@@ -97,11 +97,11 @@
                                        format = OO.getProp( post, 'content', 
'format' );
 
                                if ( content !== undefined && format !== 
undefined ) {
-                                       // Give it to the editor
-                                       widget.editor.setContent( content, 
format );
-
                                        // Update revisionId in the API
                                        widget.api.setCurrentRevision( 
post.revisionId );
+
+                                       // Activate the editor
+                                       return widget.editor.activate( { 
content: content, format: format } );
                                }
 
                        },
diff --git a/modules/flow/ui/widgets/mw.flow.ui.EditTopicSummaryWidget.js 
b/modules/flow/ui/widgets/mw.flow.ui.EditTopicSummaryWidget.js
index c982a7c..dac9df1 100644
--- a/modules/flow/ui/widgets/mw.flow.ui.EditTopicSummaryWidget.js
+++ b/modules/flow/ui/widgets/mw.flow.ui.EditTopicSummaryWidget.js
@@ -84,7 +84,7 @@
 
                // Load the editor
                this.editor.pushPending();
-               this.editor.activate();
+               this.editor.load();
 
                // Get the post from the API
                widget = this;
@@ -96,11 +96,11 @@
                                        format = OO.getProp( topicSummary, 
'content', 'format' );
 
                                if ( content !== undefined && format !== 
undefined ) {
-                                       // Give it to the editor
-                                       widget.editor.setContent( content, 
format );
-
                                        // Update revisionId in the API
                                        widget.api.setCurrentRevision( 
topicSummary.revisionId );
+
+                                       // Load the editor
+                                       return widget.editor.activate( { 
content: content, format: format } );
                                }
 
                        },
diff --git a/modules/flow/ui/widgets/mw.flow.ui.NewTopicWidget.js 
b/modules/flow/ui/widgets/mw.flow.ui.NewTopicWidget.js
index 652e383..9a09236 100644
--- a/modules/flow/ui/widgets/mw.flow.ui.NewTopicWidget.js
+++ b/modules/flow/ui/widgets/mw.flow.ui.NewTopicWidget.js
@@ -124,7 +124,7 @@
                var isDisabled = this.isExpanded() && !this.isProbablyEditable;
 
                this.title.setDisabled( isDisabled );
-               this.editor.editorSwitcherWidget.setDisabled( isDisabled );
+               this.editor.setDisabled( isDisabled );
 
                this.editor.editorControlsWidget.toggleSaveable(
                        this.isProbablyEditable &&
@@ -143,14 +143,17 @@
 
        /**
         * Expand the widget and make it ready to create a new topic
+        * @param {Object} content Content to preload into the editor
+        * @param {string} content.content Content
+        * @param {string} content.format Format of content ('html' or 
'wikitext')
         */
-       mw.flow.ui.NewTopicWidget.prototype.activate = function () {
+       mw.flow.ui.NewTopicWidget.prototype.activate = function ( content ) {
                var widget = this;
                if ( !this.isExpanded() ) {
                        // Expand the editor
                        this.toggleExpanded( true );
                        this.editor.toggleAutoFocus( false );
-                       this.editor.activate().then( function () {
+                       this.editor.activate( content ).then( function () {
                                widget.updateFormState();
                                widget.title.focus();
                                widget.editor.toggleAutoFocus( true );
@@ -166,12 +169,12 @@
         * @param {string} format
         */
        mw.flow.ui.NewTopicWidget.prototype.preload = function ( title, 
content, format ) {
-               this.activate();
-               this.title.setValue( title );
-
                if ( content && format ) {
-                       this.editor.setContent( content, format ).then( 
this.updateFormState.bind( this ) );
+                       this.activate( { content: content, format: format } );
+               } else {
+                       this.activate();
                }
+               this.title.setValue( title );
        };
 
        /**
diff --git a/modules/mw.flow.Initializer.js b/modules/mw.flow.Initializer.js
index 72acbb2..d5e37f4 100644
--- a/modules/mw.flow.Initializer.js
+++ b/modules/mw.flow.Initializer.js
@@ -822,9 +822,7 @@
 
                // Prepare the editor
                editor.pushPending();
-               editor.activate();
-
-               editor.setContent( content, 'wikitext' )
+               editor.activate( { content: content || '', format: 'wikitext' } 
)
                        .then( function () {
                                editor.popPending();
                        } );
diff --git a/modules/styles/flow.variables.less 
b/modules/styles/flow.variables.less
index 2765e4e..422ac23 100644
--- a/modules/styles/flow.variables.less
+++ b/modules/styles/flow.variables.less
@@ -2,7 +2,7 @@
 @replyIndent: 1.5em;
 @textareaPadding: 0.3em;
 @editorToolbarHeight: 40px;
-@editorMinHeight: 10em;
+@editorMinHeight: 140px;
 
 // @todo: Use same variable as MobileFrontend
 
diff --git 
a/modules/styles/flow/widgets/editor/editors/mw.flow.ui.WikitextEditorWidget.less
 
b/modules/styles/flow/widgets/editor/editors/mw.flow.ui.WikitextEditorWidget.less
deleted file mode 100644
index 86b0dec..0000000
--- 
a/modules/styles/flow/widgets/editor/editors/mw.flow.ui.WikitextEditorWidget.less
+++ /dev/null
@@ -1,47 +0,0 @@
-@import 'mediawiki.mixins';
-@import '../../../../flow.colors';
-@import '../../../../flow.variables';
-
-.flow-ui-wikitextEditorWidget {
-       position: relative;
-
-       .oo-ui-textInputWidget {
-               max-width: none;
-
-               // Work around Chrome bug adding 5px below the textarea
-               line-height: 0;
-
-               textarea {
-                       /* Re-apply system default after override by OOUI */
-                       font-size: 13.3333px;
-                       min-height: @editorMinHeight;
-                       -webkit-appearance: none;
-                       background: transparent;
-
-                       &,
-                       &:focus {
-                               border: 0;
-                               box-shadow: none;
-                               outline: 0;
-                       }
-
-                       /* stylelint-disable indentation */
-                       .mixin-placeholder( {
-                               font-weight: normal;
-                       } );
-                       /* stylelint-enable indentation */
-               }
-       }
-
-       &-label {
-               color: @colorTextLight;
-               font-size: 0.75em;
-               padding: 4px 6px 6px 6px;
-               position: absolute;
-               bottom: 0;
-       }
-
-       > .oo-ui-toolbar {
-               float: right;
-       }
-}
diff --git 
a/modules/styles/flow/widgets/editor/mw.flow.ui.EditorSwitcherWidget.less 
b/modules/styles/flow/widgets/editor/mw.flow.ui.EditorSwitcherWidget.less
deleted file mode 100644
index e4257f5..0000000
--- a/modules/styles/flow/widgets/editor/mw.flow.ui.EditorSwitcherWidget.less
+++ /dev/null
@@ -1,42 +0,0 @@
-@import 'mediawiki.mixins';
-@import '../../../flow.colors';
-@import '../../../flow.variables';
-
-.flow-ui-editorSwitcherWidget {
-       display: block;
-       background-color: #fff;
-
-       &-placeholder-input {
-               max-width: none;
-               background-color: transparent;
-               // Provide fallback for browsers that don't have calc function
-               // (Currently IE8, Opera Mini, Android 4.3)
-               min-height: @editorMinHeight;
-               // The placeholder should be the same height as the editor: 
minHeight + toolbarHeight.
-               // However, each of those is in different
-               // units (minHeight in em and toolbarHeight in px) which means 
we must
-               // use CSS's native calc() method.
-               // This escapes the calc() method while using LESS variables:
-               min-height: ~'calc( @{editorMinHeight} + @{editorToolbarHeight} 
)';
-
-               & textarea {
-                       border: 0;
-
-                       /* stylelint-disable indentation */
-                       .mixin-placeholder( {
-                               font-weight: normal;
-                       } );
-                       /* stylelint-enable indentation */
-               }
-       }
-
-       // Undo top margin on error box
-       &-error.flow-errors {
-               margin-top: 0;
-       }
-
-       // HACK: Undo border set by "old" editor CSS
-       > .flow-ui-visualEditorWidget > .ve-init-target {
-               border: 0;
-       }
-}
diff --git a/modules/styles/flow/widgets/editor/mw.flow.ui.EditorWidget.less 
b/modules/styles/flow/widgets/editor/mw.flow.ui.EditorWidget.less
index 45287a5..c2f96b0 100644
--- a/modules/styles/flow/widgets/editor/mw.flow.ui.EditorWidget.less
+++ b/modules/styles/flow/widgets/editor/mw.flow.ui.EditorWidget.less
@@ -1,5 +1,34 @@
+@import 'mediawiki.mixins';
+@import '../../../flow.variables';
+@import '../../../flow.colors';
+
 .flow-ui-editorWidget {
+       background-color: #fff;
+
        &-collapsed .flow-ui-editorControlsWidget {
                display: none;
        }
+
+       // Undo top margin on error box
+       &-error.flow-errors {
+               margin-top: 0;
+       }
+
+       > .flow-ui-editorWidget-editor {
+               position: relative;
+               min-height: @editorMinHeight + @editorToolbarHeight;
+
+               // HACK: Undo border set by "old" editor CSS
+               > .ve-init-target {
+                       border: 0;
+               }
+       }
+
+       &-wikitextHelpLabel {
+               color: @colorTextLight;
+               font-size: 0.75em;
+               padding: 4px 6px 6px 6px;
+               position: absolute;
+               bottom: @editorToolbarHeight;
+       }
 }
diff --git a/modules/styles/flow/widgets/editor/mw.flow.ui.editor-monobook.less 
b/modules/styles/flow/widgets/editor/mw.flow.ui.editor-monobook.less
index 0208958..e5ef597 100644
--- a/modules/styles/flow/widgets/editor/mw.flow.ui.editor-monobook.less
+++ b/modules/styles/flow/widgets/editor/mw.flow.ui.editor-monobook.less
@@ -24,23 +24,10 @@
                font-size: @uiFontSize;
        }
 
-       .flow-ui-wikitextEditorWidget,
-       .flow-ui-visualEditorWidget {
+       .flow-ui-editorWidget-editor {
                > .oo-ui-toolbar {
                        font-size: @uiFontSize;
                        margin-top: -44px;
-               }
-       }
-
-       .flow-ui-wikitextEditorWidget {
-               > .oo-ui-toolbar > .oo-ui-toolbar-bar {
-                       /* Floating mini toolbar doesn't need drop shadow */
-                       box-shadow: none;
-               }
-
-               .oo-ui-textInputWidget textarea {
-                       /* Adjust min-height to new font size */
-                       min-height: 10em * 12.7/13.3333;
                }
        }
 
@@ -67,7 +54,7 @@
 }
 
 .flow-editor,
-.flow-ui-editorSwitcherWidget {
+.flow-ui-editorWidget-editor {
        // Undo border & box-shadow on textarea and re-apply it on the
        // div that contains textarea + legal text + switch button
        border: 1px solid #c8ccd1;
@@ -75,7 +62,7 @@
 }
 
 .flow-editor.flow-ui-focused,
-.flow-ui-editorSwitcherWidget-focused {
+.flow-ui-editorWidget-focused .flow-ui-editorWidget-editor {
        outline: 0;
        border-color: #a7dcff;
        box-shadow: 0 0 0.3em #a7dcff, 0 0 0 #fff;
diff --git a/modules/styles/flow/widgets/editor/mw.flow.ui.editor-vector.less 
b/modules/styles/flow/widgets/editor/mw.flow.ui.editor-vector.less
index 996c3bf..b1cd5e7 100644
--- a/modules/styles/flow/widgets/editor/mw.flow.ui.editor-vector.less
+++ b/modules/styles/flow/widgets/editor/mw.flow.ui.editor-vector.less
@@ -26,8 +26,7 @@
                font-size: @uiFontSize;
        }
 
-       .flow-ui-wikitextEditorWidget,
-       .flow-ui-visualEditorWidget {
+       .flow-ui-editorWidget-editor {
                > .oo-ui-toolbar {
                        font-size: @uiFontSize;
                        // The 1px of positive margin gives room for the blue 
border
@@ -70,7 +69,7 @@
 }
 
 .flow-editor,
-.flow-ui-editorSwitcherWidget {
+.flow-ui-editorWidget-editor {
        // because we're attaching switcher controls below the textarea & we
        // want them to look unified with the textarea, we'll have to take away
        // it's border and re-apply on the parent node that contains both
@@ -83,7 +82,7 @@
 }
 
 .flow-editor.flow-ui-focused,
-.flow-ui-editorSwitcherWidget-focused {
+.flow-ui-editorWidget-focused .flow-ui-editorWidget-editor {
        // Undo border & box-shadow on textarea and re-apply it on the
        // div that contains textarea + legal text + switch button
        border-color: @colorProgressive;
diff --git a/modules/styles/flow/widgets/mw.flow.ui.NewTopicWidget.less 
b/modules/styles/flow/widgets/mw.flow.ui.NewTopicWidget.less
index ff74b7a..650858c 100644
--- a/modules/styles/flow/widgets/mw.flow.ui.NewTopicWidget.less
+++ b/modules/styles/flow/widgets/mw.flow.ui.NewTopicWidget.less
@@ -41,13 +41,13 @@
 
        &-editor {
                .flow-editor,
-               .flow-ui-editorSwitcherWidget {
+               .flow-ui-editorWidget {
                        margin-top: -1px;
                        border-top-left-radius: 0;
                        border-top-right-radius: 0;
                }
                .flow-editor.flow-ui-focused,
-               .flow-ui-editorSwitcherWidget-focused {
+               .flow-ui-editorWidget-focused .flow-ui-editorWidget-editor {
                        /* Force above the title widget so border isn't cropped 
*/
                        position: relative;
                }

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

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

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

Reply via email to