Mattflaschen has uploaded a new change for review.
https://gerrit.wikimedia.org/r/192759
Change subject: This is the initial re-activation of VE.
......................................................................
This is the initial re-activation of VE.
Basically all the high-level stuff works:
* New topics
* New posts
* Replies
* Header
* Summaries
I did some refactoring, such as making an AbstractEditor and
using OOJS conventions (inheritance, their approach to static properties).
I came up with a convention for how editors should signal whether they use
preview, and whether the editor is currently empty.
Updated the VE loading and isSupported code. There are probably some
simplifications the VE team can help us make at some point.
Fixed a problem where inputs were being 'expanded' (which was a no-op
for the wikitext editor, but causes VE to load in the new topic title
box).
The most obvious user-facing issues are:
* It's a bit slow on first load (not sure how evident this will be on
real servers). Some of this is specifically because of
how we handle VE/wikitext conversion.
* It doesn't support MW-specific features, such as links(!)
* When you click the header pencil, it shows the wikitext first,
until you focus the textarea
However, it should be reviewable as a first pass (it will not impact
users since it requires a config change to enable it).
Bug: T88182
Change-Id: If273555cd8bb4633f06c70d4979196c31526cbf6
---
M Resources.php
M handlebars/compiled/flow_block_topic.handlebars.php
M handlebars/compiled/flow_block_topiclist.handlebars.php
M handlebars/compiled/flow_post.handlebars.php
M handlebars/flow_form_buttons.handlebars
A modules/editor/editors/ext.flow.editors.AbstractEditor.js
M modules/editor/editors/ext.flow.editors.none.js
M modules/editor/editors/ext.flow.editors.visualeditor.js
M modules/editor/ext.flow.editor.js
M modules/engine/components/common/flow-component-events.js
M modules/engine/misc/mw-ui.enhance.js
M modules/styles/board/form-actions.less
12 files changed, 240 insertions(+), 70 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Flow
refs/changes/59/192759/1
diff --git a/Resources.php b/Resources.php
index 0390875..9227b40 100644
--- a/Resources.php
+++ b/Resources.php
@@ -357,6 +357,7 @@
),
'dependencies' => array(
'oojs',
+ 'ext.flow.editor',
'ext.flow.templating', // ResourceLoader templating
'ext.flow.handlebars', // prototype-based for
progressiveEnhancement
'ext.flow.vendor.storer',
@@ -386,8 +387,10 @@
'ext.flow.editor' => $flowResourceTemplate + array(
'scripts' => array(
'editor/ext.flow.editor.js',
+ 'editor/editors/ext.flow.editors.AbstractEditor.js',
),
'dependencies' => array(
+ 'oojs',
'ext.flow.parsoid',
// specific editor (ext.flow.editors.*) dependencies
(if any) will be loaded via JS
),
diff --git a/handlebars/compiled/flow_block_topic.handlebars.php
b/handlebars/compiled/flow_block_topic.handlebars.php
index 1448f17..a4ab2ed 100644
--- a/handlebars/compiled/flow_block_topic.handlebars.php
+++ b/handlebars/compiled/flow_block_topic.handlebars.php
@@ -165,7 +165,7 @@
data-flow-api-target="< form textarea"
name="preview"
data-role="action"
- class="mw-ui-button mw-ui-progressive mw-ui-quiet mw-ui-flush-right
flow-js"
+ class="mw-ui-button mw-ui-progressive mw-ui-quiet mw-ui-flush-right
flow-form-action-preview flow-js"
>'.LCRun3::ch($cx, 'l10n', array(array('flow-preview'),array()),
>'encq').'</button>
diff --git a/handlebars/compiled/flow_block_topiclist.handlebars.php
b/handlebars/compiled/flow_block_topiclist.handlebars.php
index 85dc503..46135c7 100644
--- a/handlebars/compiled/flow_block_topiclist.handlebars.php
+++ b/handlebars/compiled/flow_block_topiclist.handlebars.php
@@ -102,7 +102,7 @@
data-flow-api-target="< form textarea"
name="preview"
data-role="action"
- class="mw-ui-button mw-ui-progressive mw-ui-quiet mw-ui-flush-right
flow-js"
+ class="mw-ui-button mw-ui-progressive mw-ui-quiet mw-ui-flush-right
flow-form-action-preview flow-js"
>'.LCRun3::ch($cx, 'l10n', array(array('flow-preview'),array()),
>'encq').'</button>
diff --git a/handlebars/compiled/flow_post.handlebars.php
b/handlebars/compiled/flow_post.handlebars.php
index a6cb44a..45f362f 100644
--- a/handlebars/compiled/flow_post.handlebars.php
+++ b/handlebars/compiled/flow_post.handlebars.php
@@ -193,7 +193,7 @@
data-flow-api-target="< form textarea"
name="preview"
data-role="action"
- class="mw-ui-button mw-ui-progressive mw-ui-quiet mw-ui-flush-right
flow-js"
+ class="mw-ui-button mw-ui-progressive mw-ui-quiet mw-ui-flush-right
flow-form-action-preview flow-js"
>'.LCRun3::ch($cx, 'l10n', array(array('flow-preview'),array()),
>'encq').'</button>
diff --git a/handlebars/flow_form_buttons.handlebars
b/handlebars/flow_form_buttons.handlebars
index 64bda0d..9eeabc7 100644
--- a/handlebars/flow_form_buttons.handlebars
+++ b/handlebars/flow_form_buttons.handlebars
@@ -2,7 +2,7 @@
data-flow-api-target="< form textarea"
name="preview"
data-role="action"
- class="mw-ui-button mw-ui-progressive mw-ui-quiet mw-ui-flush-right
flow-js"
+ class="mw-ui-button mw-ui-progressive mw-ui-quiet mw-ui-flush-right
flow-form-action-preview flow-js"
{{!-- No data-flow-eventlog-action here; we'll do that in code because
the action will toggle between preview & keep-editing --}}
>
diff --git a/modules/editor/editors/ext.flow.editors.AbstractEditor.js
b/modules/editor/editors/ext.flow.editors.AbstractEditor.js
new file mode 100644
index 0000000..20b656a
--- /dev/null
+++ b/modules/editor/editors/ext.flow.editors.AbstractEditor.js
@@ -0,0 +1,37 @@
+( function ( mw ) {
+ 'use strict';
+
+ /**
+ * Abstract editor class for Flow content
+ * Sets certain defaults, but most have to be implemented in subclasses
+ *
+ * @class
+ * @abstract
+ *
+ * @constructor
+ */
+ mw.flow.editor.AbstractEditor = function () {};
+
+ OO.initClass( mw.flow.editor.AbstractEditor );
+
+ // Static methods
+
+ /**
+ * Returns whether this editor uses a preview mode
+ *
+ * @return {boolean}
+ */
+ mw.flow.editor.AbstractEditor.static.usesPreview = function () {
+ return true;
+ };
+
+ /**
+ * Determines if this editor is supported for the current user and
+ * environment (browser, etc.)
+ *
+ * @return {boolean}
+ */
+ mw.flow.editor.AbstractEditor.static.isSupported = function () {
+ return true;
+ };
+} ( mediaWiki ) );
diff --git a/modules/editor/editors/ext.flow.editors.none.js
b/modules/editor/editors/ext.flow.editors.none.js
index 98de6f4..2136a58 100644
--- a/modules/editor/editors/ext.flow.editors.none.js
+++ b/modules/editor/editors/ext.flow.editors.none.js
@@ -2,6 +2,11 @@
'use strict';
/**
+ * Editor class that uses a simple wikitext textarea
+ *
+ * @class
+ * @constructor
+ *
* @param {jQuery} $node
* @param {string} [content='']
*/
@@ -20,12 +25,15 @@
this.autoExpand.call( this.$node.get( 0 ) );
};
+ OO.inheritClass( mw.flow.editors.none, mw.flow.editor.AbstractEditor );
+
+ // Static properties
/**
* Type of content to use (html or wikitext)
*
* @var string
*/
- mw.flow.editors.none.format = 'wikitext';
+ mw.flow.editors.none.static.format = 'wikitext';
mw.flow.editors.none.prototype.destroy = function () {
// unset min-height that was set for auto-expansion
@@ -44,6 +52,15 @@
};
/**
+ * Checks whether the field is empty
+ *
+ * @return {boolean} True if and only if it's empty
+ */
+ mw.flow.editors.none.prototype.isEmpty = function () {
+ return this.getRawContent() === '';
+ };
+
+ /**
* Auto-expand/shrink as content changes.
*/
mw.flow.editors.none.prototype.autoExpand = function() {
diff --git a/modules/editor/editors/ext.flow.editors.visualeditor.js
b/modules/editor/editors/ext.flow.editors.visualeditor.js
index 6a2209a..6cf20fe 100644
--- a/modules/editor/editors/ext.flow.editors.visualeditor.js
+++ b/modules/editor/editors/ext.flow.editors.visualeditor.js
@@ -1,5 +1,5 @@
-( function ( $, mw ) {
+( function ( $, mw, ve ) {
'use strict';
/**
@@ -22,12 +22,7 @@
mw.loader.using( this.getModules(), $.proxy( this.init, this,
content || '' ) );
};
- /**
- * Type of content to use (html or wikitext)
- *
- * @var {string}
- */
- mw.flow.editors.visualeditor.format = 'html';
+ OO.inheritClass( mw.flow.editors.visualeditor,
mw.flow.editor.AbstractEditor );
/**
* List of callbacks to execute when VE is fully loaded
@@ -40,41 +35,85 @@
* @param {string} [content='']
*/
mw.flow.editors.visualeditor.prototype.init = function ( content ) {
- var $veNode,
- $focusedElement = $( ':focus' );
+ var $veNode, htmlDoc, dmDoc, target,
+ $focusedElement = $( ':focus' ),
+ flowEditor = this;
+
+ // ve.createDocumentFromHtml documents support for an empty
string
+ // to create an empty document, but does not mention other
falsy values.
+ content = content || '';
// add i18n messages to VE
- window.ve.init.platform.addMessages( mw.messages.values );
+ ve.init.platform.addMessages( mw.messages.values );
$.removeSpinner( 'flow-editor-loading' );
// init ve, save target object
- this.target = new window.ve.init.sa.Target(
- // ve does not "convert" textareas = bind to new div
- $( '<div>' ).insertAfter( this.$node ),
- window.ve.createDocumentFromHtml( content || '' )
+ //
+ // We need at least some MW-specific stuff (e.g. links,
MW-style files).
+ //
+ // But for now we're using standalone, since
+ // ve.init.mw.Target has some stuff that is not applicable
+ // to us (e.g. submitUrl, this.$checkboxes), and there
+ // were glitches when I tried to use it.
+ //
+ // However, we will have to look at this later.
+ target = this.target = new ve.init.sa.Target(
+ 'desktop'
);
- $veNode = this.target.surface.$element.find(
'.ve-ce-documentNode' );
+ htmlDoc = ve.createDocumentFromHtml( content ); // HTMLDocument
- // focus VE instance if textarea had focus
- if ( !$focusedElement.length || this.$node.is( $focusedElement
) ) {
- this.focus();
- } else {
- $focusedElement.focus();
- }
+ // Based on ve.init.mw.Target.prototype.setupSurface
+ dmDoc = this.dmDoc = ve.dm.converter.getModelFromDom(
+ htmlDoc,
+ null,
+ mw.config.get( 'wgVisualEditor' ).pageLanguageCode,
+ mw.config.get( 'wgVisualEditor' ).pageLanguageDir
+ );
+ dmDoc.buildNodeTree();
- $veNode.addClass( 'mw-ui-input' );
+ setTimeout( function () {
+ var surface = target.addSurface( dmDoc ),
+ surfaceView = surface.getView(),
+ $documentNode =
surfaceView.getDocument().getDocumentNode().$element;
- // simulate a keyup event on the original node, so the
validation code will
- // pick up changes in the new node
- $veNode.keyup( $.proxy( function () {
- this.$node.keyup();
- }, this ) );
+ $( '<div>' ).insertAfter( flowEditor.$node ).append(
target.$element );
+ surface.$element.appendTo( target.$element );
- $.each( this.initCallbacks, $.proxy( function( k, callback ) {
- callback.apply( this );
- }, this ) );
+ $documentNode.addClass(
+ // Add appropriately mw-content-ltr or mw-content-rtl
class
+ 'mw-content-' + mw.config.get( 'wgVisualEditor'
).pageLanguageDir
+ );
+
+ target.setSurface( surface );
+ setTimeout( function () {
+ surface.getContext().toggle( true ); // Visible
+ target.active = true;
+
+ // focus VE instance if textarea had focus
+ if ( !$focusedElement.length ||
flowEditor.$node.is( $focusedElement ) ) {
+ flowEditor.focus();
+ } else {
+ $focusedElement.focus();
+ }
+
+ $veNode = surface.$element.find(
'.ve-ce-documentNode' );
+
+ $veNode.addClass( 'mw-ui-input' );
+
+ // simulate a keyup event on the original node,
so the validation code will
+ // pick up changes in the new node
+ $veNode.keyup( $.proxy( function () {
+ this.$node.keyup();
+ }, flowEditor ) );
+
+ $.each( flowEditor.initCallbacks, $.proxy(
function( k, callback ) {
+ callback.apply( this );
+ }, flowEditor ) );
+
+ } );
+ } );
};
mw.flow.editors.visualeditor.prototype.destroy = function () {
@@ -91,52 +130,75 @@
* @return {array}
*/
mw.flow.editors.visualeditor.prototype.getModules = function () {
- var
- // core setup
- core =
- mw.config.get( 'wgVisualEditorConfig'
).enableExperimentalCode ?
- ['ext.visualEditor.experimental'] :
- ['ext.visualEditor.core'],
+ var core, standalone, messages, icons, specific;
- // standalone target
- standalone = ['ext.visualEditor.standalone'],
+ // core setup
+ core = ['ext.visualEditor.core.desktop'];
+ if ( mw.config.get( 'wgVisualEditorConfig'
).enableExperimentalCode ) {
+ core.push( 'ext.visualEditor.experimental' );
+ }
- // data module
- messages = ['ext.visualEditor.data'],
+ // Standalone
+ standalone = ['ext.visualEditor.standalone'];
- // icons
- icons =
- document.createElementNS &&
document.createElementNS( 'http://www.w3.org/2000/svg', 'svg' ).createSVGRect ?
-
['ext.visualEditor.viewPageTarget.icons-vector',
'ext.visualEditor.icons-vector'] :
-
['ext.visualEditor.viewPageTarget.icons-raster',
'ext.visualEditor.icons-raster'],
+ // data module
+ messages = ['ext.visualEditor.data'];
- // plugins
- plugins = mw.config.get( 'wgVisualEditorConfig'
).pluginModules || [],
+ // icons
+ icons = ['ext.visualEditor.icons'];
- // site & user
- specific = ['site', 'user'];
+ // plugins
+ //
+ // No plugins supported for now. Later, we can figure out
which plugins we want.
- return [].concat( core, standalone, messages, icons, plugins,
specific );
+ // plugins = mw.config.get( 'wgVisualEditorConfig'
).pluginModules || [],
+
+ // site & user
+ specific = ['site', 'user'];
+
+ return [].concat(
+ core,
+ standalone,
+ messages,
+ icons,
+ // plugins,
+ specific
+ );
};
/**
+ * Gets HTML of Flow field
+ *
* @return {string}
*/
mw.flow.editors.visualeditor.prototype.getRawContent = function () {
+ var doc, html;
+
// If we haven't fully loaded yet, just return nothing.
if ( !this.target ) {
return '';
}
// get document from ve
- var model = this.target.surface.getModel();
+ doc = ve.dm.converter.getDomFromModel( this.dmDoc );
// document content will include html, head & body nodes; get
only content inside body node
- return $( window.ve.properOuterHtml(
model.getDocument().documentElement ) ).wrapAll( '<div>' ).parent().html();
+ html = ve.properInnerHtml( $( doc.documentElement ).find(
'body' )[0] );
+ return html;
};
- mw.flow.editors.visualeditor.isSupported = function() {
- return mw.user.options.get( 'visualeditor-enable' ) ? true :
false;
+ /**
+ * Checks if the document is empty
+ *
+ * @return {boolean} True if and only if it's empty
+ */
+ mw.flow.editors.visualeditor.prototype.isEmpty = function () {
+ if ( !this.dmDoc ) {
+ return true;
+ }
+
+ // Per Roan
+ return this.dmDoc.data.countNonInternalElements() <= 2;
};
mw.flow.editors.visualeditor.prototype.focus = function() {
@@ -162,4 +224,28 @@
this.target.surface.getModel().setSelection( new ve.Range(
cursorPos ) );
};
-} ( jQuery, mediaWiki ) );
+
+ // Static fields
+
+ /**
+ * Type of content to use (html or wikitext)
+ *
+ * @var {string}
+ */
+ mw.flow.editors.visualeditor.static.format = 'html';
+
+ // Static methods
+
+ mw.flow.editors.visualeditor.static.isSupported = function () {
+ return !!(
+ mw.user.options.get( 'visualeditor-enable' ) &&
+ // Since VE commit
e2fab2f1ebf2a28f18b8ead08c478c4fc95cd64e, SVG is required
+ document.createElementNS &&
+ document.createElementNS( 'http://www.w3.org/2000/svg',
'svg' ).createSVGRect
+ );
+ };
+
+ mw.flow.editors.visualeditor.static.usesPreview = function () {
+ return false;
+ };
+} ( jQuery, mediaWiki, ve ) );
diff --git a/modules/editor/ext.flow.editor.js
b/modules/editor/ext.flow.editor.js
index 70efaf7..9b3cbcf 100644
--- a/modules/editor/ext.flow.editor.js
+++ b/modules/editor/ext.flow.editor.js
@@ -3,6 +3,9 @@
mw.flow = mw.flow || {}; // create mw.flow globally
mw.flow.editors = {};
+
+ // This is more of an EditorFacade/EditorDispatcher or something, and
should be renamed.
+ // It's not the base class, nor is it an actual editor.
mw.flow.editor = {
/**
* Specific editor to be used.
@@ -44,8 +47,7 @@
mw.loader.using( 'ext.flow.editors.' + editor, function
() {
// Some editors only work under certain
circumstances
if (
- $.isFunction(
mw.flow.editors[editor].isSupported ) &&
- !mw.flow.editors[editor].isSupported()
+
!mw.flow.editors[editor].static.isSupported()
) {
mw.flow.editor.loadEditor( editorIndex
+ 1 );
} else {
@@ -62,7 +64,7 @@
*/
load: function ( $node, content, contentFormat ) {
/**
- * When calling load(), init() may nog yet have
completed loading the
+ * When calling load(), init() may not yet have
completed loading the
* dependencies. To make sure it doesn't break, this
will in interval,
* check for it and only start loading once
initialization is complete.
*/
@@ -127,7 +129,7 @@
* @return {string}
*/
getFormat: function () {
- return mw.flow.editor.editor.format;
+ return mw.flow.editor.editor.static.format;
},
/**
diff --git a/modules/engine/components/common/flow-component-events.js
b/modules/engine/components/common/flow-component-events.js
index a6ad9fd..f2e4f53 100644
--- a/modules/engine/components/common/flow-component-events.js
+++ b/modules/engine/components/common/flow-component-events.js
@@ -713,10 +713,12 @@
var $context = $( event.currentTarget || event.delegateTarget
|| event.target ),
component = mw.flow.getPrototypeMethod( 'component',
'getInstanceByElement' )( $context );
- // Expand this textarea
- component.emitWithReturn( 'expandTextarea', $context );
+ // Expand this if it's a textarea
+ if ( $context.is( 'textarea' ) ) {
+ component.emitWithReturn( 'expandTextarea', $context );
+ }
- // Show the form (and swap it for textarea if needed)
+ // Show the form (and initialize editors)
component.emitWithReturn( 'showForm', $context.closest( 'form'
) );
}
FlowComponentEventsMixin.eventHandlers.focusField =
flowEventsMixinFocusField;
@@ -770,7 +772,7 @@
* @param {jQuery} $container
*/
function flowEventsMixinInitializeEditors( $container ) {
- var flowComponent = this;
+ var flowComponent = this, $form;
mw.loader.using( 'ext.flow.editor', function() {
var $editors = $container.find(
'textarea:not(.flow-input-compressed)' );
@@ -783,8 +785,11 @@
// JS-less users can interact)
mw.flow.editor.load( $editor, $editor.val(),
'wikitext' );
+ $form = $editor.closest( 'form' );
+ $form.toggleClass(
'flow-editor-supports-preview', mw.flow.editor.editor.static.usesPreview() );
+
// Kill editor instance when the form it's in
is cancelled
- flowComponent.emitWithReturn(
'addFormCancelCallback', $editor.closest( 'form' ), function() {
+ flowComponent.emitWithReturn(
'addFormCancelCallback', $form, function() {
if ( mw.flow.editor.exists( $editor ) )
{
mw.flow.editor.destroy( $editor
);
}
diff --git a/modules/engine/misc/mw-ui.enhance.js
b/modules/engine/misc/mw-ui.enhance.js
index 0419bbe..3da4e70 100644
--- a/modules/engine/misc/mw-ui.enhance.js
+++ b/modules/engine/misc/mw-ui.enhance.js
@@ -96,7 +96,12 @@
ready = true;
$fields.each( function () {
- if ( this.value === '' ) {
+ var $this = $( this );
+ if ( mw.flow.editor.exists( $this ) ) {
+ if ( mw.flow.editor.getEditor( $this
).isEmpty() ) {
+ ready = false;
+ }
+ } else if ( this.value === '' ) {
ready = false;
}
} );
@@ -110,6 +115,7 @@
* Usage: field needs required attribute
*/
$( document ).ready( function () {
+ // We should probably not use this change detection method for
VE
$( document ).on( 'keyup.flow-actions-disabler',
'.mw-ui-input', function () {
enableFormWithRequiredFields( $( this ).closest( 'form'
) );
} );
diff --git a/modules/styles/board/form-actions.less
b/modules/styles/board/form-actions.less
index 2f27583..2901e96 100644
--- a/modules/styles/board/form-actions.less
+++ b/modules/styles/board/form-actions.less
@@ -30,6 +30,20 @@
width: 15em;
}
+.client-js {
+ // We determine in JS whether their editor has a preview mode.
+ // We assume all no-JS compatible editors do.
+ // The extra specificity is to override the normal .flow-js behavior
+
+ .flow-js.flow-form-action-preview {
+ display: none;
+ }
+
+ .flow-editor-supports-preview .flow-js.flow-form-action-preview {
+ display: block;
+ }
+}
+
@media all and (min-width: @wgFlowDeviceWidthTablet) {
.flow-anon-warning-mobile {
display: none;
--
To view, visit https://gerrit.wikimedia.org/r/192759
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: If273555cd8bb4633f06c70d4979196c31526cbf6
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/Flow
Gerrit-Branch: master
Gerrit-Owner: Mattflaschen <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits