jenkins-bot has submitted this change and it was merged. ( 
https://gerrit.wikimedia.org/r/343878 )

Change subject: VisualEditor source mode support
......................................................................


VisualEditor source mode support

Long-term todo:
* Performance will be poor on large pages due
  to using a auto-height textarea which CodeMirror
  doesn't optimise.

Change-Id: I16598fcdbeee51e6fae88376ec81f1c8552b383d
---
M .eslintrc.json
M CodeMirror.hooks.php
M Gruntfile.js
M extension.json
M i18n/en.json
M i18n/qqq.json
M resources/ext.CodeMirror.less
A resources/modules/ve-cm/ve.ui.CodeMirror.init.js
A resources/modules/ve-cm/ve.ui.CodeMirror.init.less
A resources/modules/ve-cm/ve.ui.CodeMirrorAction.js
A resources/modules/ve-cm/ve.ui.CodeMirrorTool.js
11 files changed, 312 insertions(+), 6 deletions(-)

Approvals:
  jenkins-bot: Verified
  Jforrester: Looks good to me, approved



diff --git a/.eslintrc.json b/.eslintrc.json
index 40f6bcf..6068805 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -7,7 +7,10 @@
        },
        "globals": {
                "mediaWiki": false,
-               "CodeMirror": false
+               "CodeMirror": false,
+               "ve": false,
+               "mw": false,
+               "OO": false
        },
        "rules": {
                "dot-notation": [ "error", { "allowKeywords": true } ]
diff --git a/CodeMirror.hooks.php b/CodeMirror.hooks.php
index 147148d..6bbaa15 100644
--- a/CodeMirror.hooks.php
+++ b/CodeMirror.hooks.php
@@ -107,8 +107,8 @@
         */
        public static function onMakeGlobalVariablesScript( array &$vars, 
OutputPage $out ) {
                $context = $out->getContext();
-               // add CodeMirror vars only for edit pages
-               if ( self::isCodeMirrorEnabled( $context ) ) {
+               // add CodeMirror vars on edit pages, or if VE is installed
+               if ( self::isCodeMirrorEnabled( $context ) || class_exists( 
'VisualEditorHooks' )  ) {
                        $vars['extCodeMirrorConfig'] = self::getConfiguraton( 
$context );
                }
        }
diff --git a/Gruntfile.js b/Gruntfile.js
index 4a474d8..90f0e22 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -15,7 +15,7 @@
                },
                stylelint: {
                        all: [
-                               '**/*.css',
+                               '**/*.{css,less}',
                                '!resources/lib/**',
                                '!node_modules/**'
                        ]
diff --git a/extension.json b/extension.json
index 309e110..c592dab 100644
--- a/extension.json
+++ b/extension.json
@@ -96,6 +96,37 @@
                                "ext.CodeMirror.lib.mode.clike",
                                "ext.CodeMirror.lib"
                        ]
+               },
+               "ext.CodeMirror.visualEditor.init": {
+                       "scripts": [
+                               "modules/ve-cm/ve.ui.CodeMirror.init.js"
+                       ],
+                       "styles": [
+                               "modules/ve-cm/ve.ui.CodeMirror.init.less"
+                       ],
+                       "messages": [
+                               "codemirror-toggle-label"
+                       ],
+                       "targets": [
+                               "desktop"
+                       ]
+               },
+               "ext.CodeMirror.visualEditor": {
+                       "dependencies": [
+                               "ext.visualEditor.mwcore",
+                               "ext.CodeMirror.lib",
+                               "ext.CodeMirror.mode.mediawiki",
+                               "mediawiki.api",
+                               "mediawiki.api.options",
+                               "user.options"
+                       ],
+                       "scripts": [
+                               "modules/ve-cm/ve.ui.CodeMirrorAction.js",
+                               "modules/ve-cm/ve.ui.CodeMirrorTool.js"
+                       ],
+                       "targets": [
+                               "desktop"
+                       ]
                }
        },
        "ResourceFileModulePaths": {
@@ -113,6 +144,10 @@
                        "CodeMirrorHooks::onGetPreferences"
                ]
        },
+       "VisualEditorPluginModules": [
+               "ext.CodeMirror.visualEditor.init",
+               "ext.CodeMirror.visualEditor"
+       ],
        "config": {
                "CodeMirrorEnableFrontend": true
        },
diff --git a/i18n/en.json b/i18n/en.json
index a77c902..425efaf 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -6,5 +6,6 @@
        },
        "codemirror-desc": "Provides syntax highlighting in wikitext editor",
        "codemirror-enable-label": "Enable CodeMirror (Syntax highlight)",
