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