jenkins-bot has submitted this change and it was merged.

Change subject: Editor switcher
......................................................................


Editor switcher

Create buttons to switch between wikitext and visual editors.  Remembers the
selected editor and continues to use that on future requests.  This removes
any user visible references to previewing, and instead has a link in the
wikitext editor help that switches to VE for previews.  A followup will remove
the various preview related code that is now unused.

Bug: T90763
Change-Id: Ibed8693bfd825819aa53fec61385303b29cecfb1
---
M Flow.php
M Hooks.php
M Resources.php
D handlebars/compiled/flow_block_board_history.handlebars.php
M handlebars/compiled/flow_block_header_edit.handlebars.php
M handlebars/compiled/flow_block_header_undo_edit.handlebars.php
M handlebars/compiled/flow_block_topic.handlebars.php
M handlebars/compiled/flow_block_topic_lock.handlebars.php
M handlebars/compiled/flow_block_topic_moderate_post.handlebars.php
M handlebars/compiled/flow_block_topic_moderate_topic.handlebars.php
M handlebars/compiled/flow_block_topic_undo_edit.handlebars.php
M handlebars/compiled/flow_block_topiclist.handlebars.php
M handlebars/compiled/flow_block_topiclist_newtopic.handlebars.php
M handlebars/compiled/flow_block_topicsummary_edit.handlebars.php
M handlebars/compiled/flow_block_topicsummary_undo_edit.handlebars.php
M handlebars/compiled/flow_post.handlebars.php
A handlebars/flow_editor_switcher.partial.handlebars
M handlebars/flow_form_buttons.partial.handlebars
M i18n/en.json
M i18n/qqq.json
M modules/editor/editors/ext.flow.editors.none.js
M modules/editor/editors/visualeditor/ext.flow.editors.visualeditor.js
M modules/editor/editors/visualeditor/mw.flow.ve.CommandRegistry.js
M modules/editor/editors/visualeditor/mw.flow.ve.Target.js
A 
modules/editor/editors/visualeditor/ui/actions/mw.flow.ve.ui.SwitchEditorAction.js
A modules/editor/editors/visualeditor/ui/images/icons/flow-switch-editor.svg
M modules/editor/editors/visualeditor/ui/mw.flow.ve.ui.Icons.less
A modules/editor/editors/visualeditor/ui/tools/mw.flow.ve.ui.SwitchEditorTool.js
M modules/editor/ext.flow.editor.js
A modules/engine/components/board/features/flow-board-switcheditor.js
A modules/styles/board/editor-switcher.less
31 files changed, 455 insertions(+), 267 deletions(-)

Approvals:
  Mattflaschen: Looks good to me, approved
  Matthias Mullie: Looks good to me, but someone else must approve
  jenkins-bot: Verified



diff --git a/Flow.php b/Flow.php
index 11ab593..e66bd23 100644
--- a/Flow.php
+++ b/Flow.php
@@ -256,6 +256,9 @@
 // for more information.
 $wgDefaultUserOptions['flow-topiclist-sortby'] = 'newest';
 
+// Default editor to use in Flow
+$wgDefaultUserOptions['flow-editor'] = 'none';
+
 // Maximum number of users that can be mentioned in one comment
 $wgFlowMaxMentionCount = 100;
 
diff --git a/Hooks.php b/Hooks.php
index 14d7fe7..8151b58 100644
--- a/Hooks.php
+++ b/Hooks.php
@@ -954,6 +954,10 @@
                        'type' => 'api',
                );
 
+               $preferences['flow-editor'] = array(
+                       'type' => 'api'
+               );
+
                return true;
        }
 
diff --git a/Resources.php b/Resources.php
index 6b5b095..1e39a70 100644
--- a/Resources.php
+++ b/Resources.php
@@ -54,6 +54,7 @@
                        'handlebars/flow_edit_post_ajax.partial.handlebars',
                        'handlebars/flow_edit_post.partial.handlebars',
                        'handlebars/flow_edit_topic_title.partial.handlebars',
+                       'handlebars/flow_editor_switcher.partial.handlebars',
                        'handlebars/flow_errors.partial.handlebars',
                        'handlebars/flow_form_buttons.partial.handlebars',
                        'handlebars/flow_header_detail.partial.handlebars',
@@ -236,6 +237,10 @@
                        'flow-edited-by',
                        // Board header
                        "flow-board-header-browse-topics-link",
+                       // editor switching
+                       "flow-wikitext-editor-help",
+                       "flow-wikitext-editor-help-uses-wikitext",
+                       "flow-wikitext-editor-help-preview-the-result",
                ),
        ) + $mobile,
        // @todo: upstream to mediawiki ui
@@ -287,6 +292,7 @@
                        'styles/board/content-preview.less',
                        'styles/board/form-actions.less',
                        'styles/board/terms-of-use.less',
+                       'styles/board/editor-switcher.less',
                ),
        ) + $mobile,
        'ext.flow.board.topic.styles' => $flowResourceTemplate + array(
@@ -360,6 +366,8 @@
                        'engine/components/board/features/flow-board-toc.js',
                        // Feature: VisualEditor
                        
'engine/components/board/features/flow-board-visualeditor.js',
+                       // Feature: Switch between editors
+                       
'engine/components/board/features/flow-board-switcheditor.js',
 
                        // Component: FlowBoardHistoryComponent
                        'engine/components/board/flow-boardhistory.js',
@@ -377,6 +385,7 @@
                        'ext.flow.jquery.conditionalScroll',
                        'mediawiki.api',
                        'mediawiki.util',
+                       'mediawiki.api.options', // required by switch-editor 
feature
                ),
                'messages' => array(
                        'flow-error-external',
@@ -421,6 +430,7 @@
                ),
                'dependencies' => array(
                        'oojs',
+                       'mediawiki.user',
                        'ext.flow.parsoid',
                        // specific editor (ext.flow.editors.*) dependencies 
(if any) will be loaded via JS
                ),
@@ -428,6 +438,9 @@
        'ext.flow.editors.none' => $flowResourceTemplate + array(
                'scripts' => array(
                        'editor/editors/ext.flow.editors.none.js',
+               ),
+               'messages' => array(
+                       'flow-wikitext-switch-editor-tooltip',
                ),
        ) + $mobile,
 
@@ -443,6 +456,8 @@
                        // MentionInspectorTool must be after MentionInspector 
and before MentionContextItem.
                        
'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/tools/mw.flow.ve.ui.SwitchEditorTool.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',
                ),
@@ -470,6 +485,7 @@
                        'flow-ve-mention-inspector-remove-label',
                        'flow-ve-mention-inspector-invalid-user',
                        'flow-ve-mention-tool-title',
+                       'flow-ve-switch-editor-tool-title',
                ),
        ),
 
