mediawiki-extensions-CodeMi.../resources/ext.CodeMirror.js

348 lines
11 KiB
JavaScript
Raw Normal View History

/* global CodeMirror, mediaWiki */
( function ( mw, $ ) {
// codeMirror needs a special textselection jQuery function to work, save the current one to restore when
// CodeMirror get's disabled.
var origTextSelection = $.fn.textSelection,
codeMirror = mw.user.options.get( 'usecodemirror' ) === '1' || mw.user.options.get( 'usecodemirror' ) === 1,
api = new mw.Api(),
// function for a textselection function for CodeMirror
cmTextSelection = function ( command, options ) {
if ( !codeMirror || codeMirror.getTextArea() !== this[0] ) {
return origTextSelection.call( this, command, options );
}
var fn, retval;
fn = {
/**
* Get the contents of the textarea
*/
getContents: function () {
return codeMirror.doc.getValue();
},
/**
* Get the currently selected text in this textarea. Will focus the textarea
* in some browsers (IE/Opera)
*/
getSelection: function () {
return codeMirror.doc.getSelection();
},
/**
* Inserts text at the beginning and end of a text selection, optionally
* inserting text at the caret when selection is empty.
*/
encapsulateSelection: function ( options ) {
return this.each( function () {
var insertText, selText,
selectPeri = options.selectPeri,
pre = options.pre, post = options.post;
if ( options.selectionStart !== undefined ) {
//fn[command].call( this, options );
fn.setSelection( { 'start': options.selectionStart, 'end': options.selectionEnd } ); // not tested
}
selText = codeMirror.doc.getSelection();
if ( !selText ) {
selText = options.peri;
} else if ( options.replace ) {
selectPeri = false;
selText = options.peri;
} else {
selectPeri = false;
while ( selText.charAt( selText.length - 1 ) === ' ' ) {
// Exclude ending space char
selText = selText.substring( 0, selText.length - 1 );
post += ' ';
}
while ( selText.charAt( 0 ) === ' ' ) {
// Exclude prepending space char
selText = selText.substring( 1, selText.length );
pre = ' ' + pre;
}
}
/**
* Do the splitlines stuff.
*
* Wrap each line of the selected text with pre and post
*/
function doSplitLines( selText, pre, post ) {
var i,
insertText = '',
selTextArr = selText.split( '\n' );
for ( i = 0; i < selTextArr.length; i++ ) {
insertText += pre + selTextArr[i] + post;
if ( i !== selTextArr.length - 1 ) {
insertText += '\n';
}
}
return insertText;
}
if ( options.splitlines ) {
selectPeri = false;
insertText = doSplitLines( selText, pre, post );
} else {
insertText = pre + selText + post;
}
var startCursor = codeMirror.doc.getCursor( true );
if ( options.ownline ) {
if ( startCursor.ch !== 0 ) {
insertText = '\n' + insertText;
pre += '\n';
}
var endCursor = codeMirror.doc.getCursor( false );
if ( codeMirror.doc.getLine( endCursor.line ).length !== endCursor.ch ) {
insertText += '\n';
post += '\n';
}
}
codeMirror.doc.replaceSelection( insertText );
if ( selectPeri ) {
codeMirror.doc.setSelection(
codeMirror.doc.posFromIndex( codeMirror.doc.indexFromPos( startCursor ) + pre.length ),
codeMirror.doc.posFromIndex( codeMirror.doc.indexFromPos( startCursor ) + pre.length + selText.length )
);
}
});
},
/**
* Get the position (in resolution of bytes not necessarily characters)
* in a textarea
*/
getCaretPosition: function ( options ) {
var caretPos = codeMirror.doc.indexFromPos( codeMirror.doc.getCursor( true ) );
if ( options.startAndEnd ) {
var endPos = codeMirror.doc.indexFromPos( codeMirror.doc.getCursor( false ) );
return [ caretPos, endPos ];
}
return caretPos;
},
setSelection: function ( options ) {
return this.each( function () {
codeMirror.doc.setSelection( codeMirror.doc.posFromIndex( options.start ), codeMirror.doc.posFromIndex( options.end ) );
});
},
/**
* Scroll a textarea to the current cursor position. You can set the cursor
* position with setSelection()
* @param options boolean Whether to force a scroll even if the caret position
* is already visible. Defaults to false
*/
scrollToCaretPosition: function ( /* options */ ) {
return this.each(function () {
codeMirror.scrollIntoView( null );
});
}
};
switch ( command ) {
//case 'getContents': // no params
//case 'setContents': // no params with defaults
//case 'getSelection': // no params
case 'encapsulateSelection':
options = $.extend( {
pre: '', // Text to insert before the cursor/selection
peri: '', // Text to insert between pre and post and select afterwards
post: '', // Text to insert after the cursor/selection
ownline: false, // Put the inserted text on a line of its own
replace: false, // If there is a selection, replace it with peri instead of leaving it alone
selectPeri: true, // Select the peri text if it was inserted (but not if there was a selection and replace==false, or if splitlines==true)
splitlines: false, // If multiple lines are selected, encapsulate each line individually
selectionStart: undefined, // Position to start selection at
selectionEnd: undefined // Position to end selection at. Defaults to start
}, options );
break;
case 'getCaretPosition':
options = $.extend( {
// Return [start, end] instead of just start
startAndEnd: false
}, options );
// FIXME: We may not need character position-based functions if we insert markers in the right places
break;
case 'setSelection':
options = $.extend( {
// Position to start selection at
start: undefined,
// Position to end selection at. Defaults to start
end: undefined,
// Element to start selection in (iframe only)
startContainer: undefined,
// Element to end selection in (iframe only). Defaults to startContainer
endContainer: undefined
}, options );
if ( options.end === undefined ) {
options.end = options.start;
}
if ( options.endContainer === undefined ) {
options.endContainer = options.startContainer;
}
// FIXME: We may not need character position-based functions if we insert markers in the right places
break;
case 'scrollToCaretPosition':
options = $.extend( {
force: false // Force a scroll even if the caret position is already visible
}, options );
break;
}
retval = fn[command].call( this, options );
codeMirror.focus();
return retval;
},
/**
* Adds the CodeMirror button to WikiEditor
*/
addCodeMirrorToWikiEditor = function () {
if ( $( '#wikiEditor-section-main' ).length > 0 ) {
$( '#wpTextbox1' ).wikiEditor(
'addToToolbar',
{
'section': 'main',
'groups': {
'codemirror':{
'tools': {
'CodeMirror': {
label: 'CodeMirror',
type: 'button',
// FIXME: There should be a better way?
icon: mw.config.get( 'wgExtensionAssetsPath' ) + '/CodeMirror/resources/images/cm-' + ( codeMirror ? 'on.png' : 'off.png' ),
action: {
type: 'callback',
execute: function( context ) {
switchCodeMirror( context );
}
}
}
}
}
}
}
);
}
};
/**
* Save CodeMirror enabled pref.
*
* @param {Boolean} prefValue True, if CodeMirror should be enabled by default, otherwise false.
*/
function setCodeEditorPreference( prefValue ) {
api.postWithToken( 'options', {
action: 'options',
optionname: 'usecodemirror',
optionvalue: prefValue ? 1 : 0
} ).fail( function ( code, result ) {
// FIXME: Should this throw an user visible error message?
mw.log.warn( 'Failed to set code editor preference: ' + code + '\n' + result.error );
} );
}
/**
* Enables or disables CodeMirror
*
* @param {undefined} context Doc needed
*/
function switchCodeMirror( context ) {
var $img, $src;
if ( context !== false ) {
$img = context.modules.toolbar.$toolbar.find( 'img.tool[rel=CodeMirror]' );
} else {
$img = $( '#CodeMirrorButton' );
}
if ( codeMirror ) {
setCodeEditorPreference( false );
codeMirror.save();
codeMirror.toTextArea();
codeMirror = false;
$.fn.textSelection = origTextSelection;
$src = mw.config.get( 'wgExtensionAssetsPath' ) + '/CodeMirror/resources/images/' + ( context ? 'cm-off.png' : 'old-cm-off.png' );
$img.attr( 'src', $src );
} else {
enableCodeMirror();
$src = mw.config.get( 'wgExtensionAssetsPath' ) + '/CodeMirror/resources/images/' + ( context ? 'cm-on.png' : 'old-cm-on.png' );
$img.attr( 'src', $src );
setCodeEditorPreference( true );
}
}
/**
* Replaces the default textarea with CodeMirror
*/
function enableCodeMirror() {
var textbox1 = $( '#wpTextbox1' );
if ( textbox1[0].style.display === 'none' ) {
return;
}
codeMirror = CodeMirror.fromTextArea( textbox1[0], {
mwextFunctionSynonyms: mw.config.get( 'extCodeMirrorFunctionSynonyms' ),
mwextTags: mw.config.get( 'extCodeMirrorTags' ),
mwextDoubleUnderscore: mw.config.get( 'extCodeMirrorDoubleUnderscore' ),
mwextUrlProtocols: mw.config.get( 'extCodeMirrorUrlProtocols' ),
mwextModes: mw.config.get( 'extCodeMirrorExtModes' ),
styleActiveLine: true,
lineWrapping: true,
readOnly: textbox1[0].readOnly,
// select mediawiki as text input mode
mode: 'text/mediawiki'
} );
// Our best friend, IE, needs some special css
if ( window.navigator.userAgent.indexOf('Trident/') > -1 ) {
$( '.CodeMirror' ).addClass( 'CodeMirrorIE' );
}
// set the hight of the textarea
codeMirror.setSize( null, textbox1.height() );
// Overwrite default textselection of WikiEditor to work with CodeMirror, too
$.fn.textSelection = cmTextSelection;
}
/* Check if view is in edit mode and that the required modules are available. Then, customize the toolbar … */
if ( $.inArray( mw.config.get( 'wgAction' ), [ 'edit', 'submit' ] ) !== -1 ) {
// This function shouldn't be called without user.options is loaded, but it's not guaranteed
mw.loader.using( 'user.options', function () {
// This can be the string "0" if the user disabled the preference - Bug T54542#555387
if ( mw.user.options.get( 'usebetatoolbar' ) === 1 || mw.user.options.get( 'usebetatoolbar' ) === '1' ) {
// load wikiEditor's toolbar (if not already) and add our button
$.when(
mw.loader.using( 'ext.wikiEditor.toolbar' ), $.ready
).then( addCodeMirrorToWikiEditor );
} else {
// If WikiEditor isn't enabled, add CodeMirror button to the default wiki editor toolbar
var $image = $( '<img>' ).attr( {
width: 23,
height: 22,
src: mw.config.get( 'wgExtensionAssetsPath' ) + '/CodeMirror/resources/images/old-cm-' + (codeMirror ? 'on.png' : 'off.png'),
alt: 'CodeMirror',
title: 'CodeMirror',
id: 'CodeMirrorButton',
'class': 'mw-toolbar-editbutton'
} ).click( function () {
switchCodeMirror( false );
return false;
} );
$( '#toolbar' ).append( $image );
}
} );
}
// enable CodeMirror
if ( codeMirror ) {
enableCodeMirror();
}
}( mediaWiki, jQuery ) );