-       "codemirror-disable-label": "Disable CodeMirror (Syntax highlight)"
+       "codemirror-disable-label": "Disable CodeMirror (Syntax highlight)",
+       "codemirror-toggle-label": "Syntax highlighting"
 }
diff --git a/i18n/qqq.json b/i18n/qqq.json
index 5fce41e..81cc890 100644
--- a/i18n/qqq.json
+++ b/i18n/qqq.json
@@ -9,5 +9,6 @@
        },
        "codemirror-desc": "{{desc|name=Code 
Mirror|url=https://www.mediawiki.org/wiki/Extension:CodeMirror}}\n\nAdditional 
info: Discription of \"Syntax highlighting\" in 
wiki\n[[mw:Extension:SyntaxHighlight GeSHi]]",
        "codemirror-enable-label": "Title tooltip for button to enable 
CodeMirror in the editing toolbar.",
-       "codemirror-disable-label": "Title tooltip for button to disable 
CodeMirror in the editing toolbar."
+       "codemirror-disable-label": "Title tooltip for button to disable 
CodeMirror in the editing toolbar.",
+       "codemirror-toggle-label": "Title tooltip for button to toggle 
CodeMirror in the editing toolbar."
 }
diff --git a/resources/ext.CodeMirror.less b/resources/ext.CodeMirror.less
index 30a2f48..600820f 100644
--- a/resources/ext.CodeMirror.less
+++ b/resources/ext.CodeMirror.less
@@ -29,3 +29,8 @@
 .wikiEditor-ui-toolbar .tool-codemirror-off {
        .background-image-svg( 'images/cm-off.svg', 'images/cm-off.png' );
 }