diff --git a/handlebars/compiled/flow_block_board_history.handlebars.php 
b/handlebars/compiled/flow_block_board_history.handlebars.php
deleted file mode 100644
index 9910c1a..0000000
--- a/handlebars/compiled/flow_block_board_history.handlebars.php
+++ /dev/null
@@ -1,142 +0,0 @@
-<?php return function ($in, $debugopt = 1) {
-    $cx = array(
-        'flags' => array(
-            'jstrue' => false,
-            'jsobj' => false,
-            'spvar' => true,
-            'prop' => false,
-            'method' => false,
-            'mustlok' => false,
-            'mustsec' => false,
-            'echo' => false,
-            'debug' => $debugopt,
-        ),
-        'constants' => array(),
-        'helpers' => array(            'l10n' => 'Flow\TemplateHelper::l10n',
-            'html' => 'Flow\TemplateHelper::htmlHelper',
-            'historyTimestamp' => 'Flow\TemplateHelper::historyTimestamp',
-            'historyDescription' => 'Flow\TemplateHelper::historyDescription',
-            'showCharacterDifference' => 
'Flow\TemplateHelper::showCharacterDifference',
-            'concat' => 'Flow\TemplateHelper::concat',
-),
-        'blockhelpers' => array(),
-        'hbhelpers' => array(            'ifCond' => 
'Flow\TemplateHelper::ifCond',
-),
-        'partials' => array('flow_moderation_actions_list' => function ($cx, 
$in) {return '<section>'.LCRun3::hbch($cx, 'ifCond', 
array(array(((isset($in['moderationType']) && is_array($in)) ? 
$in['moderationType'] : null),'===','topic'),array()), $in, false, 
function($cx, $in) {return ''.((LCRun3::ifvar($cx, 
((isset($in['actions']['edit']) && is_array($in['actions'])) ? 
$in['actions']['edit'] : null))) ? '<li 
class="flow-js">'.htmlentities((string)((isset($in['noop']) && is_array($in)) ? 
$in['noop'] : null), ENT_QUOTES, 'UTF-8').'<a 
class="'.htmlentities((string)((isset($in['moderationMwUiClass']) && 
is_array($in)) ? $in['moderationMwUiClass'] : null), ENT_QUOTES, 'UTF-8').' 
mw-ui-quiet"
-                                  
href="'.htmlentities((string)((isset($in['actions']['edit']['url']) && 
is_array($in['actions']['edit'])) ? $in['actions']['edit']['url'] : null), 
ENT_QUOTES, 'UTF-8').'"
-                                  
title="'.htmlentities((string)((isset($in['actions']['edit']['title']) && 
is_array($in['actions']['edit'])) ? $in['actions']['edit']['title'] : null), 
ENT_QUOTES, 'UTF-8').'"
-                                  data-flow-interactive-handler="apiRequest"
-                                  data-flow-api-handler="activateEditTitle"
-                                  data-flow-api-target="< .flow-topic-titlebar"
-                               >'.((LCRun3::ifvar($cx, 
((isset($in['moderationIcons']) && is_array($in)) ? $in['moderationIcons'] : 
null))) ? '<span class="wikiglyph wikiglyph-pencil"></span> ' : 
'').''.LCRun3::ch($cx, 'l10n', array(array(LCRun3::ch($cx, 'concat', 
array(array('flow-',((isset($in['moderationType']) && is_array($in)) ? 
$in['moderationType'] : null),'-action-edit-title'),array()), 'raw')),array()), 
'encq').'</a>'.htmlentities((string)((isset($in['noop']) && is_array($in)) ? 
$in['noop'] : null), ENT_QUOTES, 'UTF-8').'</li>' : '').''.((LCRun3::ifvar($cx, 
((isset($in['links']['topic-history']) && is_array($in['links'])) ? 
$in['links']['topic-history'] : null))) ? 
'<li>'.htmlentities((string)((isset($in['noop']) && is_array($in)) ? 
$in['noop'] : null), ENT_QUOTES, 'UTF-8').'<a 
class="'.htmlentities((string)((isset($in['moderationMwUiClass']) && 
is_array($in)) ? $in['moderationMwUiClass'] : null), ENT_QUOTES, 'UTF-8').' 
mw-ui-quiet"
-                                  
href="'.htmlentities((string)((isset($in['links']['topic-history']['url']) && 
is_array($in['links']['topic-history'])) ? $in['links']['topic-history']['url'] 
: null), ENT_QUOTES, 'UTF-8').'"
-                                  
title="'.htmlentities((string)((isset($in['links']['topic-history']['title']) 
&& is_array($in['links']['topic-history'])) ? 
$in['links']['topic-history']['title'] : null), ENT_QUOTES, 
'UTF-8').'">'.((LCRun3::ifvar($cx, ((isset($in['moderationIcons']) && 
is_array($in)) ? $in['moderationIcons'] : null))) ? '<span class="wikiglyph 
wikiglyph-clock"></span> ' : '').''.LCRun3::ch($cx, 'l10n', 
array(array(LCRun3::ch($cx, 'concat', 
array(array('flow-',((isset($in['moderationType']) && is_array($in)) ? 
$in['moderationType'] : null),'-action-history'),array()), 'raw')),array()), 
'encq').'</a>'.htmlentities((string)((isset($in['noop']) && is_array($in)) ? 
$in['noop'] : null), ENT_QUOTES, 'UTF-8').'</li>' : '').''.((LCRun3::ifvar($cx, 
((isset($in['links']['topic']) && is_array($in['links'])) ? 
$in['links']['topic'] : null))) ? 
'<li>'.htmlentities((string)((isset($in['noop']) && is_array($in)) ? 
$in['noop'] : null), ENT_QUOTES, 'UTF-8').'<a 
class="'.htmlentities((string)((isset($in['moderationMwUiClass']) && 
is_array($in)) ? $in['moderationMwUiClass'] : null), ENT_QUOTES, 'UTF-8').' 
mw-ui-quiet"
-                                  
href="'.htmlentities((string)((isset($in['links']['topic']['url']) && 
is_array($in['links']['topic'])) ? $in['links']['topic']['url'] : null), 
ENT_QUOTES, 'UTF-8').'"
-                                  
title="'.htmlentities((string)((isset($in['links']['topic']['title']) && 
is_array($in['links']['topic'])) ? $in['links']['topic']['title'] : null), 
ENT_QUOTES, 'UTF-8').'">'.((LCRun3::ifvar($cx, ((isset($in['moderationIcons']) 
&& is_array($in)) ? $in['moderationIcons'] : null))) ? '<span class="wikiglyph 
wikiglyph-link"></span> ' : '').''.LCRun3::ch($cx, 'l10n', 
array(array(LCRun3::ch($cx, 'concat', 
array(array('flow-',((isset($in['moderationType']) && is_array($in)) ? 
$in['moderationType'] : null),'-action-view'),array()), 'raw')),array()), 
'encq').'</a>'.htmlentities((string)((isset($in['noop']) && is_array($in)) ? 
$in['noop'] : null), ENT_QUOTES, 'UTF-8').'</li>' : '').''.((LCRun3::ifvar($cx, 
((isset($in['actions']['summarize']) && is_array($in['actions'])) ? 
$in['actions']['summarize'] : null))) ? 
'<li>'.htmlentities((string)((isset($in['noop']) && is_array($in)) ? 
$in['noop'] : null), ENT_QUOTES, 'UTF-8').'<a 
class="'.htmlentities((string)((isset($in['moderationMwUiClass']) && 
is_array($in)) ? $in['moderationMwUiClass'] : null), ENT_QUOTES, 'UTF-8').' 
mw-ui-progressive mw-ui-quiet"
-                                  data-flow-interactive-handler="apiRequest"
-                                  
data-flow-api-handler="activateSummarizeTopic"
-                                  data-flow-api-target="< .flow-topic-titlebar 
.flow-topic-summary-container"
-                                  
href="'.htmlentities((string)((isset($in['actions']['summarize']['url']) && 
is_array($in['actions']['summarize'])) ? $in['actions']['summarize']['url'] : 
null), ENT_QUOTES, 'UTF-8').'"
-                                  
title="'.htmlentities((string)((isset($in['actions']['summarize']['title']) && 
is_array($in['actions']['summarize'])) ? $in['actions']['summarize']['title'] : 
null), ENT_QUOTES, 'UTF-8').'">'.((LCRun3::ifvar($cx, 
((isset($in['moderationIcons']) && is_array($in)) ? $in['moderationIcons'] : 
null))) ? '<span class="wikiglyph wikiglyph-stripe-toc"></span> ' : 
'').''.((LCRun3::ifvar($cx, ((isset($in['summary']) && is_array($in)) ? 
$in['summary'] : null))) ? ''.LCRun3::ch($cx, 'l10n', 
array(array(LCRun3::ch($cx, 'concat', 
array(array('flow-',((isset($in['moderationType']) && is_array($in)) ? 
$in['moderationType'] : null),'-action-resummarize-topic'),array()), 
'raw')),array()), 'encq').'' : ''.LCRun3::ch($cx, 'l10n', 
array(array(LCRun3::ch($cx, 'concat', 
array(array('flow-',((isset($in['moderationType']) && is_array($in)) ? 
$in['moderationType'] : null),'-action-summarize-topic'),array()), 
'raw')),array()), 'encq').'').'</a>'.htmlentities((string)((isset($in['noop']) 
&& is_array($in)) ? $in['noop'] : null), ENT_QUOTES, 'UTF-8').'</li>' : 
'').'';}).''.LCRun3::hbch($cx, 'ifCond', 
array(array(((isset($in['moderationType']) && is_array($in)) ? 
$in['moderationType'] : null),'===','post'),array()), $in, false, function($cx, 
$in) {return ''.((LCRun3::ifvar($cx, ((isset($in['actions']['edit']) && 
is_array($in['actions'])) ? $in['actions']['edit'] : null))) ? '<li>
-                               <a 
class="'.htmlentities((string)((isset($in['moderationMwUiClass']) && 
is_array($in)) ? $in['moderationMwUiClass'] : null), ENT_QUOTES, 'UTF-8').' 
mw-ui-progressive mw-ui-quiet"
-                                  
href="'.htmlentities((string)((isset($in['actions']['edit']['url']) && 
is_array($in['actions']['edit'])) ? $in['actions']['edit']['url'] : null), 
ENT_QUOTES, 'UTF-8').'"
-                                  
title="'.htmlentities((string)((isset($in['actions']['edit']['title']) && 
is_array($in['actions']['edit'])) ? $in['actions']['edit']['title'] : null), 
ENT_QUOTES, 'UTF-8').'"
-                                  data-flow-api-handler="activateEditPost"
-                                  data-flow-api-target="< .flow-post-main"
-                                  data-flow-interactive-handler="apiRequest"
-                               >'.((LCRun3::ifvar($cx, 
((isset($in['moderationIcons']) && is_array($in)) ? $in['moderationIcons'] : 
null))) ? '<span class="wikiglyph wikiglyph-pencil"></span> ' : 
'').''.LCRun3::ch($cx, 'l10n', 
array(array('flow-post-action-edit-post'),array()), 'encq').'</a>
-                       </li>' : '').''.((LCRun3::ifvar($cx, 
((isset($in['links']['post']) && is_array($in['links'])) ? $in['links']['post'] 
: null))) ? '<li>'.htmlentities((string)((isset($in['noop']) && is_array($in)) 
? $in['noop'] : null), ENT_QUOTES, 'UTF-8').'<a 
class="'.htmlentities((string)((isset($in['moderationMwUiClass']) && 
is_array($in)) ? $in['moderationMwUiClass'] : null), ENT_QUOTES, 'UTF-8').' 
mw-ui-quiet"
-                                  
href="'.htmlentities((string)((isset($in['links']['post']['url']) && 
is_array($in['links']['post'])) ? $in['links']['post']['url'] : null), 
ENT_QUOTES, 'UTF-8').'"
-                                  
title="'.htmlentities((string)((isset($in['links']['post']['title']) && 
is_array($in['links']['post'])) ? $in['links']['post']['title'] : null), 
ENT_QUOTES, 'UTF-8').'">'.((LCRun3::ifvar($cx, ((isset($in['moderationIcons']) 
&& is_array($in)) ? $in['moderationIcons'] : null))) ? '<span class="wikiglyph 
wikiglyph-link"></span> ' : '').''.LCRun3::ch($cx, 'l10n', 
array(array(LCRun3::ch($cx, 'concat', 
array(array('flow-',((isset($in['moderationType']) && is_array($in)) ? 
$in['moderationType'] : null),'-action-view'),array()), 'raw')),array()), 
'encq').'</a>'.htmlentities((string)((isset($in['noop']) && is_array($in)) ? 
$in['noop'] : null), ENT_QUOTES, 'UTF-8').'</li>' : '').'';}).'</section>
-
-<section>'.LCRun3::hbch($cx, 'ifCond', 
array(array(((isset($in['moderationType']) && is_array($in)) ? 
$in['moderationType'] : null),'===','history'),array()), $in, false, 
function($cx, $in) {return ''.((LCRun3::ifvar($cx, 
((isset($in['actions']['undo']) && is_array($in['actions'])) ? 
$in['actions']['undo'] : null))) ? 
'<li>'.htmlentities((string)((isset($in['noop']) && is_array($in)) ? 
$in['noop'] : null), ENT_QUOTES, 'UTF-8').'<a 
class="'.htmlentities((string)((isset($in['moderationMwUiClass']) && 
is_array($in)) ? $in['moderationMwUiClass'] : null), ENT_QUOTES, 'UTF-8').' 
mw-ui-quiet"
-                                  
href="'.htmlentities((string)((isset($in['actions']['undo']['url']) && 
is_array($in['actions']['undo'])) ? $in['actions']['undo']['url'] : null), 
ENT_QUOTES, 'UTF-8').'"
-                               
>'.htmlentities((string)((isset($in['actions']['undo']['title']) && 
is_array($in['actions']['undo'])) ? $in['actions']['undo']['title'] : null), 
ENT_QUOTES, 'UTF-8').'</a>'.htmlentities((string)((isset($in['noop']) && 
is_array($in)) ? $in['noop'] : null), ENT_QUOTES, 'UTF-8').'</li>' : 
'').'';}).''.((LCRun3::ifvar($cx, ((isset($in['actions']['hide']) && 
is_array($in['actions'])) ? $in['actions']['hide'] : null))) ? 
'<li>'.htmlentities((string)((isset($in['noop']) && is_array($in)) ? 
$in['noop'] : null), ENT_QUOTES, 'UTF-8').'<a 
class="'.htmlentities((string)((isset($in['moderationMwUiClass']) && 
is_array($in)) ? $in['moderationMwUiClass'] : null), ENT_QUOTES, 'UTF-8').' 
mw-ui-quiet"
-                          
href="'.htmlentities((string)((isset($in['actions']['hide']['url']) && 
is_array($in['actions']['hide'])) ? $in['actions']['hide']['url'] : null), 
ENT_QUOTES, 'UTF-8').'"
-                          
title="'.htmlentities((string)((isset($in['actions']['hide']['title']) && 
is_array($in['actions']['hide'])) ? $in['actions']['hide']['title'] : null), 
ENT_QUOTES, 'UTF-8').'"
-                          data-flow-interactive-handler="moderationDialog"
-                          
data-flow-template="flow_moderate_'.htmlentities((string)((isset($in['moderationTemplate'])
 && is_array($in)) ? $in['moderationTemplate'] : null), ENT_QUOTES, 
'UTF-8').'.partial"
-                          data-role="hide">'.((LCRun3::ifvar($cx, 
((isset($in['moderationIcons']) && is_array($in)) ? $in['moderationIcons'] : 
null))) ? '<span class="wikiglyph wikiglyph-flag"></span> ' : 
'').''.LCRun3::ch($cx, 'l10n', array(array(LCRun3::ch($cx, 'concat', 
array(array('flow-',((isset($in['moderationType']) && is_array($in)) ? 
$in['moderationType'] : 
null),'-action-hide-',((isset($in['moderationTemplate']) && is_array($in)) ? 
$in['moderationTemplate'] : null)),array()), 'raw')),array()), 
'encq').'</a>'.htmlentities((string)((isset($in['noop']) && is_array($in)) ? 
$in['noop'] : null), ENT_QUOTES, 'UTF-8').'</li>' : '').''.((LCRun3::ifvar($cx, 
((isset($in['actions']['unhide']) && is_array($in['actions'])) ? 
$in['actions']['unhide'] : null))) ? 
'<li>'.htmlentities((string)((isset($in['noop']) && is_array($in)) ? 
$in['noop'] : null), ENT_QUOTES, 'UTF-8').'<a 
class="'.htmlentities((string)((isset($in['moderationMwUiClass']) && 
is_array($in)) ? $in['moderationMwUiClass'] : null), ENT_QUOTES, 'UTF-8').' 
mw-ui-quiet"
-                          
href="'.htmlentities((string)((isset($in['actions']['unhide']['url']) && 
is_array($in['actions']['unhide'])) ? $in['actions']['unhide']['url'] : null), 
ENT_QUOTES, 'UTF-8').'"
-                          
title="'.htmlentities((string)((isset($in['actions']['unhide']['title']) && 
is_array($in['actions']['unhide'])) ? $in['actions']['unhide']['title'] : 
null), ENT_QUOTES, 'UTF-8').'"
-                          data-flow-interactive-handler="moderationDialog"
-                          
data-flow-template="flow_moderate_'.htmlentities((string)((isset($in['moderationTemplate'])
 && is_array($in)) ? $in['moderationTemplate'] : null), ENT_QUOTES, 
'UTF-8').'.partial"
-                          data-role="unhide">'.((LCRun3::ifvar($cx, 
((isset($in['moderationIcons']) && is_array($in)) ? $in['moderationIcons'] : 
null))) ? '<span class="wikiglyph wikiglyph-flag"></span> ' : 
'').''.LCRun3::ch($cx, 'l10n', array(array(LCRun3::ch($cx, 'concat', 
array(array('flow-',((isset($in['moderationType']) && is_array($in)) ? 
$in['moderationType'] : 
null),'-action-unhide-',((isset($in['moderationTemplate']) && is_array($in)) ? 
$in['moderationTemplate'] : null)),array()), 'raw')),array()), 
'encq').'</a>'.htmlentities((string)((isset($in['noop']) && is_array($in)) ? 
$in['noop'] : null), ENT_QUOTES, 'UTF-8').'</li>' : '').''.((LCRun3::ifvar($cx, 
((isset($in['actions']['delete']) && is_array($in['actions'])) ? 
$in['actions']['delete'] : null))) ? 
'<li>'.htmlentities((string)((isset($in['noop']) && is_array($in)) ? 
$in['noop'] : null), ENT_QUOTES, 'UTF-8').'<a 
class="'.htmlentities((string)((isset($in['moderationMwUiClass']) && 
is_array($in)) ? $in['moderationMwUiClass'] : null), ENT_QUOTES, 'UTF-8').' 
mw-ui-destructive mw-ui-quiet"
-                          
href="'.htmlentities((string)((isset($in['actions']['delete']['url']) && 
is_array($in['actions']['delete'])) ? $in['actions']['delete']['url'] : null), 
ENT_QUOTES, 'UTF-8').'"
-                          
title="'.htmlentities((string)((isset($in['actions']['delete']['title']) && 
is_array($in['actions']['delete'])) ? $in['actions']['delete']['title'] : 
null), ENT_QUOTES, 'UTF-8').'"
-                          data-flow-interactive-handler="moderationDialog"
-                          
data-flow-template="flow_moderate_'.htmlentities((string)((isset($in['moderationTemplate'])
 && is_array($in)) ? $in['moderationTemplate'] : null), ENT_QUOTES, 
'UTF-8').'.partial"
-                          data-role="delete">'.((LCRun3::ifvar($cx, 
((isset($in['moderationIcons']) && is_array($in)) ? $in['moderationIcons'] : 
null))) ? '<span class="wikiglyph wikiglyph-trash"></span> ' : 
'').''.LCRun3::ch($cx, 'l10n', array(array(LCRun3::ch($cx, 'concat', 
array(array('flow-',((isset($in['moderationType']) && is_array($in)) ? 
$in['moderationType'] : 
null),'-action-delete-',((isset($in['moderationTemplate']) && is_array($in)) ? 
$in['moderationTemplate'] : null)),array()), 'raw')),array()), 
'encq').'</a>'.htmlentities((string)((isset($in['noop']) && is_array($in)) ? 
$in['noop'] : null), ENT_QUOTES, 'UTF-8').'</li>' : '').''.((LCRun3::ifvar($cx, 
((isset($in['actions']['undelete']) && is_array($in['actions'])) ? 
$in['actions']['undelete'] : null))) ? 
'<li>'.htmlentities((string)((isset($in['noop']) && is_array($in)) ? 
$in['noop'] : null), ENT_QUOTES, 'UTF-8').'<a 
class="'.htmlentities((string)((isset($in['moderationMwUiClass']) && 
is_array($in)) ? $in['moderationMwUiClass'] : null), ENT_QUOTES, 'UTF-8').' 
mw-ui-destructive mw-ui-quiet"
-                          
href="'.htmlentities((string)((isset($in['actions']['undelete']['url']) && 
is_array($in['actions']['undelete'])) ? $in['actions']['undelete']['url'] : 
null), ENT_QUOTES, 'UTF-8').'"
-                          
title="'.htmlentities((string)((isset($in['actions']['undelete']['title']) && 
is_array($in['actions']['undelete'])) ? $in['actions']['undelete']['title'] : 
null), ENT_QUOTES, 'UTF-8').'"
-                          data-flow-interactive-handler="moderationDialog"
-                          
data-flow-template="flow_moderate_'.htmlentities((string)((isset($in['moderationTemplate'])
 && is_array($in)) ? $in['moderationTemplate'] : null), ENT_QUOTES, 
'UTF-8').'.partial"
-                          data-role="undelete">'.((LCRun3::ifvar($cx, 
((isset($in['moderationIcons']) && is_array($in)) ? $in['moderationIcons'] : 
null))) ? '<span class="wikiglyph wikiglyph-trash"></span> ' : 
'').''.LCRun3::ch($cx, 'l10n', array(array(LCRun3::ch($cx, 'concat', 
array(array('flow-',((isset($in['moderationType']) && is_array($in)) ? 
$in['moderationType'] : 
null),'-action-undelete-',((isset($in['moderationTemplate']) && is_array($in)) 
? $in['moderationTemplate'] : null)),array()), 'raw')),array()), 
'encq').'</a>'.htmlentities((string)((isset($in['noop']) && is_array($in)) ? 
$in['noop'] : null), ENT_QUOTES, 'UTF-8').'</li>' : '').''.((LCRun3::ifvar($cx, 
((isset($in['actions']['suppress']) && is_array($in['actions'])) ? 
$in['actions']['suppress'] : null))) ? 
'<li>'.htmlentities((string)((isset($in['noop']) && is_array($in)) ? 
$in['noop'] : null), ENT_QUOTES, 'UTF-8').'<a 
class="'.htmlentities((string)((isset($in['moderationMwUiClass']) && 
is_array($in)) ? $in['moderationMwUiClass'] : null), ENT_QUOTES, 'UTF-8').' 
mw-ui-destructive mw-ui-quiet"
-                          
href="'.htmlentities((string)((isset($in['actions']['suppress']['url']) && 
is_array($in['actions']['suppress'])) ? $in['actions']['suppress']['url'] : 
null), ENT_QUOTES, 'UTF-8').'"
-                          
title="'.htmlentities((string)((isset($in['actions']['suppress']['title']) && 
is_array($in['actions']['suppress'])) ? $in['actions']['suppress']['title'] : 
null), ENT_QUOTES, 'UTF-8').'"
-                          data-flow-interactive-handler="moderationDialog"
-                          
data-flow-template="flow_moderate_'.htmlentities((string)((isset($in['moderationTemplate'])
 && is_array($in)) ? $in['moderationTemplate'] : null), ENT_QUOTES, 
'UTF-8').'.partial"
-                          data-role="suppress">'.((LCRun3::ifvar($cx, 
((isset($in['moderationIcons']) && is_array($in)) ? $in['moderationIcons'] : 
null))) ? '<span class="wikiglyph wikiglyph-block"></span> ' : 
'').''.LCRun3::ch($cx, 'l10n', array(array(LCRun3::ch($cx, 'concat', 
array(array('flow-',((isset($in['moderationType']) && is_array($in)) ? 
$in['moderationType'] : 
null),'-action-suppress-',((isset($in['moderationTemplate']) && is_array($in)) 
? $in['moderationTemplate'] : null)),array()), 'raw')),array()), 
'encq').'</a>'.htmlentities((string)((isset($in['noop']) && is_array($in)) ? 
$in['noop'] : null), ENT_QUOTES, 'UTF-8').'</li>' : '').''.((LCRun3::ifvar($cx, 
((isset($in['actions']['unsuppress']) && is_array($in['actions'])) ? 
$in['actions']['unsuppress'] : null))) ? 
'<li>'.htmlentities((string)((isset($in['noop']) && is_array($in)) ? 
$in['noop'] : null), ENT_QUOTES, 'UTF-8').'<a 
class="'.htmlentities((string)((isset($in['moderationMwUiClass']) && 
is_array($in)) ? $in['moderationMwUiClass'] : null), ENT_QUOTES, 'UTF-8').' 
mw-ui-destructive mw-ui-quiet"
-                          
href="'.htmlentities((string)((isset($in['actions']['unsuppress']['url']) && 
is_array($in['actions']['unsuppress'])) ? $in['actions']['unsuppress']['url'] : 
null), ENT_QUOTES, 'UTF-8').'"
-                          
title="'.htmlentities((string)((isset($in['actions']['unsuppress']['title']) && 
is_array($in['actions']['unsuppress'])) ? $in['actions']['unsuppress']['title'] 
: null), ENT_QUOTES, 'UTF-8').'"
-                          data-flow-interactive-handler="moderationDialog"
-                          
data-flow-template="flow_moderate_'.htmlentities((string)((isset($in['moderationTemplate'])
 && is_array($in)) ? $in['moderationTemplate'] : null), ENT_QUOTES, 
