/* global CodeMirror, mw, $ */ var initExtCodeMirror = function() { var origTextSelection = $.fn.textSelection, codeMirror = false; // Replace jquery.textSelection.js var cmTextSelection = function ( command, options ) { if ( ! codeMirror ) { return origTextSelection( 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; }; function setCodeEditorPreference( prefValue ) { var api = new mw.Api(); api.postWithToken( 'options', { action: 'options', optionname: 'usecodemirror', optionvalue: prefValue ? 1 : 0 } ).fail( function ( code, result ) { mw.log.error( 'Failed to set code editor preference: ' + code + '\n' + result.error ); } ); } if ( mw.user.options.get( 'usecodemirror' ) === '1' || mw.user.options.get( 'usecodemirror' ) === 1 ) { codeMirror = true; } function addCodeMirrorToWikiEditor() { (function waitWikiEditor() { if ( $( '#wikiEditor-section-main' ).length > 0 ) { $( '#wpTextbox1' ).wikiEditor( 'addToToolbar', { 'section': 'main', 'groups': { 'codemirror':{ // 'label': 'CodeMirror', 'tools': { 'CodeMirror': { label: 'CodeMirror', type: 'button', icon: mw.config.get( 'wgExtensionAssetsPath' ) + '/CodeMirror/resources/images/cm-' + (codeMirror ? 'on.png' : 'off.png'), action: {type: 'callback', execute: function( context ){ switchCodeMirror( context ); } } // }, // 'Undo':{ // label: 'Undo', // type: 'button', // icon: mw.config.get( 'wgExtensionAssetsPath' ) + '/CodeMirror/resources/images/undo.png', // action: {type: 'callback', execute: function(context){alert(context);} } // }, // 'Redo':{ // label: 'Redo', // type: 'button', // icon: mw.config.get( 'wgExtensionAssetsPath' ) + '/CodeMirror/resources/images/redo.png', // action: {type: 'callback', execute: function(context){alert(context);} } } } } } } ); } else { setTimeout( waitWikiEditor, 500 ); } })(); } if ( $( '#wpTextbox1' ).wikiEditor && mw.user.options.get( 'usebetatoolbar' ) === 1 && mw.user.options.get( 'showtoolbar' ) === 1 ) { addCodeMirrorToWikiEditor(); } else { var $image = $( '' ).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 ); } function switchCodeMirror( context ) { //alert( 'switchCodeMirror: ' + codeMirror ); 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 ); } } 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' ), mwextMode: mw.config.get( 'extCodeMirrorExtMode' ), //matchMW: true, styleActiveLine: true, //gutters: ['CodeMirror-mediawiki-gutter'], //lint: true, lineWrapping: true, //indentUnit: 4, //indentWithTabs: true, //matchBrackets: true, //autoCloseBrackets: true, mode: 'text/mediawiki' } ); if ( window.navigator.userAgent.indexOf('Trident/') > -1 ) { //IE specific code goes here $( '.CodeMirror' ).addClass( 'CodeMirrorIE' ); } codeMirror.setSize( null, textbox1.height() ); $.fn.textSelection = cmTextSelection; } if ( codeMirror ) { enableCodeMirror(); } }; $.when( $.ready, mw.loader.using( ['user.options', 'jquery.textSelection', 'mediawiki.api'] ) ).done( initExtCodeMirror() );