+
+.oo-ui-popupWidget.ve-init-mw-switchPopupWidget {
+       // Increase z-index to above scrollbar
+       z-index: 7;
+}
diff --git a/resources/modules/ve-cm/ve.ui.CodeMirror.init.js 
b/resources/modules/ve-cm/ve.ui.CodeMirror.init.js
new file mode 100644
index 0000000..4708879
--- /dev/null
+++ b/resources/modules/ve-cm/ve.ui.CodeMirror.init.js
@@ -0,0 +1,12 @@
+( function ( ve, mw ) {
+       mw.libs.ve.targetLoader.addPlugin( function () {
+               var i, target, index;
+               for ( i in ve.init.mw ) {
+                       target = ve.init.mw[ i ];
+                       if ( target === ve.init.mw.DesktopArticleTarget ) {
+                               index = target.static.actionGroups[ 1 
].include.indexOf( 'changeDirectionality' );
+                               target.static.actionGroups[ 1 ].include.splice( 
index, 0, 'codeMirror' );
+                       }
+               }
+       } );
+}( ve, mediaWiki ) );
diff --git a/resources/modules/ve-cm/ve.ui.CodeMirror.init.less 
b/resources/modules/ve-cm/ve.ui.CodeMirror.init.less
new file mode 100644
index 0000000..2ddbf17
--- /dev/null
+++ b/resources/modules/ve-cm/ve.ui.CodeMirror.init.less
@@ -0,0 +1,49 @@
+.ve-init-mw-desktopArticleTarget {
+       .CodeMirror {
+               height: auto;
+               z-index: -1;
+               position: absolute;
+               top: 0;
+               left: 0;
+               font-size: 1.17216em;
+               line-height: 1.5em;
+               width: 100%;
+               -webkit-box-sizing: border-box;
+               -moz-box-sizing: border-box;
+               box-sizing: border-box;
+
+               padding: 0 1.14286em; /* 1/0.875 */
+               @media screen and ( min-width: 982px ) {
+                       padding: 0 1.71429em; /* surface-margin-left (1.5em) / 
(mw-body-content font-size) 0.875em */
+               }
+       }
+
+       .CodeMirror pre,
+       .CodeMirror-lines {
+               padding: 0;
+       }
+
+       .CodeMirror-scroll {
+               margin-right: 0;
+               overflow: auto !important; /* stylelint-disable-line 
declaration-no-important */
+       }
+
+       .CodeMirror-sizer {
+               border-right: 0;
+       }
+
+       .CodeMirror pre.cm-mw-section-1,
+       .CodeMirror pre.cm-mw-section-2 {
+               font-size: inherit;
+               line-height: inherit;
+               font-weight: bold;
+       }
+}
+
+.ve-ce-documentNode-codeEditor-hide {
+       opacity: 0.3;
+}
+
+.ve-ce-documentNode-codeEditor-webkit-hide {
+       -webkit-text-fill-color: transparent;
+}
diff --git a/resources/modules/ve-cm/ve.ui.CodeMirrorAction.js 
b/resources/modules/ve-cm/ve.ui.CodeMirrorAction.js
new file mode 100644
index 0000000..effe1de
--- /dev/null
+++ b/resources/modules/ve-cm/ve.ui.CodeMirrorAction.js
@@ -0,0 +1,122 @@
+/*!
+ * VisualEditor UserInterface CodeMirrorAction class.
+ *
+ * @copyright 2011-2017 VisualEditor Team and others; see 
http://ve.mit-license.org
+ */
+
+/**
+ * CodeMirror action
+ *
+ * @class
+ * @extends ve.ui.Action
+ * @constructor
+ * @param {ve.ui.Surface} surface Surface to act on
+ */
+ve.ui.CodeMirrorAction = function VeUiCodeMirrorAction() {
+       // Parent constructor
+       ve.ui.CodeMirrorAction.super.apply( this, arguments );
+};
+
+/* Inheritance */
+
+OO.inheritClass( ve.ui.CodeMirrorAction, ve.ui.Action );
+
+/* Static Properties */
+
+ve.ui.CodeMirrorAction.static.name = 'codeMirror';
+
+/**
+ * @inheritdoc
+ */
+ve.ui.CodeMirrorAction.static.methods = [ 'toggle' ];
+
+/* Methods */
+
+/**
+ * @method
+ * @param {boolean} [enable] State to force toggle to, inverts current state 
if undefined
+ * @return {boolean} Action was executed
+ */
+ve.ui.CodeMirrorAction.prototype.toggle = function ( enable ) {
+       var surface = this.surface,
+               surfaceView = surface.getView(),
+               doc = surface.getModel().getDocument();
+
+       if ( !surface.mirror && enable !== false ) {
+               surface.mirror = CodeMirror( surfaceView.$element[ 0 ], {
+                       value: surface.getDom(),
+                       mwConfig: mw.config.get( 'extCodeMirrorConfig' ),
+                       lineWrapping: true,
+                       tabSize: 1,
+                       scrollbarStyle: 'null',
+                       viewportMargin: 5,
+                       // select mediawiki as text input mode
+                       mode: 'text/mediawiki',
+                       extraKeys: {
+                               Tab: false
+                       }
+               } );
+
+               surfaceView.$documentNode.addClass(
+                       'WebkitTextFillColor' in document.body.style ?
+                       've-ce-documentNode-codeEditor-webkit-hide' :
+                       've-ce-documentNode-codeEditor-webkit'
+               );
+
+               // As the action is regenerated each time, we need to store the 
bound listener
+               // in the mirror for later disconnection.
+               surface.mirror.veTransactionListener = 
this.onDocumentTransact.bind( this, surface );
+
+               doc.on( 'transact', surface.mirror.veTransactionListener );
+       } else if ( surface.mirror && enable !== true ) {
+               doc.off( 'transact', surface.mirror.veTransactionListener );
+
+               surfaceView.$documentNode.removeClass(
+                       've-ce-documentNode-codeEditor-webkit-hide 
ve-ce-documentNode-codeEditor-webkit'
+               );
+
+               surface.mirror.getWrapperElement().remove();
+
+               surface.mirror = null;
+       }
+
+       return true;
+};
+
+ve.ui.CodeMirrorAction.prototype.onDocumentTransact = function ( surface, tx ) 
{
+       var node, textRange, line,
+               doc = surface.getModel().getDocument(),
+               mirror = surface.mirror,
+               modifiedRange = tx.getModifiedRange( doc ),
+               nodes = doc.selectNodes( modifiedRange, 'leaves' );
+
+       // TODO: Iterate over operations and perform a replaceRange for each 
replace operation
+       if ( nodes.length === 1 && nodes[ 0 ].node instanceof ve.dm.TextNode ) {
+               node = nodes[ 0 ].node.parent;
+               textRange = nodes[ 0 ].nodeRange;
+               line = node.parent.children.indexOf( node );
+               if ( tx.operations.every( function ( op ) {
+                       return op.type === 'retain' || ( op.type === 'replace' 
&& op.remove.length === 0 );
+               } ) ) {
+                       // Single line insert
+                       mirror.replaceRange(
+                               doc.data.getText( true, modifiedRange ),
+                               { line: line, ch: modifiedRange.start - 
textRange.start }
+                       );
+               } else {
+                       // Single line replace
+                       mirror.replaceRange(
+                               doc.data.getText( true, textRange ),
+                               { line: line, ch: 0 },
+                               { line: line, ch: mirror.getLine( line ).length 
}
+                       );
+               }
+       } else {
+               // Fallback - flush whole doc
+               mirror.setValue( surface.getDom() );
+       }
+};
+
+/* Registration */
+
+ve.ui.actionFactory.register( ve.ui.CodeMirrorAction );
diff --git a/resources/modules/ve-cm/ve.ui.CodeMirrorTool.js 
b/resources/modules/ve-cm/ve.ui.CodeMirrorTool.js
new file mode 100644
index 0000000..e7cd52e
--- /dev/null
+++ b/resources/modules/ve-cm/ve.ui.CodeMirrorTool.js
@@ -0,0 +1,78 @@
+/**
+ * MediaWiki UserInterface CodeMirror tool.
+ *
+ * @class
+ * @abstract
+ * @extends ve.ui.Tool
+ * @constructor
+ * @param {OO.ui.ToolGroup} toolGroup
+ * @param {Object} [config] Configuration options
+ */
+ve.ui.CodeMirrorTool = function VeUiCodeMirrorTool() {
+       // Parent constructor
+       ve.ui.CodeMirrorTool.super.apply( this, arguments );
+
+       // Events
+       this.toolbar.connect( this, { surfaceChange: 'onSurfaceChange' } );
+};
+
+/* Inheritance */
+
+OO.inheritClass( ve.ui.CodeMirrorTool, ve.ui.Tool );
+
+/* Static properties */
+
+ve.ui.CodeMirrorTool.static.name = 'codeMirror';
+ve.ui.CodeMirrorTool.static.autoAddToCatchall = false;
+ve.ui.CodeMirrorTool.static.title = OO.ui.deferMsg( 'codemirror-toggle-label' 
);
+ve.ui.CodeMirrorTool.static.icon = 'code';
+ve.ui.CodeMirrorTool.static.group = 'codeMirror';
+ve.ui.CodeMirrorTool.static.commandName = 'codeMirror';
+ve.ui.CodeMirrorTool.static.deactivateOnSelect = false;
+
+/**
+ * @inheritdoc
+ */
+ve.ui.CodeMirrorTool.prototype.onSelect = function () {
+       var useCodeMirror;
+
+       // Parent method
+       ve.ui.CodeMirrorTool.super.prototype.onSelect.apply( this, arguments );
+
+       useCodeMirror = !!this.toolbar.surface.mirror;
+       this.setActive( useCodeMirror );
+
+       new mw.Api().saveOption( 'usecodemirror', useCodeMirror ? 1 : 0 );
+       mw.user.options.set( 'usecodemirror', useCodeMirror ? 1 : 0 );
+};
+
+/**
+ * @inheritdoc
+ */
+ve.ui.CodeMirrorTool.prototype.onSurfaceChange = function ( oldSurface, 
newSurface ) {
+       var useCodeMirror,
+               isDisabled = newSurface.getMode() !== 'source',
+               command = this.getCommand(),
+               surface = this.toolbar.getSurface();
+
+       this.setDisabled( isDisabled );
+       if ( !isDisabled ) {
+               useCodeMirror = mw.user.options.get( 'usecodemirror' ) > 0;
+               command.execute( surface, [ useCodeMirror ] );
+               this.setActive( useCodeMirror );
+       }
+};
+
+ve.ui.CodeMirrorTool.prototype.onUpdateState = function () {};
+
+/* Registration */
+
+ve.ui.toolFactory.register( ve.ui.CodeMirrorTool );
+
+/* Command */
+
+ve.ui.commandRegistry.register(
+       new ve.ui.Command(
+               'codeMirror', 'codeMirror', 'toggle'
+       )
+);

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

Gerrit-MessageType: merged
Gerrit-Change-Id: I16598fcdbeee51e6fae88376ec81f1c8552b383d
Gerrit-PatchSet: 24
Gerrit-Project: mediawiki/extensions/CodeMirror
Gerrit-Branch: master
Gerrit-Owner: Esanders <esand...@wikimedia.org>
Gerrit-Reviewer: Bartosz DziewoƄski <matma....@gmail.com>
Gerrit-Reviewer: Esanders <esand...@wikimedia.org>
Gerrit-Reviewer: Jforrester <jforres...@wikimedia.org>
Gerrit-Reviewer: Kaldari <rkald...@wikimedia.org>
Gerrit-Reviewer: MusikAnimal <musikani...@wikimedia.org>
Gerrit-Reviewer: Niharika29 <nko...@wikimedia.org>
Gerrit-Reviewer: Pastakhov <pastak...@yandex.ru>
Gerrit-Reviewer: Samwilson <s...@samwilson.id.au>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to