'UTF-8').'.partial"
-                          data-role="unsuppress">'.((LCRun3::ifvar($cx, 
((isset($in['moderationIcons']) && is_array($in)) ? $in['moderationIcons'] : 
null))) ? '<span class="wikiglyph wikiglyph-block"></span> ' : 
'').''.LCRun3::ch($cx, 'l10n', array(array(LCRun3::ch($cx, 'concat', 
array(array('flow-',((isset($in['moderationType']) && is_array($in)) ? 
$in['moderationType'] : 
null),'-action-unsuppress-',((isset($in['moderationTemplate']) && 
is_array($in)) ? $in['moderationTemplate'] : null)),array()), 'raw')),array()), 
'encq').'</a>'.htmlentities((string)((isset($in['noop']) && is_array($in)) ? 
$in['noop'] : null), ENT_QUOTES, 'UTF-8').'</li>' : '').''.LCRun3::hbch($cx, 
'ifCond', array(array(((isset($in['moderationType']) && is_array($in)) ? 
$in['moderationType'] : null),'===','history'),array()), $in, false, 
function($cx, $in) {return ''.((LCRun3::ifvar($cx, 
((isset($in['actions']['lock']) && is_array($in['actions'])) ? 
$in['actions']['lock'] : null))) ? 
'<li>'.htmlentities((string)((isset($in['noop']) && is_array($in)) ? 
$in['noop'] : null), ENT_QUOTES, 'UTF-8').'<a 
class="'.htmlentities((string)((isset($in['moderationMwUiClass']) && 
is_array($in)) ? $in['moderationMwUiClass'] : null), ENT_QUOTES, 'UTF-8').' 
mw-ui-destructive mw-ui-quiet"
-                                  
data-flow-interactive-handler="moderationDialog"
-                                  
data-flow-template="flow_moderate_topic.partial"
-                                  data-role="lock"
-                                  
data-flow-id="'.htmlentities((string)((isset($in['postId']) && is_array($in)) ? 
$in['postId'] : null), ENT_QUOTES, 'UTF-8').'"
-                                  
href="'.htmlentities((string)((isset($in['actions']['lock']['url']) && 
is_array($in['actions']['lock'])) ? $in['actions']['lock']['url'] : null), 
ENT_QUOTES, 'UTF-8').'"
-                                  
title="'.htmlentities((string)((isset($in['actions']['lock']['title']) && 
is_array($in['actions']['lock'])) ? $in['actions']['lock']['title'] : null), 
ENT_QUOTES, 'UTF-8').'">'.((LCRun3::ifvar($cx, ((isset($in['moderationIcons']) 
&& is_array($in)) ? $in['moderationIcons'] : null))) ? '<span class="wikiglyph 
wikiglyph-lock"></span> ' : '').''.LCRun3::ch($cx, 'l10n', 
array(array(LCRun3::ch($cx, 'concat', 
array(array('flow-',((isset($in['moderationType']) && is_array($in)) ? 
$in['moderationType'] : null),'-action-lock-topic'),array()), 'raw')),array()), 
'encq').'</a>'.htmlentities((string)((isset($in['noop']) && is_array($in)) ? 
$in['noop'] : null), ENT_QUOTES, 'UTF-8').'</li>' : '').''.((LCRun3::ifvar($cx, 
((isset($in['actions']['unlock']) && is_array($in['actions'])) ? 
$in['actions']['unlock'] : null))) ? 
'<li>'.htmlentities((string)((isset($in['noop']) && is_array($in)) ? 
$in['noop'] : null), ENT_QUOTES, 'UTF-8').'<a 
class="'.htmlentities((string)((isset($in['moderationMwUiClass']) && 
is_array($in)) ? $in['moderationMwUiClass'] : null), ENT_QUOTES, 'UTF-8').' 
mw-ui-destructive mw-ui-quiet"
-                                  
data-flow-interactive-handler="moderationDialog"
-                                  
data-flow-template="flow_moderate_topic.partial"
-                                  data-role="unlock"
-                                  
data-flow-id="'.htmlentities((string)((isset($in['postId']) && is_array($in)) ? 
$in['postId'] : null), ENT_QUOTES, 'UTF-8').'"
-                                  
href="'.htmlentities((string)((isset($in['actions']['unlock']['url']) && 
is_array($in['actions']['unlock'])) ? $in['actions']['unlock']['url'] : null), 
ENT_QUOTES, 'UTF-8').'"
-                                  
title="'.htmlentities((string)((isset($in['actions']['unlock']['title']) && 
is_array($in['actions']['unlock'])) ? $in['actions']['unlock']['title'] : 
null), ENT_QUOTES, 'UTF-8').'">'.((LCRun3::ifvar($cx, 
((isset($in['moderationIcons']) && is_array($in)) ? $in['moderationIcons'] : 
null))) ? '<span class="wikiglyph wikiglyph-unlock"></span> ' : 
'').''.LCRun3::ch($cx, 'l10n', array(array(LCRun3::ch($cx, 'concat', 
array(array('flow-',((isset($in['moderationType']) && is_array($in)) ? 
$in['moderationType'] : null),'-action-unlock-topic'),array()), 
'raw')),array()), 'encq').'</a>'.htmlentities((string)((isset($in['noop']) && 
is_array($in)) ? $in['noop'] : null), ENT_QUOTES, 'UTF-8').'</li>' : '').'';}, 
function($cx, $in) {return ''.((LCRun3::ifvar($cx, 
((isset($in['actions']['lock']) && is_array($in['actions'])) ? 
$in['actions']['lock'] : null))) ? 
'<li>'.htmlentities((string)((isset($in['noop']) && is_array($in)) ? 
$in['noop'] : null), ENT_QUOTES, 'UTF-8').'<a 
class="'.htmlentities((string)((isset($in['moderationMwUiClass']) && 
is_array($in)) ? $in['moderationMwUiClass'] : null), ENT_QUOTES, 'UTF-8').' 
mw-ui-destructive mw-ui-quiet"
-                                  data-flow-interactive-handler="apiRequest"
-                                  data-flow-api-handler="activateLockTopic"
-                                  
data-flow-id="'.htmlentities((string)((isset($in['postId']) && is_array($in)) ? 
$in['postId'] : null), ENT_QUOTES, 'UTF-8').'"
-                                  data-flow-api-target="< .flow-topic-titlebar 
.flow-topic-summary-container"
-                                  
href="'.htmlentities((string)((isset($in['actions']['lock']['url']) && 
is_array($in['actions']['lock'])) ? $in['actions']['lock']['url'] : null), 
ENT_QUOTES, 'UTF-8').'"
-                                  
title="'.htmlentities((string)((isset($in['actions']['lock']['title']) && 
is_array($in['actions']['lock'])) ? $in['actions']['lock']['title'] : null), 
ENT_QUOTES, 'UTF-8').'">'.((LCRun3::ifvar($cx, ((isset($in['moderationIcons']) 
&& is_array($in)) ? $in['moderationIcons'] : null))) ? '<span class="wikiglyph 
wikiglyph-lock"></span> ' : '').''.LCRun3::ch($cx, 'l10n', 
array(array(LCRun3::ch($cx, 'concat', 
array(array('flow-',((isset($in['moderationType']) && is_array($in)) ? 
$in['moderationType'] : null),'-action-lock-topic'),array()), 'raw')),array()), 
'encq').'</a>'.htmlentities((string)((isset($in['noop']) && is_array($in)) ? 
$in['noop'] : null), ENT_QUOTES, 'UTF-8').'</li>' : '').''.((LCRun3::ifvar($cx, 
((isset($in['actions']['unlock']) && is_array($in['actions'])) ? 
$in['actions']['unlock'] : null))) ? 
'<li>'.htmlentities((string)((isset($in['noop']) && is_array($in)) ? 
$in['noop'] : null), ENT_QUOTES, 'UTF-8').'<a 
class="'.htmlentities((string)((isset($in['moderationMwUiClass']) && 
is_array($in)) ? $in['moderationMwUiClass'] : null), ENT_QUOTES, 'UTF-8').' 
mw-ui-destructive mw-ui-quiet"
-                                  data-flow-interactive-handler="apiRequest"
-                                  data-flow-api-handler="activateLockTopic"
-                                  
data-flow-id="'.htmlentities((string)((isset($in['postId']) && is_array($in)) ? 
$in['postId'] : null), ENT_QUOTES, 'UTF-8').'"
-                                  data-flow-api-target="< .flow-topic-titlebar 
.flow-topic-summary-container"
-                                  
href="'.htmlentities((string)((isset($in['actions']['unlock']['url']) && 
is_array($in['actions']['unlock'])) ? $in['actions']['unlock']['url'] : null), 
ENT_QUOTES, 'UTF-8').'"
-                                  
title="'.htmlentities((string)((isset($in['actions']['unlock']['title']) && 
is_array($in['actions']['unlock'])) ? $in['actions']['unlock']['title'] : 
null), ENT_QUOTES, 'UTF-8').'">'.((LCRun3::ifvar($cx, 
((isset($in['moderationIcons']) && is_array($in)) ? $in['moderationIcons'] : 
null))) ? '<span class="wikiglyph wikiglyph-unlock"></span> ' : 
'').''.LCRun3::ch($cx, 'l10n', array(array(LCRun3::ch($cx, 'concat', 
array(array('flow-',((isset($in['moderationType']) && is_array($in)) ? 
$in['moderationType'] : null),'-action-unlock-topic'),array()), 
'raw')),array()), 'encq').'</a>'.htmlentities((string)((isset($in['noop']) && 
is_array($in)) ? $in['noop'] : null), ENT_QUOTES, 'UTF-8').'</li>' : 
'').'';}).'</section>
-';},'flow_history_line' => function ($cx, $in) {return '<span 
class="flow-pipelist">
-       ('.htmlentities((string)((isset($in['noop']) && is_array($in)) ? 
$in['noop'] : null), ENT_QUOTES, 'UTF-8').'<span>'.((LCRun3::ifvar($cx, 
((isset($in['links']['diff-cur']) && is_array($in['links'])) ? 
$in['links']['diff-cur'] : null))) ? '<a 
href="'.htmlentities((string)((isset($in['links']['diff-cur']['url']) && 
is_array($in['links']['diff-cur'])) ? $in['links']['diff-cur']['url'] : null), 
ENT_QUOTES, 'UTF-8').'" 
title="'.htmlentities((string)((isset($in['links']['diff-cur']['title']) && 
is_array($in['links']['diff-cur'])) ? $in['links']['diff-cur']['title'] : 
null), ENT_QUOTES, 
'UTF-8').'">'.htmlentities((string)((isset($in['links']['diff-cur']['text']) && 
is_array($in['links']['diff-cur'])) ? $in['links']['diff-cur']['text'] : null), 
ENT_QUOTES, 'UTF-8').'</a>' : ''.LCRun3::ch($cx, 'l10n', 
array(array('cur'),array()), 'encq').'').'</span>
-       <span>
-'.((LCRun3::ifvar($cx, ((isset($in['links']['diff-prev']) && 
is_array($in['links'])) ? $in['links']['diff-prev'] : null))) ? '               
  <a href="'.htmlentities((string)((isset($in['links']['diff-prev']['url']) && 
