mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/CodeMirror
synced 2024-11-23 22:03:28 +00:00
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
This commit is contained in:
parent
c9b4aa65fd
commit
7b01a98ad0
|
@ -7,7 +7,10 @@
|
|||
},
|
||||
"globals": {
|
||||
"mediaWiki": false,
|
||||
"CodeMirror": false
|
||||
"CodeMirror": false,
|
||||
"ve": false,
|
||||
"mw": false,
|
||||
"OO": false
|
||||
},
|
||||
"rules": {
|
||||
"dot-notation": [ "error", { "allowKeywords": true } ]
|
||||
|
|
|
@ -107,8 +107,8 @@ class CodeMirrorHooks {
|
|||
*/
|
||||
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 );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ module.exports = function ( grunt ) {
|
|||
},
|
||||
stylelint: {
|
||||
all: [
|
||||
'**/*.css',
|
||||
'**/*.{css,less}',
|
||||
'!resources/lib/**',
|
||||
'!node_modules/**'
|
||||
]
|
||||
|
|
|
@ -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
|
||||
},
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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."
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
12
resources/modules/ve-cm/ve.ui.CodeMirror.init.js
Normal file
12
resources/modules/ve-cm/ve.ui.CodeMirror.init.js
Normal file
|
@ -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 ) );
|
49
resources/modules/ve-cm/ve.ui.CodeMirror.init.less
Normal file
49
resources/modules/ve-cm/ve.ui.CodeMirror.init.less
Normal file
|
@ -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;
|
||||
}
|
122
resources/modules/ve-cm/ve.ui.CodeMirrorAction.js
Normal file
122
resources/modules/ve-cm/ve.ui.CodeMirrorAction.js
Normal file
|
@ -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 );
|
78
resources/modules/ve-cm/ve.ui.CodeMirrorTool.js
Normal file
78
resources/modules/ve-cm/ve.ui.CodeMirrorTool.js
Normal file
|
@ -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'
|
||||
)
|
||||
);
|
Loading…
Reference in a new issue