diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 00000000..40f6bcfa --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,15 @@ +{ + "extends": "wikimedia", + "env": { + "browser": true, + "jquery": true, + "qunit": true + }, + "globals": { + "mediaWiki": false, + "CodeMirror": false + }, + "rules": { + "dot-notation": [ "error", { "allowKeywords": true } ] + } +} diff --git a/.jscsrc b/.jscsrc deleted file mode 100644 index 83457a7c..00000000 --- a/.jscsrc +++ /dev/null @@ -1,6 +0,0 @@ -{ - "preset": "wikimedia", - "requireVarDeclFirst": null, - "requireMultipleVarDecl": null, - "disallowEmptyBlocks": null -} diff --git a/.jshintignore b/.jshintignore deleted file mode 100644 index 1fc3c970..00000000 --- a/.jshintignore +++ /dev/null @@ -1,4 +0,0 @@ -node_modules -vendor -# upstream libs -resources/lib/ diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index d980a037..00000000 --- a/.jshintrc +++ /dev/null @@ -1,25 +0,0 @@ -{ - // Enforcing - "bitwise": true, - "eqeqeq": true, - "freeze": true, - "latedef": true, - "noarg": true, - "nonew": true, - "undef": true, - "unused": true, - "strict": false, - - // Relaxing - "es5": false, - - // Environment - "browser": true, - "jquery": true, - - "globals": { - "mediaWiki": false, - "CodeMirror": false, - "mod": false - } -} diff --git a/.stylelintrc b/.stylelintrc new file mode 100644 index 00000000..2c907302 --- /dev/null +++ b/.stylelintrc @@ -0,0 +1,3 @@ +{ + "extends": "stylelint-config-wikimedia" +} diff --git a/Gruntfile.js b/Gruntfile.js index c4b1daa8..4a474d8a 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,23 +1,24 @@ -/*jshint node:true */ +/* eslint-env node, es6 */ module.exports = function ( grunt ) { - grunt.loadNpmTasks( 'grunt-contrib-jshint' ); - grunt.loadNpmTasks( 'grunt-jsonlint' ); grunt.loadNpmTasks( 'grunt-banana-checker' ); - grunt.loadNpmTasks( 'grunt-jscs' ); + grunt.loadNpmTasks( 'grunt-eslint' ); + grunt.loadNpmTasks( 'grunt-jsonlint' ); + grunt.loadNpmTasks( 'grunt-stylelint' ); grunt.initConfig( { - jshint: { - options: { - jshintrc: true - }, + eslint: { all: [ '**/*.js', '!resources/lib/**', '!node_modules/**' ] }, - jscs: { - src: '<%= jshint.all %>' + stylelint: { + all: [ + '**/*.css', + '!resources/lib/**', + '!node_modules/**' + ] }, banana: { all: 'i18n/' @@ -30,6 +31,6 @@ module.exports = function ( grunt ) { } } ); - grunt.registerTask( 'test', [ 'jshint', 'jscs', 'jsonlint', 'banana' ] ); + grunt.registerTask( 'test', [ 'eslint', 'stylelint', 'jsonlint', 'banana' ] ); grunt.registerTask( 'default', 'test' ); }; diff --git a/package.json b/package.json index 8a4e5f3b..629b866a 100644 --- a/package.json +++ b/package.json @@ -3,11 +3,12 @@ "test": "grunt test" }, "devDependencies": { - "grunt": "0.4.5", - "grunt-cli": "0.1.13", - "grunt-contrib-jshint": "0.11.3", - "grunt-banana-checker": "0.4.0", - "grunt-jscs": "2.5.0", - "grunt-jsonlint": "1.0.7" + "eslint-config-wikimedia": "0.3.0", + "grunt": "1.0.1", + "grunt-banana-checker": "0.5.0", + "grunt-eslint": "19.0.0", + "grunt-jsonlint": "1.1.0", + "grunt-stylelint": "0.7.0", + "stylelint-config-wikimedia": "0.4.1" } } diff --git a/resources/ext.CodeMirror.js b/resources/ext.CodeMirror.js index ca5ae36d..9c4a9dfa 100644 --- a/resources/ext.CodeMirror.js +++ b/resources/ext.CodeMirror.js @@ -1,248 +1,272 @@ ( function ( mw, $ ) { + var origTextSelection, codeMirror, api, originHooksTextarea; + if ( mw.config.get( 'wgCodeEditorCurrentLanguage' ) ) { // If the CodeEditor is used then just exit; return; } // 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; + origTextSelection = $.fn.textSelection; + codeMirror = mw.user.options.get( 'usecodemirror' ) === '1' || mw.user.options.get( 'usecodemirror' ) === 1; + api = new mw.Api(); + originHooksTextarea = $.valHooks.textarea; - fn = { - /** - * Get the contents of the textarea - */ - getContents: function () { - return codeMirror.doc.getValue(); - }, + // function for a textselection function for CodeMirror + function cmTextSelection( command, options ) { + var fn, retval; - setContents: function ( newContents ) { - codeMirror.doc.setValue( newContents ); - }, + if ( !codeMirror || codeMirror.getTextArea() !== this[ 0 ] ) { + return origTextSelection.call( this, command, options ); + } - /** - * Get the currently selected text in this textarea. Will focus the textarea - * in some browsers (IE/Opera) - */ - getSelection: function () { - return codeMirror.doc.getSelection(); - }, + fn = { + /** + * Get the contents of the textarea + * + * @return {string} + */ + getContents: function () { + return codeMirror.doc.getValue(); + }, - /** - * 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, - startCursor = codeMirror.doc.getCursor( true ), - endCursor = codeMirror.doc.getCursor( false ); + setContents: function ( newContents ) { + codeMirror.doc.setValue( newContents ); + }, - if ( options.selectionStart !== undefined ) { - // fn[command].call( this, options ); - fn.setSelection( { start: options.selectionStart, end: options.selectionEnd } ); // not tested + /** + * Get the currently selected text in this textarea. Will focus the textarea + * in some browsers (IE/Opera) + * + * @return {string} + */ + 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. + * + * @param {Object} options + * @return {jQuery} + */ + encapsulateSelection: function ( options ) { + return this.each( function () { + var insertText, + selText, + selectPeri = options.selectPeri, + pre = options.pre, + post = options.post, + startCursor = codeMirror.doc.getCursor( true ), + endCursor = codeMirror.doc.getCursor( false ); + + 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 += ' '; } - - 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; - } + 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' ); + /** + * Do the splitlines stuff. + * + * Wrap each line of the selected text with pre and post + * + * @param {string} selText + * @param {string} pre + * @param {string} post + * @return {string} + */ + 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; - } - - if ( options.ownline ) { - if ( startCursor.ch !== 0 ) { - insertText = '\n' + insertText; - pre += '\n'; - } - - if ( codeMirror.doc.getLine( endCursor.line ).length !== endCursor.ch ) { + for ( i = 0; i < selTextArr.length; i++ ) { + insertText += pre + selTextArr[ i ] + post; + if ( i !== selTextArr.length - 1 ) { insertText += '\n'; - post += '\n'; } } + return insertText; + } - codeMirror.doc.replaceSelection( insertText ); + if ( options.splitlines ) { + selectPeri = false; + insertText = doSplitLines( selText, pre, post ); + } else { + insertText = pre + selText + post; + } - 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 ) - ); + if ( options.ownline ) { + if ( startCursor.ch !== 0 ) { + insertText = '\n' + insertText; + pre += '\n'; } - } ); - }, - /** - * 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 ) ), - endPos = codeMirror.doc.indexFromPos( codeMirror.doc.getCursor( false ) ); - if ( options.startAndEnd ) { - return [ caretPos, endPos ]; + if ( codeMirror.doc.getLine( endCursor.line ).length !== endCursor.ch ) { + insertText += '\n'; + post += '\n'; + } } - return caretPos; - }, - setSelection: function ( options ) { - return this.each( function () { - codeMirror.doc.setSelection( codeMirror.doc.posFromIndex( options.start ), codeMirror.doc.posFromIndex( options.end ) ); - } ); - }, + codeMirror.doc.replaceSelection( insertText ); - /** - * Scroll a textarea to the current cursor position. You can set the cursor - * position with setSelection() - */ - scrollToCaretPosition: function () { - return this.each( function () { - codeMirror.scrollIntoView( null ); - } ); + 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 + * + * @param {Object} options + * @return {number} + */ + 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; + }, - 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 ); + setSelection: function ( options ) { + return this.each( function () { + codeMirror.doc.setSelection( codeMirror.doc.posFromIndex( options.start ), codeMirror.doc.posFromIndex( options.end ) ); + } ); + }, - 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; + /** + * Scroll a textarea to the current cursor position. You can set the cursor + * position with setSelection() + * + * @return {jQuery} + */ + scrollToCaretPosition: function () { + return this.each( function () { + codeMirror.scrollIntoView( null ); + } ); } + }; - retval = fn[ command ].call( this, options ); - codeMirror.focus(); + 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 ); - return retval; - }, - /** - * Adds the CodeMirror button to WikiEditor - */ - addCodeMirrorToWikiEditor = function () { - if ( $( '#wikiEditor-section-main' ).length > 0 ) { - var msg = codeMirror ? 'codemirror-disable-label' : 'codemirror-enable-label'; + 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; + } - $( '#wpTextbox1' ).wikiEditor( - 'addToToolbar', - { - section: 'main', - groups: { - codemirror: { - tools: { - CodeMirror: { - label: mw.msg( msg ), - 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 ); - } + retval = fn[ command ].call( this, options ); + codeMirror.focus(); + + return retval; + } + + /** + * Adds the CodeMirror button to WikiEditor + */ + function addCodeMirrorToWikiEditor() { + var msg; + if ( $( '#wikiEditor-section-main' ).length > 0 ) { + msg = codeMirror ? 'codemirror-disable-label' : 'codemirror-enable-label'; + + $( '#wpTextbox1' ).wikiEditor( + 'addToToolbar', + { + section: 'main', + groups: { + codemirror: { + tools: { + CodeMirror: { + label: mw.msg( msg ), + 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 ) { + // eslint-disable-next-line no-use-before-define + switchCodeMirror( context ); } } } } } } - ); - } - }, - originHooksTextarea = $.valHooks.textarea; + } + ); + } + } // define JQuery hook for searching and replacing text using JS if CodeMirror is enabled, see Bug: T108711 $.valHooks.textarea = { @@ -308,6 +332,7 @@ .attr( 'src', $src ) .attr( 'title', mw.msg( 'codemirror-enable-label' ) ); } else { + // eslint-disable-next-line no-use-before-define enableCodeMirror(); $src = mw.config.get( 'wgExtensionAssetsPath' ) + '/CodeMirror/resources/images/' + ( context ? 'cm-on.png' : 'old-cm-on.png' ); $img @@ -327,20 +352,20 @@ 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', - extraKeys: { - Tab: false - } - } ); + 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', + extraKeys: { + Tab: false + } + } ); // Our best friend, IE, needs some special css if ( window.navigator.userAgent.indexOf( 'Trident/' ) > -1 ) { $( '.CodeMirror' ).addClass( 'CodeMirrorIE' ); @@ -356,6 +381,7 @@ 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 () { + var $image; // 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 @@ -364,7 +390,7 @@ ).then( addCodeMirrorToWikiEditor ); } else { // If WikiEditor isn't enabled, add CodeMirror button to the default wiki editor toolbar - var $image = $( '' ).attr( { + $image = $( '' ).attr( { width: 23, height: 22, src: mw.config.get( 'wgExtensionAssetsPath' ) + '/CodeMirror/resources/images/old-cm-' + ( codeMirror ? 'on.png' : 'off.png' ), diff --git a/resources/mode/mediawiki/mediawiki.css b/resources/mode/mediawiki/mediawiki.css index aae23002..ecd98389 100644 --- a/resources/mode/mediawiki/mediawiki.css +++ b/resources/mode/mediawiki/mediawiki.css @@ -1,5 +1,5 @@ .CodeMirror { - border: 1px solid #CCC; + border: 1px solid #ccc; font-size: medium; line-height: 1.5em; } @@ -8,101 +8,104 @@ font-size: small; } +/* stylelint-disable block-opening-brace-newline-before, block-opening-brace-newline-after, + block-closing-brace-space-after, declaration-block-single-line-max-declarations, + declaration-block-semicolon-newline-after, selector-list-comma-newline-after */ + .cm-mw-pagename { text-decoration: underline; } -.cm-mw-matching {background-color: gold;} +.cm-mw-matching { background-color: #ffd700; } -.cm-mw-skipformatting { background-color: #adf; } -.cm-mw-list {color: #08f; font-weight: bold; background-color: #eee;} -.cm-mw-signature, -.cm-mw-hr { color: #08f; font-weight: bold; background-color: #eee; } -.cm-mw-indenting {color: #08f; font-weight: bold; background-color: #ddd;} -.cm-mw-mnemonic {color: #090;} -.cm-mw-comment {color: #aaa; font-weight: normal;} -.cm-mw-apostrophes-bold, .cm-mw-apostrophes-italic {color: #08f;} +.cm-mw-skipformatting { background-color: #adf; } +.cm-mw-list { color: #08f; font-weight: bold; background-color: #eee; } +.cm-mw-signature, .cm-mw-hr { color: #08f; font-weight: bold; background-color: #eee; } +.cm-mw-indenting { color: #08f; font-weight: bold; background-color: #ddd; } +.cm-mw-mnemonic { color: #090; } +.cm-mw-comment { color: #aaa; font-weight: normal; } +.cm-mw-apostrophes-bold, .cm-mw-apostrophes-italic { color: #08f; } -pre.cm-mw-section-1 {font-size: 1.8em;} -pre.cm-mw-section-2 {font-size: 1.5em;} -pre.cm-mw-section-3 {font-weight: bold;} -pre.cm-mw-section-4 {font-weight: bold;} -pre.cm-mw-section-5 {font-weight: bold;} -pre.cm-mw-section-6 {font-weight: bold;} -.cm-mw-section-header {color: #08f; font-weight: normal;} +pre.cm-mw-section-1 { font-size: 1.8em; } +pre.cm-mw-section-2 { font-size: 1.5em; } +pre.cm-mw-section-3 { font-weight: bold; } +pre.cm-mw-section-4 { font-weight: bold; } +pre.cm-mw-section-5 { font-weight: bold; } +pre.cm-mw-section-6 { font-weight: bold; } +.cm-mw-section-header { color: #08f; font-weight: normal; } -.cm-mw-template {color: #a11; font-weight: normal;} -.cm-mw-template-name {color: #a11; font-weight: bold;} -.cm-mw-template-name-mnemonic {font-weight: normal;} -.cm-mw-template-argument-name {color: #a11; font-weight: bold;} -.cm-mw-template-delimiter {color: #a11; font-weight: bold;} -.cm-mw-template-bracket {color: #a11; font-weight: bold;} +.cm-mw-template { color: #a11; font-weight: normal; } +.cm-mw-template-name { color: #a11; font-weight: bold; } +.cm-mw-template-name-mnemonic { font-weight: normal; } +.cm-mw-template-argument-name { color: #a11; font-weight: bold; } +.cm-mw-template-delimiter { color: #a11; font-weight: bold; } +.cm-mw-template-bracket { color: #a11; font-weight: bold; } -.cm-mw-templatevariable {color: #f50; font-weight: normal;} -.cm-mw-templatevariable-name {color: #f50; font-weight: bold;} -.cm-mw-templatevariable-bracket {color: #f50; font-weight: normal;} -.cm-mw-templatevariable-delimiter {color: #f50; font-weight: bold;} +.cm-mw-templatevariable { color: #f50; font-weight: normal; } +.cm-mw-templatevariable-name { color: #f50; font-weight: bold; } +.cm-mw-templatevariable-bracket { color: #f50; font-weight: normal; } +.cm-mw-templatevariable-delimiter { color: #f50; font-weight: bold; } -.cm-mw-parserfunction {font-weight: normal;} -.cm-mw-parserfunction-name {color: #70a; font-weight: bold;} -.cm-mw-parserfunction-bracket {color: #70a; font-weight: bold;} -.cm-mw-parserfunction-delimiter {color: #70a; font-weight: bold;} +.cm-mw-parserfunction { font-weight: normal; } +.cm-mw-parserfunction-name { color: #70a; font-weight: bold; } +.cm-mw-parserfunction-bracket { color: #70a; font-weight: bold; } +.cm-mw-parserfunction-delimiter { color: #70a; font-weight: bold; } -pre.cm-mw-exttag {background-image: url(img/ext2.png);} -.cm-mw-exttag {background-image: url(img/ext4.png);} -.cm-mw-exttag-name {color: #70a; font-weight: bold;} -.cm-mw-exttag-bracket {color: #70a; font-weight: normal;} -.cm-mw-exttag-attribute {color: #70a; font-weight: normal;} +pre.cm-mw-exttag { background-image: url( img/ext2.png ); } +.cm-mw-exttag { background-image: url( img/ext4.png ); } +.cm-mw-exttag-name { color: #70a; font-weight: bold; } +.cm-mw-exttag-bracket { color: #70a; font-weight: normal; } +.cm-mw-exttag-attribute { color: #70a; font-weight: normal; } -.cm-mw-htmltag-name {color: #170; font-weight: bold;} -.cm-mw-htmltag-bracket {color: #170; font-weight: normal;} -.cm-mw-htmltag-attribute {color: #170; font-weight: normal;} +.cm-mw-htmltag-name { color: #170; font-weight: bold; } +.cm-mw-htmltag-bracket { color: #170; font-weight: normal; } +.cm-mw-htmltag-attribute { color: #170; font-weight: normal; } -pre.cm-mw-tag-pre, .cm-mw-tag-pre {background-image: url(img/black4.png);} -pre.cm-mw-tag-nowiki, .cm-mw-tag-nowiki {background-image: url(img/black4.png);} +pre.cm-mw-tag-pre, .cm-mw-tag-pre { background-image: url( img/black4.png ); } +pre.cm-mw-tag-nowiki, .cm-mw-tag-nowiki { background-image: url( img/black4.png ); } -.cm-mw-link-pagename {color: #219; font-weight: normal;} -.cm-mw-link-tosection {color: #08f; font-weight: normal;} -.cm-mw-link-bracket {color: #219; font-weight: normal;} -.cm-mw-link-text {} -.cm-mw-link-delimiter {color: #219; font-weight: normal;} +.cm-mw-link-pagename { color: #219; font-weight: normal; } +.cm-mw-link-tosection { color: #08f; font-weight: normal; } +.cm-mw-link-bracket { color: #219; font-weight: normal; } +/* .cm-mw-link-text { } */ +.cm-mw-link-delimiter { color: #219; font-weight: normal; } -.cm-mw-extlink, .cm-mw-free-extlink {color: #219; font-weight: normal;} -.cm-mw-extlink-protocol, .cm-mw-free-extlink-protocol {color: #219; font-weight: bold;} -.cm-mw-extlink-text {} -.cm-mw-extlink-bracket {color: #219; font-weight: bold;} +.cm-mw-extlink, .cm-mw-free-extlink { color: #219; font-weight: normal; } +.cm-mw-extlink-protocol, .cm-mw-free-extlink-protocol { color: #219; font-weight: bold; } +/* .cm-mw-extlink-text { } */ +.cm-mw-extlink-bracket { color: #219; font-weight: bold; } -.cm-mw-table-bracket {color: #e0e; font-weight: bold;} -.cm-mw-table-delimiter {color: #e0e; font-weight: bold;} -.cm-mw-table-definition {color: #e0e; font-weight: normal;} -.cm-mw-table-caption {font-weight: bold;} +.cm-mw-table-bracket { color: #e0e; font-weight: bold; } +.cm-mw-table-delimiter { color: #e0e; font-weight: bold; } +.cm-mw-table-definition { color: #e0e; font-weight: normal; } +.cm-mw-table-caption { font-weight: bold; } -.cm-mw-template-ground {} -.cm-mw-template2-ground {background-image: url(img/template4.png);} -.cm-mw-template3-ground {background-image: url(img/template8.png);} -.cm-mw-template-ext-ground {background-image: url(img/ext4.png);} -.cm-mw-template-ext2-ground {background-image: url(img/ext4.png),url(img/ext4.png);} -.cm-mw-template-ext3-ground {background-image: url(img/ext4.png),url(img/ext4.png),url(img/ext4.png);} -.cm-mw-template-link-ground {background-image: url(img/link4.png);} -.cm-mw-template-ext-link-ground {background-image: url(img/ext4.png),url(img/link4.png);} -.cm-mw-template-ext2-link-ground {background-image: url(img/ext4.png),url(img/ext4.png),url(img/link4.png);} -.cm-mw-template-ext3-link-ground {background-image: url(img/ext4.png),url(img/ext4.png),url(img/ext4.png),url(img/link4.png);} -.cm-mw-template2-ext-ground {background-image: url(img/template4.png),url(img/ext4.png);} -.cm-mw-template2-ext2-ground {background-image: url(img/template4.png),url(img/ext4.png),url(img/ext4.png);} -.cm-mw-template2-ext3-ground {background-image: url(img/template4.png),url(img/ext4.png),url(img/ext4.png),url(img/ext4.png);} -.cm-mw-template2-link-ground {background-image: url(img/template4.png),url(img/link4.png);} -.cm-mw-template2-ext-link-ground {background-image: url(img/template4.png),url(img/ext4.png),url(img/link4.png);} -.cm-mw-template2-ext2-link-ground {background-image: url(img/template4.png),url(img/ext4.png),url(img/ext4.png),url(img/link4.png);} -.cm-mw-template2-ext3-link-ground {background-image: url(img/template4.png),url(img/ext4.png),url(img/ext4.png),url(img/ext4.png),url(img/link4.png);} -.cm-mw-template3-ext-ground {background-image: url(img/template8.png),url(img/ext4.png);} -.cm-mw-template3-ext2-ground {background-image: url(img/template8.png),url(img/ext4.png),url(img/ext4.png);} -.cm-mw-template3-ext3-ground {background-image: url(img/template8.png),url(img/ext4.png),url(img/ext4.png),url(img/ext4.png);} -.cm-mw-template3-link-ground {background-image: url(img/template8.png),url(img/link4.png);} -.cm-mw-template3-ext-link-ground {background-image: url(img/template8.png),url(img/ext4.png),url(img/link4.png);} -.cm-mw-template3-ext2-link-ground {background-image: url(img/template8.png),url(img/ext4.png),url(img/ext4.png),url(img/link4.png);} -.cm-mw-template3-ext3-link-ground {background-image: url(img/template8.png),url(img/ext4.png),url(img/ext4.png),url(img/ext4.png),url(img/link4.png);} -.cm-mw-ext-ground {background-image: url(img/ext4.png)} -.cm-mw-ext2-ground {background-image: url(img/ext4.png),url(img/ext4.png);} -.cm-mw-ext3-ground {background-image: url(img/ext4.png),url(img/ext4.png),url(img/ext4.png);} -.cm-mw-ext-link-ground {background-image: url(img/link4.png);} -.cm-mw-ext2-link-ground {background-image: url(img/ext4.png),url(img/link4.png);} -.cm-mw-ext3-link-ground {background-image: url(img/ext4.png),url(img/ext4.png),url(img/link4.png);} -.cm-mw-link-ground {background-image: url(img/link4.png);} +/* .cm-mw-template-ground {} */ +.cm-mw-template2-ground { background-image: url( img/template4.png ); } +.cm-mw-template3-ground { background-image: url( img/template8.png ); } +.cm-mw-template-ext-ground { background-image: url( img/ext4.png ); } +.cm-mw-template-ext2-ground { background-image: url( img/ext4.png ), url( img/ext4.png ); } +.cm-mw-template-ext3-ground { background-image: url( img/ext4.png ), url( img/ext4.png ), url( img/ext4.png ); } +.cm-mw-template-link-ground { background-image: url( img/link4.png ); } +.cm-mw-template-ext-link-ground { background-image: url( img/ext4.png ), url( img/link4.png ); } +.cm-mw-template-ext2-link-ground { background-image: url( img/ext4.png ), url( img/ext4.png ), url( img/link4.png ); } +.cm-mw-template-ext3-link-ground { background-image: url( img/ext4.png ), url( img/ext4.png ), url( img/ext4.png ), url( img/link4.png ); } +.cm-mw-template2-ext-ground { background-image: url( img/template4.png ), url( img/ext4.png ); } +.cm-mw-template2-ext2-ground { background-image: url( img/template4.png ), url( img/ext4.png ), url( img/ext4.png ); } +.cm-mw-template2-ext3-ground { background-image: url( img/template4.png ), url( img/ext4.png ), url( img/ext4.png ), url( img/ext4.png ); } +.cm-mw-template2-link-ground { background-image: url( img/template4.png ), url( img/link4.png ); } +.cm-mw-template2-ext-link-ground { background-image: url( img/template4.png ), url( img/ext4.png ), url( img/link4.png ); } +.cm-mw-template2-ext2-link-ground { background-image: url( img/template4.png ), url( img/ext4.png ), url( img/ext4.png ), url( img/link4.png ); } +.cm-mw-template2-ext3-link-ground { background-image: url( img/template4.png ), url( img/ext4.png ), url( img/ext4.png ), url( img/ext4.png ), url( img/link4.png ); } +.cm-mw-template3-ext-ground { background-image: url( img/template8.png ), url( img/ext4.png ); } +.cm-mw-template3-ext2-ground { background-image: url( img/template8.png ), url( img/ext4.png ), url( img/ext4.png ); } +.cm-mw-template3-ext3-ground { background-image: url( img/template8.png ), url( img/ext4.png ), url( img/ext4.png ), url( img/ext4.png ); } +.cm-mw-template3-link-ground { background-image: url( img/template8.png ), url( img/link4.png ); } +.cm-mw-template3-ext-link-ground { background-image: url( img/template8.png ), url( img/ext4.png ), url( img/link4.png ); } +.cm-mw-template3-ext2-link-ground { background-image: url( img/template8.png ), url( img/ext4.png ), url( img/ext4.png ), url( img/link4.png ); } +.cm-mw-template3-ext3-link-ground { background-image: url( img/template8.png ), url( img/ext4.png ), url( img/ext4.png ), url( img/ext4.png ), url( img/link4.png ); } +.cm-mw-ext-ground { background-image: url( img/ext4.png ); } +.cm-mw-ext2-ground { background-image: url( img/ext4.png ), url( img/ext4.png ); } +.cm-mw-ext3-ground { background-image: url( img/ext4.png ), url( img/ext4.png ), url( img/ext4.png ); } +.cm-mw-ext-link-ground { background-image: url( img/link4.png ); } +.cm-mw-ext2-link-ground { background-image: url( img/ext4.png ), url( img/link4.png ); } +.cm-mw-ext3-link-ground { background-image: url( img/ext4.png ), url( img/ext4.png ), url( img/link4.png ); } +.cm-mw-link-ground { background-image: url( img/link4.png ); } diff --git a/resources/mode/mediawiki/mediawiki.js b/resources/mode/mediawiki/mediawiki.js index 844b0bf6..985ce5a7 100644 --- a/resources/mode/mediawiki/mediawiki.js +++ b/resources/mode/mediawiki/mediawiki.js @@ -1,3 +1,4 @@ +/* eslint-disable no-use-before-define */ ( function ( CodeMirror ) { 'use strict'; @@ -19,7 +20,7 @@ return style; } - CodeMirror.defineMode( 'mediawiki', function ( config/*, parserConfig */ ) { + CodeMirror.defineMode( 'mediawiki', function ( config /* , parserConfig */ ) { var urlProtocols = new RegExp( config.mwextUrlProtocols, 'i' ), permittedHtmlTags = { b: true, bdi: true, del: true, i: true, ins: true, @@ -354,19 +355,20 @@ function eatLinkText() { var linkIsBold, linkIsItalic; return function ( stream, state ) { + var tmpstyle; if ( stream.match( ']]' ) ) { state.tokenize = state.stack.pop(); return makeLocalStyle( 'mw-link-bracket', state, 'nLink' ); } if ( stream.match( '\'\'\'' ) ) { - linkIsBold = ( linkIsBold ? false : true ); + linkIsBold = !linkIsBold; return makeLocalStyle( 'mw-link-text mw-apostrophes', state ); } if ( stream.match( '\'\'' ) ) { - linkIsItalic = ( linkIsItalic ? false : true ); + linkIsItalic = !linkIsItalic; return makeLocalStyle( 'mw-link-text mw-apostrophes', state ); } - var tmpstyle = 'mw-link-text'; + tmpstyle = 'mw-link-text'; if ( linkIsBold ) { tmpstyle += ' strong'; } @@ -441,7 +443,7 @@ } if ( stream.eat( '>' ) ) { state.extName = name; - if ( name in config.mwextModes.tag ) { + if ( name in config.mwextModes.tag ) { state.extMode = CodeMirror.getMode( config, config.mwextModes.tag[ name ] ); state.extState = CodeMirror.startState( state.extMode ); } @@ -627,13 +629,14 @@ function eatWikiText( style, mnemonicStyle ) { return function ( stream, state ) { + var ch, tmp, mt, name, isCloseTag, tagname, + sol = stream.sol(); + function chain( parser ) { state.stack.push( state.tokenize ); state.tokenize = parser; return parser( stream, state ); } - var ch, - sol = stream.sol(); if ( sol ) { if ( stream.match( urlProtocols ) ) { // highlight free external links, bug T108448 @@ -649,7 +652,7 @@ } break; case '=': - var tmp = stream.match( /(={0,5})(.+?(=\1\s*))$/ ); + tmp = stream.match( /(={0,5})(.+?(=\1\s*))$/ ); if ( tmp ) { // Title stream.backUp( tmp[ 2 ].length ); state.stack.push( state.tokenize ); @@ -685,7 +688,7 @@ return 'mw-skipformatting'; } // break is not necessary here - /*falls through*/ + // falls through case '{': if ( stream.eat( '|' ) ) { stream.eatSpace(); @@ -709,10 +712,10 @@ if ( !( firstsingleletterword || stream.match( '\'\'', false ) ) ) { prepareItalicForCorrection( stream ); } - isBold = isBold ? false : true; + isBold = !isBold; return makeLocalStyle( 'mw-apostrophes-bold', state ); } else if ( stream.eat( '\'' ) ) { // italic - isItalic = isItalic ? false : true; + isItalic = !isItalic; return makeLocalStyle( 'mw-apostrophes-italic', state ); } break; @@ -726,7 +729,7 @@ return makeLocalStyle( 'mw-link-bracket', state ); } } else { - var mt = stream.match( urlProtocols ); + mt = stream.match( urlProtocols ); if ( mt ) { state.nLink++; stream.backUp( mt[ 0 ].length ); @@ -750,7 +753,7 @@ return makeLocalStyle( 'mw-parserfunction-bracket', state ); } // Check for parser function without '#' - var name = stream.match( /([^\s\u00a0\}\[\]<\{\'\|\&\:]+)(\:|[\s\u00a0]*)(\}\}?)?(.)?/ ); + name = stream.match( /([^\s\u00a0\}\[\]<\{\'\|\&\:]+)(\:|[\s\u00a0]*)(\}\}?)?(.)?/ ); if ( name ) { stream.backUp( name[ 0 ].length ); if ( ( name[ 2 ] === ':' || name[ 4 ] === undefined || name[ 3 ] === '}}' ) && ( name[ 1 ].toLowerCase() in config.mwextFunctionSynonyms[ 0 ] || name[ 1 ] in config.mwextFunctionSynonyms[ 1 ] ) ) { @@ -768,8 +771,8 @@ } break; case '<': - var isCloseTag = ( stream.eat( '/' ) ? true : false ); - var tagname = stream.match( /[^>\/\s\u00a0\.\*\,\[\]\{\}\$\^\+\?\|\/\\'`~<=!@#%&\(\)-]+/ ); + isCloseTag = !!stream.eat( '/' ); + tagname = stream.match( /[^>\/\s\u00a0\.\*\,\[\]\{\}\$\^\+\?\|\/\\'`~<=!@#%&\(\)-]+/ ); if ( stream.match( '!--' ) ) { // coment return chain( eatBlock( 'mw-comment', '-->' ) ); } @@ -937,9 +940,10 @@ return t.style; }, blankLine: function ( state ) { + var ret; if ( state.extName ) { if ( state.extMode ) { - var ret = ''; + ret = ''; if ( state.extMode.blankLine ) { ret = ' ' + state.extMode.blankLine( state.extState ); } @@ -955,12 +959,13 @@ function eatNowiki( style, lineStyle ) { return function ( stream, state, ownLine ) { + var s; if ( ownLine && stream.sol() ) { state.ownLine = true; } else if ( ownLine === false && state.ownLine ) { state.ownLine = false; } - var s = ( state.ownLine ? lineStyle : style ); + s = ( state.ownLine ? lineStyle : style ); if ( stream.match( /[^&]+/ ) ) { return s; } @@ -969,14 +974,14 @@ }; } - CodeMirror.defineMode( 'mw-tag-pre', function ( /*config, parserConfig */ ) { + CodeMirror.defineMode( 'mw-tag-pre', function ( /* config, parserConfig */ ) { return { startState: function () { return {}; }, token: eatNowiki( 'mw-tag-pre', 'line-cm-mw-tag-pre' ) }; } ); - CodeMirror.defineMode( 'mw-tag-nowiki', function ( /*config, parserConfig */ ) { + CodeMirror.defineMode( 'mw-tag-nowiki', function ( /* config, parserConfig */ ) { return { startState: function () { return {}; }, token: eatNowiki( 'mw-tag-nowiki', 'line-cm-mw-tag-nowiki' )