is_array($in['links']['diff-prev'])) ? $in['links']['diff-prev']['url'] : 
null), ENT_QUOTES, 'UTF-8').'" 
title="'.htmlentities((string)((isset($in['links']['diff-prev']['title']) && 
is_array($in['links']['diff-prev'])) ? $in['links']['diff-prev']['title'] : 
null), ENT_QUOTES, 
'UTF-8').'">'.htmlentities((string)((isset($in['links']['diff-prev']['text']) 
&& is_array($in['links']['diff-prev'])) ? $in['links']['diff-prev']['text'] : 
null), ENT_QUOTES, 'UTF-8').'</a>' : ''.LCRun3::ch($cx, 'l10n', 
array(array('last'),array()), 'encq').'').'</span>'.((LCRun3::ifvar($cx, 
((isset($in['links']['topic']) && is_array($in['links'])) ? 
$in['links']['topic'] : null))) ? '          <span><a 
href="'.htmlentities((string)((isset($in['links']['topic']['url']) && 
is_array($in['links']['topic'])) ? $in['links']['topic']['url'] : null), 
ENT_QUOTES, 'UTF-8').'" 
title="'.htmlentities((string)((isset($in['links']['topic']['title']) && 
is_array($in['links']['topic'])) ? $in['links']['topic']['title'] : null), 
ENT_QUOTES, 
'UTF-8').'">'.htmlentities((string)((isset($in['links']['topic']['text']) && 
is_array($in['links']['topic'])) ? $in['links']['topic']['text'] : null), 
ENT_QUOTES, 'UTF-8').'</a></span>' : '').')
-</span>
-
-'.LCRun3::ch($cx, 'historyTimestamp', array(array($in),array()), 'encq').'
-
-<span class="mw-changeslist-separator">. .</span>
-'.LCRun3::ch($cx, 'historyDescription', array(array($in),array()), 'encq').'
-
-'.((LCRun3::ifvar($cx, ((isset($in['size']) && is_array($in)) ? $in['size'] : 
null))) ? '      <span class="mw-changeslist-separator">. .</span>
-       '.LCRun3::ch($cx, 'showCharacterDifference', 
array(array(((isset($in['size']['old']) && is_array($in['size'])) ? 
$in['size']['old'] : null),((isset($in['size']['new']) && 
is_array($in['size'])) ? $in['size']['new'] : null)),array()), 'encq').'
-' : '').'
-<ul class="flow-history-moderation-menu">
-'.LCRun3::p($cx, 'flow_moderation_actions_list', 
array(array($in),array('moderationType'=>'history','moderationTarget'=>'post','moderationTemplate'=>'post','moderationMwUiClass'=>'mw-ui-anchor','moderationIcons'=>false))).'</ul>
-';},),
-        'scopes' => array($in),
-        'sp_vars' => array('root' => $in),
-
-    );
-    
-    return '<div class="flow-board-history">
-       '.LCRun3::ch($cx, 'html', array(array(((isset($in['navbar']) && 
is_array($in)) ? $in['navbar'] : null)),array()), 'encq').'
-
-       <ul>
-'.LCRun3::sec($cx, ((isset($in['revisions']) && is_array($in)) ? 
$in['revisions'] : null), $in, true, function($cx, $in) {return '               
      <li>'.LCRun3::p($cx, 'flow_history_line', 
array(array($in),array())).'</li>
-';}).' </ul>
-
-       '.LCRun3::ch($cx, 'html', array(array(((isset($in['navbar']) && 
is_array($in)) ? $in['navbar'] : null)),array()), 'encq').'
-</div>
-';
-}
-?>
\ No newline at end of file
diff --git a/handlebars/compiled/flow_block_header_edit.handlebars.php 
b/handlebars/compiled/flow_block_header_edit.handlebars.php
index 7c27adb..ed7019e 100644
--- a/handlebars/compiled/flow_block_header_edit.handlebars.php
+++ b/handlebars/compiled/flow_block_header_edit.handlebars.php
@@ -25,15 +25,7 @@
 ';}).'         </ul>
        </div>
 ' : '').'</div>
-';},'flow_form_buttons' => function ($cx, $in) {return '<button 
data-flow-api-handler="preview"
-        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-form-action-preview flow-js"
-
->'.LCRun3::ch($cx, 'l10n', array(array('flow-preview'),array()), 
'encq').'</button>
-
-<button data-flow-interactive-handler="cancelForm"
+';},'flow_form_buttons' => function ($cx, $in) {return '<button 
data-flow-interactive-handler="cancelForm"
         data-role="cancel"
         type="reset"
         class="mw-ui-button mw-ui-destructive mw-ui-quiet mw-ui-flush-right 
flow-js"
diff --git a/handlebars/compiled/flow_block_header_undo_edit.handlebars.php 
b/handlebars/compiled/flow_block_header_undo_edit.handlebars.php
index e4870d6..26cebca 100644
--- a/handlebars/compiled/flow_block_header_undo_edit.handlebars.php
+++ b/handlebars/compiled/flow_block_header_undo_edit.handlebars.php
@@ -26,15 +26,7 @@
 ';}).'         </ul>
        </div>
 ' : '').'</div>
-';},'flow_form_buttons' => function ($cx, $in) {return '<button 
data-flow-api-handler="preview"
-        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-form-action-preview flow-js"
-
->'.LCRun3::ch($cx, 'l10n', array(array('flow-preview'),array()), 
'encq').'</button>
-
-<button data-flow-interactive-handler="cancelForm"
+';},'flow_form_buttons' => function ($cx, $in) {return '<button 
data-flow-interactive-handler="cancelForm"
         data-role="cancel"
         type="reset"
         class="mw-ui-button mw-ui-destructive mw-ui-quiet mw-ui-flush-right 
flow-js"
diff --git a/handlebars/compiled/flow_block_topic.handlebars.php 
b/handlebars/compiled/flow_block_topic.handlebars.php
index 5e09f77..1b4b4d5 100644
--- a/handlebars/compiled/flow_block_topic.handlebars.php
+++ b/handlebars/compiled/flow_block_topic.handlebars.php
@@ -171,15 +171,7 @@
 '.LCRun3::hbch($cx, 'progressiveEnhancement', array(array(),array()), $in, 
false, function($cx, $in) {return '         <div 
class="flow-anon-warning-desktop">
 '.LCRun3::hbch($cx, 'tooltip', 
array(array(),array('positionClass'=>'left','contextClass'=>'progressive','extraClass'=>'flow-form-collapsible','isBlock'=>true)),
 $in, false, function($cx, $in) {return ''.LCRun3::ch($cx, 'l10nParse', 
array(array('flow-anon-warning',LCRun3::ch($cx, 'linkWithReturnTo', 
array(array('Special:UserLogin'),array()), 'raw'),LCRun3::ch($cx, 
'linkWithReturnTo', array(array('Special:UserLogin/signup'),array()), 
'raw')),array()), 'encq').'';}).'         </div>
 ';}).'</div>
-';},'flow_form_buttons' => function ($cx, $in) {return '<button 
data-flow-api-handler="preview"
-        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-form-action-preview flow-js"
-
->'.LCRun3::ch($cx, 'l10n', array(array('flow-preview'),array()), 
'encq').'</button>
-
-<button data-flow-interactive-handler="cancelForm"
+';},'flow_form_buttons' => function ($cx, $in) {return '<button 
data-flow-interactive-handler="cancelForm"
         data-role="cancel"
         type="reset"
         class="mw-ui-button mw-ui-destructive mw-ui-quiet mw-ui-flush-right 
flow-js"
diff --git a/handlebars/compiled/flow_block_topic_lock.handlebars.php 
b/handlebars/compiled/flow_block_topic_lock.handlebars.php
index 456be5b..2a0dd0f 100644
--- a/handlebars/compiled/flow_block_topic_lock.handlebars.php
+++ b/handlebars/compiled/flow_block_topic_lock.handlebars.php
@@ -25,15 +25,7 @@
 ';}).'         </ul>
        </div>
 ' : '').'</div>
-';},'flow_form_buttons' => function ($cx, $in) {return '<button 
data-flow-api-handler="preview"
-        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-form-action-preview flow-js"
-
->'.LCRun3::ch($cx, 'l10n', array(array('flow-preview'),array()), 
'encq').'</button>
-
-<button data-flow-interactive-handler="cancelForm"
+';},'flow_form_buttons' => function ($cx, $in) {return '<button 
data-flow-interactive-handler="cancelForm"
         data-role="cancel"
         type="reset"
         class="mw-ui-button mw-ui-destructive mw-ui-quiet mw-ui-flush-right 
flow-js"
diff --git a/handlebars/compiled/flow_block_topic_moderate_post.handlebars.php 
b/handlebars/compiled/flow_block_topic_moderate_post.handlebars.php
index 0818ca7..ff8c4e4 100644
--- a/handlebars/compiled/flow_block_topic_moderate_post.handlebars.php
+++ b/handlebars/compiled/flow_block_topic_moderate_post.handlebars.php
@@ -216,15 +216,7 @@
 '.LCRun3::hbch($cx, 'progressiveEnhancement', array(array(),array()), $in, 
false, function($cx, $in) {return '         <div 
class="flow-anon-warning-desktop">
 '.LCRun3::hbch($cx, 'tooltip', 
array(array(),array('positionClass'=>'left','contextClass'=>'progressive','extraClass'=>'flow-form-collapsible','isBlock'=>true)),
 $in, false, function($cx, $in) {return ''.LCRun3::ch($cx, 'l10nParse', 
array(array('flow-anon-warning',LCRun3::ch($cx, 'linkWithReturnTo', 
array(array('Special:UserLogin'),array()), 'raw'),LCRun3::ch($cx, 
'linkWithReturnTo', array(array('Special:UserLogin/signup'),array()), 
'raw')),array()), 'encq').'';}).'         </div>
 ';}).'</div>
-';},'flow_form_buttons' => function ($cx, $in) {return '<button 
data-flow-api-handler="preview"
-        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-form-action-preview flow-js"
-
->'.LCRun3::ch($cx, 'l10n', array(array('flow-preview'),array()), 
'encq').'</button>
-
-<button data-flow-interactive-handler="cancelForm"
+';},'flow_form_buttons' => function ($cx, $in) {return '<button 
data-flow-interactive-handler="cancelForm"
         data-role="cancel"
         type="reset"
         class="mw-ui-button mw-ui-destructive mw-ui-quiet mw-ui-flush-right 
