( function () { var codeMirror, $textbox1; // Exit if WikiEditor is disabled if ( !mw.loader.getState( 'ext.wikiEditor' ) ) { return; } var useCodeMirror = mw.user.options.get( 'usecodemirror' ) > 0; var api = new mw.Api(); var originHooksTextarea = $.valHooks.textarea; // define jQuery hook for searching and replacing text using JS if CodeMirror is enabled, see Bug: T108711 $.valHooks.textarea = { get: function ( elem ) { if ( elem.id === 'wpTextbox1' && codeMirror ) { return codeMirror.doc.getValue(); } else if ( originHooksTextarea ) { return originHooksTextarea.get( elem ); } return elem.value; }, set: function ( elem, value ) { if ( elem.id === 'wpTextbox1' && codeMirror ) { return codeMirror.doc.setValue( value ); } else if ( originHooksTextarea ) { return originHooksTextarea.set( elem, value ); } elem.value = value; } }; // jQuery.textSelection overrides for CodeMirror. // See jQuery.textSelection.js for method documentation var cmTextSelection = { getContents: function () { return codeMirror.doc.getValue(); }, setContents: function ( content ) { codeMirror.doc.setValue( content ); return this; }, getSelection: function () { return codeMirror.doc.getSelection(); }, setSelection: function ( options ) { codeMirror.doc.setSelection( codeMirror.doc.posFromIndex( options.start ), codeMirror.doc.posFromIndex( options.end ) ); codeMirror.focus(); return this; }, replaceSelection: function ( value ) { codeMirror.doc.replaceSelection( value ); return this; }, getCaretPosition: function ( options ) { var caretPos = codeMirror.doc.indexFromPos( codeMirror.doc.getCursor( true ) ), endPos = codeMirror.doc.indexFromPos( codeMirror.doc.getCursor( false ) ); if ( options.startAndEnd ) { return [ caretPos, endPos ]; } return caretPos; }, scrollToCaretPosition: function () { codeMirror.scrollIntoView( null ); return this; } }; /** * Save CodeMirror enabled pref. * * @param {boolean} prefValue True, if CodeMirror should be enabled by default, otherwise false. */ function setCodeEditorPreference( prefValue ) { useCodeMirror = prefValue; // Save state for function updateToolbarButton() if ( mw.user.isAnon() ) { // Skip it for anon users return; } api.saveOption( 'usecodemirror', prefValue ? 1 : 0 ); mw.user.options.set( 'usecodemirror', prefValue ? 1 : 0 ); } /** * @return {boolean} */ function isLineNumbering() { // T285660: Backspace related bug on Android browsers as of 2021 if ( /Android\b/.test( navigator.userAgent ) ) { return false; } var namespaces = mw.config.get( 'wgCodeMirrorLineNumberingNamespaces' ); // Set to [] to disable everywhere, or null to enable everywhere return !namespaces || namespaces.indexOf( mw.config.get( 'wgNamespaceNumber' ) ) !== -1; } // Keep these modules in sync with CodeMirrorHooks.php var codeMirrorCoreModules = [ 'ext.CodeMirror.lib', 'ext.CodeMirror.mode.mediawiki' ]; /** * Replaces the default textarea with CodeMirror */ function enableCodeMirror() { var config = mw.config.get( 'extCodeMirrorConfig' ); mw.loader.using( codeMirrorCoreModules.concat( config.pluginModules ), function () { var $codeMirror, cmOptions, selectionStart = $textbox1.prop( 'selectionStart' ), selectionEnd = $textbox1.prop( 'selectionEnd' ), scrollTop = $textbox1.scrollTop(), hasFocus = $textbox1.is( ':focus' ); // If CodeMirror is already loaded or wikEd gadget is enabled, abort. See T178348. // FIXME: Would be good to replace the wikEd check with something more generic. if ( codeMirror || mw.user.options.get( 'gadget-wikEd' ) > 0 ) { return; } // T174055: Do not redefine the browser history navigation keys (T175378: for PC only) CodeMirror.keyMap.pcDefault[ 'Alt-Left' ] = false; CodeMirror.keyMap.pcDefault[ 'Alt-Right' ] = false; cmOptions = { mwConfig: config, // styleActiveLine: true, // disabled since Bug: T162204, maybe should be optional lineWrapping: true, lineNumbers: isLineNumbering(), readOnly: $textbox1[ 0 ].readOnly, // select mediawiki as text input mode mode: 'text/mediawiki', extraKeys: { Tab: false, 'Shift-Tab': false, // T174514: Move the cursor at the beginning/end of the current wrapped line Home: 'goLineLeft', End: 'goLineRight' }, inputStyle: 'contenteditable', spellcheck: true, viewportMargin: Infinity }; if ( mw.config.get( 'wgCodeMirrorEnableBracketMatching' ) ) { cmOptions.matchBrackets = { highlightNonMatching: false, maxHighlightLineLength: 10000 }; } codeMirror = CodeMirror.fromTextArea( $textbox1[ 0 ], cmOptions ); $codeMirror = $( codeMirror.getWrapperElement() ); // Allow textSelection() functions to work with CodeMirror editing field. $codeMirror.textSelection( 'register', cmTextSelection ); // Also override textSelection() functions for the "real" hidden textarea to route to // CodeMirror. We unregister this when switching to normal textarea mode. $textbox1.textSelection( 'register', cmTextSelection ); // RL module jquery.ui $codeMirror.resizable( { handles: 'se', resize: function ( event, ui ) { ui.size.width = ui.originalSize.width; } } ); if ( hasFocus ) { codeMirror.focus(); } codeMirror.doc.setSelection( codeMirror.doc.posFromIndex( selectionEnd ), codeMirror.doc.posFromIndex( selectionStart ) ); codeMirror.scrollTo( null, scrollTop ); // HACK: