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