flow-js"
diff --git a/handlebars/compiled/flow_block_topic_moderate_topic.handlebars.php 
b/handlebars/compiled/flow_block_topic_moderate_topic.handlebars.php
index 2243a5a..b3cdc2f 100644
--- a/handlebars/compiled/flow_block_topic_moderate_topic.handlebars.php
+++ b/handlebars/compiled/flow_block_topic_moderate_topic.handlebars.php
@@ -216,15 +216,7 @@
 '.LCRun3::hbch($cx, 'progressiveEnhancement', array(array(),array()), $in, 
false, function($cx, $in) {return '         <div 
class="flow-anon-warning-desktop">
 '.LCRun3::hbch($cx, 'tooltip', 
array(array(),array('positionClass'=>'left','contextClass'=>'progressive','extraClass'=>'flow-form-collapsible','isBlock'=>true)),
 $in, false, function($cx, $in) {return ''.LCRun3::ch($cx, 'l10nParse', 
array(array('flow-anon-warning',LCRun3::ch($cx, 'linkWithReturnTo', 
array(array('Special:UserLogin'),array()), 'raw'),LCRun3::ch($cx, 
'linkWithReturnTo', array(array('Special:UserLogin/signup'),array()), 
'raw')),array()), 'encq').'';}).'         </div>
 ';}).'</div>
-';},'flow_form_buttons' => function ($cx, $in) {return '<button 
data-flow-api-handler="preview"
-        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-form-action-preview flow-js"
-
->'.LCRun3::ch($cx, 'l10n', array(array('flow-preview'),array()), 
'encq').'</button>
-
-<button data-flow-interactive-handler="cancelForm"
+';},'flow_form_buttons' => function ($cx, $in) {return '<button 
data-flow-interactive-handler="cancelForm"
         data-role="cancel"
         type="reset"
         class="mw-ui-button mw-ui-destructive mw-ui-quiet mw-ui-flush-right 
flow-js"
diff --git a/handlebars/compiled/flow_block_topic_undo_edit.handlebars.php 
b/handlebars/compiled/flow_block_topic_undo_edit.handlebars.php
index e0f9401..180c143 100644
--- a/handlebars/compiled/flow_block_topic_undo_edit.handlebars.php
+++ b/handlebars/compiled/flow_block_topic_undo_edit.handlebars.php
@@ -26,15 +26,7 @@
 ';}).'         </ul>
        </div>
 ' : '').'</div>
-';},'flow_form_buttons' => function ($cx, $in) {return '<button 
data-flow-api-handler="preview"
-        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-form-action-preview flow-js"
-
->'.LCRun3::ch($cx, 'l10n', array(array('flow-preview'),array()), 
'encq').'</button>
-
-<button data-flow-interactive-handler="cancelForm"
+';},'flow_form_buttons' => function ($cx, $in) {return '<button 
data-flow-interactive-handler="cancelForm"
         data-role="cancel"
         type="reset"
         class="mw-ui-button mw-ui-destructive mw-ui-quiet mw-ui-flush-right 
flow-js"
diff --git a/handlebars/compiled/flow_block_topiclist.handlebars.php 
b/handlebars/compiled/flow_block_topiclist.handlebars.php
index fddfc2b..2978e4b 100644
--- a/handlebars/compiled/flow_block_topiclist.handlebars.php
+++ b/handlebars/compiled/flow_block_topiclist.handlebars.php
@@ -98,15 +98,7 @@
 '.LCRun3::hbch($cx, 'progressiveEnhancement', array(array(),array()), $in, 
false, function($cx, $in) {return '         <div 
class="flow-anon-warning-desktop">
 '.LCRun3::hbch($cx, 'tooltip', 
array(array(),array('positionClass'=>'left','contextClass'=>'progressive','extraClass'=>'flow-form-collapsible','isBlock'=>true)),
 $in, false, function($cx, $in) {return ''.LCRun3::ch($cx, 'l10nParse', 
array(array('flow-anon-warning',LCRun3::ch($cx, 'linkWithReturnTo', 
array(array('Special:UserLogin'),array()), 'raw'),LCRun3::ch($cx, 
'linkWithReturnTo', array(array('Special:UserLogin/signup'),array()), 
'raw')),array()), 'encq').'';}).'         </div>
 ';}).'</div>
-';},'flow_form_buttons' => function ($cx, $in) {return '<button 
data-flow-api-handler="preview"
-        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-form-action-preview flow-js"
-
->'.LCRun3::ch($cx, 'l10n', array(array('flow-preview'),array()), 
'encq').'</button>
-
-<button data-flow-interactive-handler="cancelForm"
+';},'flow_form_buttons' => function ($cx, $in) {return '<button 
data-flow-interactive-handler="cancelForm"
         data-role="cancel"
         type="reset"
         class="mw-ui-button mw-ui-destructive mw-ui-quiet mw-ui-flush-right 
flow-js"
diff --git a/handlebars/compiled/flow_block_topiclist_newtopic.handlebars.php 
b/handlebars/compiled/flow_block_topiclist_newtopic.handlebars.php
index 12a169c..ca86f2f 100644
--- a/handlebars/compiled/flow_block_topiclist_newtopic.handlebars.php
+++ b/handlebars/compiled/flow_block_topiclist_newtopic.handlebars.php
@@ -36,15 +36,7 @@
 '.LCRun3::hbch($cx, 'progressiveEnhancement', array(array(),array()), $in, 
false, function($cx, $in) {return '         <div 
class="flow-anon-warning-desktop">
 '.LCRun3::hbch($cx, 'tooltip', 
array(array(),array('positionClass'=>'left','contextClass'=>'progressive','extraClass'=>'flow-form-collapsible','isBlock'=>true)),
 $in, false, function($cx, $in) {return ''.LCRun3::ch($cx, 'l10nParse', 
array(array('flow-anon-warning',LCRun3::ch($cx, 'linkWithReturnTo', 
array(array('Special:UserLogin'),array()), 'raw'),LCRun3::ch($cx, 
'linkWithReturnTo', array(array('Special:UserLogin/signup'),array()), 
'raw')),array()), 'encq').'';}).'         </div>
 ';}).'</div>
-';},'flow_form_buttons' => function ($cx, $in) {return '<button 
data-flow-api-handler="preview"
-        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-form-action-preview flow-js"
-
->'.LCRun3::ch($cx, 'l10n', array(array('flow-preview'),array()), 
'encq').'</button>
-
-<button data-flow-interactive-handler="cancelForm"
+';},'flow_form_buttons' => function ($cx, $in) {return '<button 
data-flow-interactive-handler="cancelForm"
         data-role="cancel"
         type="reset"
         class="mw-ui-button mw-ui-destructive mw-ui-quiet mw-ui-flush-right 
flow-js"
diff --git a/handlebars/compiled/flow_block_topicsummary_edit.handlebars.php 
b/handlebars/compiled/flow_block_topicsummary_edit.handlebars.php
index d95d09c..d2f1bb3 100644
--- a/handlebars/compiled/flow_block_topicsummary_edit.handlebars.php
+++ b/handlebars/compiled/flow_block_topicsummary_edit.handlebars.php
@@ -25,15 +25,7 @@
 ';}).'         </ul>
        </div>
 ' : '').'</div>
-';},'flow_form_buttons' => function ($cx, $in) {return '<button 
data-flow-api-handler="preview"
-        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-form-action-preview flow-js"
-
->'.LCRun3::ch($cx, 'l10n', array(array('flow-preview'),array()), 
'encq').'</button>
-
-<button data-flow-interactive-handler="cancelForm"
+';},'flow_form_buttons' => function ($cx, $in) {return '<button 
data-flow-interactive-handler="cancelForm"
         data-role="cancel"
         type="reset"
         class="mw-ui-button mw-ui-destructive mw-ui-quiet mw-ui-flush-right 
flow-js"
diff --git 
a/handlebars/compiled/flow_block_topicsummary_undo_edit.handlebars.php 
b/handlebars/compiled/flow_block_topicsummary_undo_edit.handlebars.php
index d6ea514..e5d39fb 100644
--- a/handlebars/compiled/flow_block_topicsummary_undo_edit.handlebars.php
+++ b/handlebars/compiled/flow_block_topicsummary_undo_edit.handlebars.php
@@ -26,15 +26,7 @@
 ';}).'         </ul>
        </div>
 ' : '').'</div>
-';},'flow_form_buttons' => function ($cx, $in) {return '<button 
data-flow-api-handler="preview"
-        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-form-action-preview flow-js"
-
->'.LCRun3::ch($cx, 'l10n', array(array('flow-preview'),array()), 
'encq').'</button>
-
-<button data-flow-interactive-handler="cancelForm"
+';},'flow_form_buttons' => function ($cx, $in) {return '<button 
data-flow-interactive-handler="cancelForm"
         data-role="cancel"
         type="reset"
         class="mw-ui-button mw-ui-destructive mw-ui-quiet mw-ui-flush-right 
flow-js"
diff --git a/handlebars/compiled/flow_post.handlebars.php 
b/handlebars/compiled/flow_post.handlebars.php
index ec4c238..28078b4 100644
--- a/handlebars/compiled/flow_post.handlebars.php
+++ b/handlebars/compiled/flow_post.handlebars.php
@@ -192,15 +192,7 @@
 '.LCRun3::hbch($cx, 'progressiveEnhancement', array(array(),array()), $in, 
false, function($cx, $in) {return '         <div 
class="flow-anon-warning-desktop">
 '.LCRun3::hbch($cx, 'tooltip', 
array(array(),array('positionClass'=>'left','contextClass'=>'progressive','extraClass'=>'flow-form-collapsible','isBlock'=>true)),
 $in, false, function($cx, $in) {return ''.LCRun3::ch($cx, 'l10nParse', 
array(array('flow-anon-warning',LCRun3::ch($cx, 'linkWithReturnTo', 
array(array('Special:UserLogin'),array()), 'raw'),LCRun3::ch($cx, 
'linkWithReturnTo', array(array('Special:UserLogin/signup'),array()), 
'raw')),array()), 'encq').'';}).'         </div>
 ';}).'</div>
-';},'flow_form_buttons' => function ($cx, $in) {return '<button 
data-flow-api-handler="preview"
-        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-form-action-preview flow-js"
-
->'.LCRun3::ch($cx, 'l10n', array(array('flow-preview'),array()), 
'encq').'</button>
-
-<button data-flow-interactive-handler="cancelForm"
+';},'flow_form_buttons' => function ($cx, $in) {return '<button 
data-flow-interactive-handler="cancelForm"
         data-role="cancel"
         type="reset"
         class="mw-ui-button mw-ui-destructive mw-ui-quiet mw-ui-flush-right 
flow-js"
diff --git a/handlebars/flow_editor_switcher.partial.handlebars 
b/handlebars/flow_editor_switcher.partial.handlebars
new file mode 100644
index 0000000..ad0f49c
--- /dev/null
+++ b/handlebars/flow_editor_switcher.partial.handlebars
@@ -0,0 +1,17 @@
+<div class="flow-switcher-controls">
+       <div>
+               {{! this message is manually constructed in 
ext.flow.editors.none.js !}}
+               <p class="flow-wikitext-editor-help">{{html help_text}}</p>
+
+               <a href="#"
+                  title="{{l10n "flow-wikitext-switch-editor-tooltip"}}"
+                  class="mw-ui-button mw-ui-constructive flow-js 
flow-editor-color"
+                  data-flow-interactive-handler="switchEditor"
+                  data-flow-load-handler="requiresVisualEditor"
+                  data-flow-target="< form textarea"
+               >
+                       &lt;/&gt;
+               </a>
+       </div>
+       <div class="flow-ui-clear"></div>
+</div>
diff --git a/handlebars/flow_form_buttons.partial.handlebars 
b/handlebars/flow_form_buttons.partial.handlebars
index 9eeabc7..540816f 100644
--- a/handlebars/flow_form_buttons.partial.handlebars
+++ b/handlebars/flow_form_buttons.partial.handlebars
@@ -1,14 +1,3 @@
-<button data-flow-api-handler="preview"
-        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-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 --}}
->
-       {{~l10n "flow-preview"~}}
-</button>
-
 <button data-flow-interactive-handler="cancelForm"
         data-role="cancel"
         type="reset"
diff --git a/i18n/en.json b/i18n/en.json
index 520c60b..85032a9 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -539,5 +539,10 @@
        "flow-ve-mention-inspector-remove-label": "Remove",
        "flow-ve-mention-tool-title": "Mention a user",
        "flow-ve-mention-template": "ping",
-       "flow-ve-mention-inspector-invalid-user": "The username '$1' is not 
registered."
+       "flow-ve-mention-inspector-invalid-user": "The username '$1' is not 
registered.",
+       "flow-wikitext-editor-help": "Wikitext $1 and you can $2 anytime.",
+       "flow-wikitext-editor-help-uses-wikitext": "[[mw:Help:Formatting|uses 
markup]]",
+       "flow-wikitext-editor-help-preview-the-result": "preview the result",
+       "flow-wikitext-switch-editor-tooltip": "Switch to VisualEditor",
+       "flow-ve-switch-editor-tool-title": "Switch to Wikitext editor"
 }
diff --git a/i18n/qqq.json b/i18n/qqq.json
index 1252820..91c52d2 100644
--- a/i18n/qqq.json
+++ b/i18n/qqq.json
@@ -541,5 +541,10 @@
        "flow-ve-mention-inspector-remove-label": "Text of remove button on 
Flow's user mention panel (inspector) in the VisualEditor.",
        "flow-ve-mention-tool-title": "Title text for the user mention tool on 
Flow's VisualEditor toolbar.",
        "flow-ve-mention-template": "Name of on-wiki template used for user 
mentions.  The template should accept a call in the form 
<nowiki>{{templatename|Username}}</nowiki>, to mention Username.  It will use 
content language.",
-       "flow-ve-mention-inspector-invalid-user": "Error shown when the poster 
attempts to mention a user that does not exist.  Parameters:\n$1: Username.  
The username is not registered; thus, gender is unknown."
+       "flow-ve-mention-inspector-invalid-user": "Error shown when the poster 
attempts to mention a user that does not exist.  Parameters:\n$1: Username.  
The username is not registered; thus, gender is unknown.",
+       "flow-wikitext-editor-help": "Text shown at the bottom of a wikitext 
editing box",
+       "flow-wikitext-editor-help-uses-wikitext": "Link to wikitext help. See 
also flow-wikitext-editor-help",
+       "flow-wikitext-editor-help-preview-the-result": "Text of link that will 
switch from wikitext editor to an html preview.  See also 
flow-wikitext-editor-help",
+       "flow-wikitext-switch-editor-tooltip": "Tooltip for button shown below 
a wikitext editor to switch to VisualEditor.",
+       "flow-ve-switch-editor-tool-title": "Tooltip for button in visualeditor 
toolbar to switch to the wikitext editor."
 }
diff --git a/modules/editor/editors/ext.flow.editors.none.js 
b/modules/editor/editors/ext.flow.editors.none.js
index 5d33993..06c33de 100644
--- a/modules/editor/editors/ext.flow.editors.none.js
+++ b/modules/editor/editors/ext.flow.editors.none.js
@@ -23,6 +23,8 @@
                // initialize at height of existing content & update on every 
keyup
                this.$node.keyup( this.autoExpand );
                this.autoExpand.call( this.$node.get( 0 ) );
+
+               this.attachSwitcher();
        };
 
        OO.inheritClass( mw.flow.editors.none, mw.flow.editors.AbstractEditor );
@@ -43,6 +45,8 @@
        mw.flow.editors.none.static.name = 'none';
 
        mw.flow.editors.none.prototype.destroy = function () {
+               // remove the help+switcher information
+               this.$node.siblings( '.flow-switcher-controls' ).remove();
                // unset min-height that was set for auto-expansion
                this.$node.css( 'min-height', '' );
                // unset height that was set by auto-expansion
@@ -116,6 +120,27 @@
                }
        };
 
+       mw.flow.editors.none.prototype.attachSwitcher = function() {
+               var board = mw.flow.getPrototypeMethod( 'board', 
'getInstanceByElement' )( this.$node ),
+                       $preview = $( '<a>' ).attr( {
+                               href: '#',
+                               'data-flow-interactive-handler': 'switchEditor',
+                               'data-flow-target': '< form textarea'
+                       } ).text( mw.message( 
'flow-wikitext-editor-help-preview-the-result' ).text() ),
+                       $switcher = $( 
mw.flow.TemplateEngine.processTemplateGetFragment(
+                               'flow_editor_switcher.partial',
+                               {
+                                       help_text: mw.message( 
'flow-wikitext-editor-help' ).params( [
+                                               mw.message( 
'flow-wikitext-editor-help-uses-wikitext' ).parse(),
+                                               $preview[0].outerHTML
+                                       ] ).parse()
+                               }
+                       ) ).children();
+
+               // insert help information + editor switcher, and make it 
interactive
+               board.emitWithReturn( 'makeContentInteractive', 
$switcher.insertAfter( this.$node ) );
+       };
+
        mw.flow.editors.none.prototype.focus = function() {
                return this.$node.focus();
        };
diff --git 
a/modules/editor/editors/visualeditor/ext.flow.editors.visualeditor.js 
b/modules/editor/editors/visualeditor/ext.flow.editors.visualeditor.js
index 997a348..fec83ee 100644
--- a/modules/editor/editors/visualeditor/ext.flow.editors.visualeditor.js
+++ b/modules/editor/editors/visualeditor/ext.flow.editors.visualeditor.js
@@ -99,8 +99,7 @@
 
        mw.flow.editors.visualeditor.prototype.destroy = function () {
                if ( this.target ) {
-                       this.target.surface.destroy();
-                       this.target.toolbar.destroy();
+                       this.target.destroy();
                }
 
                // re-display original node
diff --git a/modules/editor/editors/visualeditor/mw.flow.ve.CommandRegistry.js 
b/modules/editor/editors/visualeditor/mw.flow.ve.CommandRegistry.js
index bd0d452..e9c9079 100644
--- a/modules/editor/editors/visualeditor/mw.flow.ve.CommandRegistry.js
+++ b/modules/editor/editors/visualeditor/mw.flow.ve.CommandRegistry.js
@@ -10,4 +10,13 @@
                        { 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 098e3ea..8e8dc15 100644
--- a/modules/editor/editors/visualeditor/mw.flow.ve.Target.js
+++ b/modules/editor/editors/visualeditor/mw.flow.ve.Target.js
@@ -37,6 +37,12 @@
                { include: [ 'flowMention' ] }
        ];
 
+       mw.flow.ve.Target.static.actionGroups = [
+               { include: [ 'flowSwitchEditor' ] }
+       ];
+
+       // Methods
+
        mw.flow.ve.Target.prototype.attachToolbar = function() {
                this.getToolbar().$element.insertAfter( 
this.getToolbar().getSurface().$element );
        };
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
new file mode 100644
index 0000000..5d2c396
--- /dev/null
+++ 
b/modules/editor/editors/visualeditor/ui/actions/mw.flow.ve.ui.SwitchEditorAction.js
@@ -0,0 +1,55 @@
+( 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 $node = this.surface.$element.closest( 'form' ).find( 'textarea' );
+
+       mw.flow.editor.switchEditor( $node, 'none' );
+};
+
+ve.ui.actionFactory.register( mw.flow.ve.ui.SwitchEditorAction );
+
+}( mediaWiki, OO, ve ) );
diff --git 
a/modules/editor/editors/visualeditor/ui/images/icons/flow-switch-editor.svg 
b/modules/editor/editors/visualeditor/ui/images/icons/flow-switch-editor.svg
new file mode 100644
index 0000000..80561ac
--- /dev/null
+++ b/modules/editor/editors/visualeditor/ui/images/icons/flow-switch-editor.svg
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/";
+   xmlns:cc="http://creativecommons.org/ns#";
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#";
+   xmlns:svg="http://www.w3.org/2000/svg";
+   xmlns="http://www.w3.org/2000/svg";
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd";
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape";
+   version="1.1"
+   width="24"
+   height="24"
+   id="svg2"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="flow-switch-editor.svg">
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="640"
+     inkscape:window-height="480"
+     id="namedview3262"
+     showgrid="false"
+     inkscape:zoom="9.8333333"
+     inkscape:cx="12"
+     inkscape:cy="7.9322034"
+     inkscape:window-x="0"
+     inkscape:window-y="24"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg2" />
+  <defs
+     id="defs4" />
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage"; />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     transform="matrix(3.1730191,0,0,6.5378108,-0.51387626,-7.0405915)"
+     
style="font-size:4px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+     id="flowRoot3264">
+    <path
+       d="m 2.8339844,2.2369966 -2.01562502,0.7167969 2.01562502,0.7128906 
0,0.3554688 -2.50390627,-0.9082031 0,-0.3242188 2.50390627,-0.9082031 
0,0.3554687"
+       style=""
+       id="path3277" />
+    <path
+       d="m 4.2734375,1.289731 0.3320312,0 -1.0156249,3.2871094 -0.3320313,0 
1.015625,-3.2871094"
+       style=""
+       id="path3279" />
+    <path
+       d="m 5.0332031,2.2369966 0,-0.3554687 2.5039063,0.9082031 0,0.3242188 
-2.5039063,0.9082031 0,-0.3554688 L 7.0449219,2.9537935 5.0332031,2.2369966"
+       style=""
+       id="path3281" />
+  </g>
+</svg>
diff --git a/modules/editor/editors/visualeditor/ui/mw.flow.ve.ui.Icons.less 
b/modules/editor/editors/visualeditor/ui/mw.flow.ve.ui.Icons.less
index 7312a4c..4e38f7c 100644
--- a/modules/editor/editors/visualeditor/ui/mw.flow.ve.ui.Icons.less
+++ b/modules/editor/editors/visualeditor/ui/mw.flow.ve.ui.Icons.less
@@ -3,3 +3,7 @@
 .oo-ui-icon-flow-mention {
        .background-image('images/icons/flow-mention.svg');
 }
+
+.oo-ui-icon-flow-switch-editor {
+       .background-image('images/icons/flow-switch-editor.svg');
+}
diff --git 
a/modules/editor/editors/visualeditor/ui/tools/mw.flow.ve.ui.SwitchEditorTool.js
 
b/modules/editor/editors/visualeditor/ui/tools/mw.flow.ve.ui.SwitchEditorTool.js
new file mode 100644
index 0000000..865f888
--- /dev/null
+++ 
b/modules/editor/editors/visualeditor/ui/tools/mw.flow.ve.ui.SwitchEditorTool.js
@@ -0,0 +1,29 @@
+( function ( mw, OO, ve ) {
+       'use strict';
+
+       /**
+        * Tool for switching editors
+        *
+        * @class
+        * @extends ve.ui.Tool
+        *
+        * @constructor
+        * @param {OO.ui.ToolGroup} toolGroup
+        * @param {Object} [config] Configuration options
+        */
+
+       mw.flow.ve.ui.SwitchEditorTool = function FlowVeSwitchEditorTool( 
toolGroup, config ) {
+               mw.flow.ve.ui.SwitchEditorTool.parent.call( this, toolGroup, 
config );
+       };
+
+       OO.inheritClass( mw.flow.ve.ui.SwitchEditorTool, ve.ui.Tool );
+
+       // Static
+       mw.flow.ve.ui.SwitchEditorTool.static.commandName = 'flowSwitchEditor';
+       mw.flow.ve.ui.SwitchEditorTool.static.name = 'flowSwitchEditor';
+       mw.flow.ve.ui.SwitchEditorTool.static.icon = 'flow-switch-editor';
+       mw.flow.ve.ui.SwitchEditorTool.static.title = OO.ui.deferMsg( 
'flow-ve-switch-editor-tool-title' );
+
+
+       ve.ui.toolFactory.register( mw.flow.ve.ui.SwitchEditorTool );
+} ( mediaWiki, OO, ve ) );
diff --git a/modules/editor/ext.flow.editor.js 
b/modules/editor/ext.flow.editor.js
index b525d12..351fc7c 100644
--- a/modules/editor/ext.flow.editor.js
+++ b/modules/editor/ext.flow.editor.js
@@ -23,15 +23,18 @@
                editors: [null],
 
                init: function () {
+                       var editorList = mw.config.get( 'wgFlowEditorList' ),
+                               index = editorList.indexOf( 
mw.user.options.get( 'flow-editor' ) );
+
                        // determine editor instance to use, depending on 
availability
-                       mw.flow.editor.loadEditor();
+                       mw.flow.editor.loadEditor( index );
                },
 
                loadEditor: function ( editorIndex ) {
                        var editorList = mw.config.get( 'wgFlowEditorList' ),
                                editor;
 
-                       if ( !editorIndex ) {
+                       if ( !editorIndex || editorIndex < 0 || editorIndex >= 
editorList.length ) {
                                editorIndex = 0;
                        }
 
@@ -179,6 +182,71 @@
                        return mw.flow.editor.editors.hasOwnProperty( 
$node.data( 'flow-editor' ) );
                },
 
+               /**
+                * Changes the default editor to desiredEditor and converts 
$node to that
+                * type of editor.
+                *
+                * @todo Should support $node containing multiple editing 
nodes, such
+                *  as selecting all active editors in the page and switching 
all of
+                *  them to the desiredEditor. Currently you will need to 
$node.each()
+                *  and call switchEditor for each iteration.
+                *
+                * @param {jQuery} $node
+                * @return {jQuery.Promise} Will resolve once editor instance 
is loaded
+                */
+               switchEditor: function ( $node, desiredEditor ) {
+                       var content, format,
+                               editorList = mw.config.get( 'wgFlowEditorList' 
),
+                               editor = mw.flow.editor.getEditor( $node ),
+                               deferred = $.Deferred(),
+                               performSwitch = function () {
+                                       if ( 
mw.flow.editors[desiredEditor].static.isSupported() ) {
+                                               content = 
editor.getRawContent();
+                                               format = 
editor.constructor.static.format;
+
+                                               mw.flow.editor.editor = 
mw.flow.editors[desiredEditor];
+
+                                               mw.flow.editor.destroy( $node );
+                                               mw.flow.editor.load( $node, 
content, format );
+
+                                               deferred.resolve();
+                                       } else {
+                                               deferred.reject( 
'editor-not-supported' );
+                                       }
+                               };
+
+                       if ( !editor ) {
+                               // $node is not an editor
+                               deferred.reject( 'not-an-editor' );
+                       } else if ( editorList.indexOf( desiredEditor ) === -1 
) {
+                               // desiredEditor does not exist
+                               deferred.reject( 'unknown-editor-type' );
+                       } else {
+                               mw.loader.using( 'ext.flow.editors.' + 
desiredEditor ).then(
+                                       performSwitch,
+                                       function() {
+                                               deferred.reject( 
'fail-loading-editor' );
+                                       }
+                               );
+                       }
+
+                       deferred.then(
+                               function() {
+                                       if ( !mw.user.isAnon() ) {
+                                               // update the user preferences; 
no preferences for anons
+                                               new mw.Api().saveOption( 
'flow-editor', desiredEditor );
+                                               // ensure we also see that 
preference in the current page
+                                               mw.user.options.set( 
'flow-editor', desiredEditor );
+                                       }
+                               },
+                               function( rejectionCode ) {
+                                       mw.flow.debug( '[switchEditor] Could 
not switch to ' + desiredEditor + ' : ' + rejectionCode );
+                               }
+                       );
+
+                       return deferred.promise();
+               },
+
                focus: function( $node ) {
                        var editor = mw.flow.editor.getEditor( $node );
 
diff --git 
a/modules/engine/components/board/features/flow-board-switcheditor.js 
b/modules/engine/components/board/features/flow-board-switcheditor.js
new file mode 100644
index 0000000..b68de6f
--- /dev/null
+++ b/modules/engine/components/board/features/flow-board-switcheditor.js
@@ -0,0 +1,71 @@
+/*!
+ * Handlers for the switching the editor from wikitext to visualeditor
+ */
+
+( function ( $, mw ) {
+       /**
+        * Binds handlers for switching from wikitext to visualeditor
+        *
+        * @param {jQuery} $container
+        * @this FlowComponent
+        * @constructor
+        */
+       function FlowBoardComponentSwitchEditorFeatureMixin( $container ) {
+               // Bind element handlers
+               this.bindNodeHandlers( 
FlowBoardComponentSwitchEditorFeatureMixin.UI.events );
+       }
+       OO.initClass( FlowBoardComponentSwitchEditorFeatureMixin );
+
+       FlowBoardComponentSwitchEditorFeatureMixin.UI = {
+               events: {
+                       interactiveHandlers: {},
+                       loadHandlers: {}
+               }
+       };
+
+       /**
+        * Toggle between possible editors.
+        *
+        * Currently the only options are visualeditor, and none.  Visualeditor 
has its own
+        * code for switching, so this is only run by clicking the switch 
button from 'none'.
+        * If we add more editors later this will have to be revisited.
+        *
+        * @param {Event}
+        * @returns {$.Promise}
+        */
+       
FlowBoardComponentSwitchEditorFeatureMixin.UI.events.interactiveHandlers.switchEditor
 = function ( event ) {
+               var $this = $( this ),
+                       $target = $this.findWithParent( $this.data( 
'flow-target' ) );
+
+               event.preventDefault();
+
+               if ( !$target.length ) {
+                       mw.flow.debug( '[switchEditor] No target located' );
+                       return $.Deferred().reject().promise();
+               }
+
+               return mw.flow.editor.switchEditor( $target, 'visualeditor' );
+       };
+
+       /**
+        * Hide wikitext editor switchEditor controls on load if visualeditor 
is not available.
+        *
+        * @param {jQuery} event
+        */
+       
FlowBoardComponentSwitchEditorFeatureMixin.UI.events.loadHandlers.requiresVisualEditor
 = function ( $switcher ) {
+               if ( mw.config.get( 'wgFlowEditorList' ).indexOf( 
'visualeditor' ) === -1 ) {
+                       $switcher.hide();
+               } else {
+                       // this just pulls in a small bit of code, the full VE 
is not pulled in until
+                       // the editor is initialized.
+                       mw.loader.using( 'ext.flow.editors.visualeditor', 
function() {
+                               if ( 
!mw.flow.editors.visualeditor.static.isSupported() ) {
+                                       $switcher.hide();
+                               }
+                       } );
+               }
+       };
+
+       // Mixin to FlowComponent
+       mw.flow.mixinComponent( 'component', 
FlowBoardComponentSwitchEditorFeatureMixin );
+}( jQuery, mediaWiki ) );
diff --git a/modules/styles/board/editor-switcher.less 
b/modules/styles/board/editor-switcher.less
new file mode 100644
index 0000000..5c52d56
--- /dev/null
+++ b/modules/styles/board/editor-switcher.less
@@ -0,0 +1,52 @@
+@import 'mediawiki.mixins';
+@import 'mediawiki.ui/variables';
+@import 'flow.colors';
+
+// extra specificity is needed to override .mw-ui-button.mw-ui-*
+.mw-ui-button.flow-editor-color {
+       .flow-editor-none & {
+               color: white;
+               background-color: @colorConstructive;
+       }
+
+       .flow-editor-visualeditor & {
+               color: @colorText;
+               background-color: white;
+       }
+}
+
+.flow-editor {
+       .flow-switcher-controls {
+               background-color: white;
+               border: 1px solid @colorFieldBorder;
+               border-top: 0;
+               padding: .25em;
+       }
+
+       textarea {
+               border-bottom: 0;
+               border-bottom-left-radius: 0;
+               border-bottom-right-radius: 0;
+               margin-bottom: 0;
+       }
+
+       // would prefer textarea:not(.flow-input-compressed) above, but ie8 
wont do it
+       // so here we re-apply the border from .mw-ui-input that was removed 
above.
+       textarea.flow-input-compressed {
+               border-bottom: 1px solid @colorFieldBorder;
+       }
+
+       // @todo this is basically the terms of use, come up with a shared
+       // name for all but the float
+       .flow-wikitext-editor-help {
+               float: left;
+               vertical-align: middle;
+               color: @colorTextLight;
+               font-size: .75em;
+               line-height: 1.4;
+       }
+
+       a.flow-editor-color {
+               float: right;
+       }
+}

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

Gerrit-MessageType: merged
Gerrit-Change-Id: Ibed8693bfd825819aa53fec61385303b29cecfb1
Gerrit-PatchSet: 24
Gerrit-Project: mediawiki/extensions/Flow
Gerrit-Branch: master
Gerrit-Owner: EBernhardson <[email protected]>
Gerrit-Reviewer: EBernhardson <[email protected]>
Gerrit-Reviewer: Krinkle <[email protected]>
Gerrit-Reviewer: Mattflaschen <[email protected]>
Gerrit-Reviewer: Matthias Mullie <[email protected]>
Gerrit-Reviewer: SG <[email protected]>
Gerrit-Reviewer: Siebrand <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

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

Reply via email to