diff --git a/client/homebrew/editor/editor.jsx b/client/homebrew/editor/editor.jsx index 6f9ce517f..6eefc183f 100644 --- a/client/homebrew/editor/editor.jsx +++ b/client/homebrew/editor/editor.jsx @@ -117,7 +117,7 @@ const Editor = createClass({ _.forEach(this.props.brew.text.split('\n'), (line, lineNumber)=>{ //reset custom line styles - codeMirror.removeLineClass(lineNumber, 'background'); + codeMirror.removeLineClass(lineNumber, 'background', 'pageLine'); codeMirror.removeLineClass(lineNumber, 'text'); // Styling for \page breaks diff --git a/scripts/project.json b/scripts/project.json index a83b7a0a1..07acdda48 100644 --- a/scripts/project.json +++ b/scripts/project.json @@ -14,6 +14,16 @@ "codemirror/mode/javascript/javascript.js", "codemirror/addon/fold/foldcode.js", "codemirror/addon/fold/foldgutter.js", + "codemirror/addon/fold/xml-fold.js", + "codemirror/addon/search/search.js", + "codemirror/addon/search/searchcursor.js", + "codemirror/addon/search/jump-to-line.js", + "codemirror/addon/search/match-highlighter.js", + "codemirror/addon/search/matchesonscrollbar.js", + "codemirror/addon/dialog/dialog.js", + "codemirror/addon/edit/closetag.js", + "codemirror/addon/edit/trailingspace.js", + "codemirror/addon/selection/active-line.js", "moment", "superagent", "marked" diff --git a/shared/naturalcrit/codeEditor/close-tag.js b/shared/naturalcrit/codeEditor/close-tag.js new file mode 100644 index 000000000..728b63a5c --- /dev/null +++ b/shared/naturalcrit/codeEditor/close-tag.js @@ -0,0 +1,48 @@ +const autoCloseCurlyBraces = function(CodeMirror, cm, typingClosingBrace) { + const ranges = cm.listSelections(), replacements = []; + for (let i = 0; i < ranges.length; i++) { + if(!ranges[i].empty()) return CodeMirror.Pass; + const pos = ranges[i].head, line = cm.getLine(pos.line), tok = cm.getTokenAt(pos); + if(!typingClosingBrace && (tok.type == 'string' || tok.string.charAt(0) != '{' || tok.start != pos.ch - 1)) + return CodeMirror.Pass; + else if(typingClosingBrace) { + let hasUnclosedBraces = false, index = -1; + do { + index = line.indexOf('{{', index + 1); + if(index !== -1 && line.indexOf('}}', index + 1) === -1) { + hasUnclosedBraces = true; + break; + } + } while (index !== -1); + if(!hasUnclosedBraces) return CodeMirror.Pass; + } + + replacements[i] = typingClosingBrace ? { + text : '}}', + newPos : CodeMirror.Pos(pos.line, pos.ch + 2) + } : { + text : '{}}', + newPos : CodeMirror.Pos(pos.line, pos.ch + 1) + }; + } + + for (let i = ranges.length - 1; i >= 0; i--) { + const info = replacements[i]; + cm.replaceRange(info.text, ranges[i].head, ranges[i].anchor, '+insert'); + const sel = cm.listSelections().slice(0); + sel[i] = { + head : info.newPos, + anchor : info.newPos + }; + cm.setSelections(sel); + } +}; + +module.exports = { + autoCloseCurlyBraces : function(CodeMirror, codeMirror) { + const map = { name: 'autoCloseCurlyBraces' }; + map[`'{'`] = function(cm) { return autoCloseCurlyBraces(CodeMirror, cm); }; + map[`'}'`] = function(cm) { return autoCloseCurlyBraces(CodeMirror, cm, true); }; + codeMirror.addKeyMap(map); + } +}; \ No newline at end of file diff --git a/shared/naturalcrit/codeEditor/codeEditor.jsx b/shared/naturalcrit/codeEditor/codeEditor.jsx index 5999df071..0956521f4 100644 --- a/shared/naturalcrit/codeEditor/codeEditor.jsx +++ b/shared/naturalcrit/codeEditor/codeEditor.jsx @@ -4,7 +4,7 @@ const React = require('react'); const createClass = require('create-react-class'); const _ = require('lodash'); const cx = require('classnames'); - +const closeTag = require('./close-tag'); let CodeMirror; if(typeof navigator !== 'undefined'){ @@ -16,8 +16,24 @@ if(typeof navigator !== 'undefined'){ require('codemirror/mode/javascript/javascript.js'); //Addons + //Code folding require('codemirror/addon/fold/foldcode.js'); require('codemirror/addon/fold/foldgutter.js'); + //Search and replace + require('codemirror/addon/search/search.js'); + require('codemirror/addon/search/searchcursor.js'); + require('codemirror/addon/search/jump-to-line.js'); + require('codemirror/addon/search/match-highlighter.js'); + require('codemirror/addon/search/matchesonscrollbar.js'); + require('codemirror/addon/dialog/dialog.js'); + //Trailing space highlighting + require('codemirror/addon/edit/trailingspace.js'); + //Active line highlighting + require('codemirror/addon/selection/active-line.js'); + //Auto-closing + //XML code folding is a requirement of the auto-closing tag feature and is not enabled + require('codemirror/addon/fold/xml-fold.js'); + require('codemirror/addon/edit/closetag.js'); const foldCode = require('./fold-code'); foldCode.registerHomebreweryHelper(CodeMirror); @@ -115,6 +131,9 @@ const CodeEditor = createClass({ 'Shift-Cmd-Enter' : this.newColumn, 'Ctrl-Enter' : this.newPage, 'Cmd-Enter' : this.newPage, + 'Ctrl-F' : 'findPersistent', + 'Cmd-F' : 'findPersistent', + 'Shift-Enter' : 'findPersistentPrevious', 'Ctrl-[' : this.foldAllCode, 'Cmd-[' : this.foldAllCode, 'Ctrl-]' : this.unfoldAllCode, @@ -142,8 +161,19 @@ const CodeEditor = createClass({ return `\u21A4 ${text} \u21A6`; } }, - gutters : ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'] + gutters : ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'], + autoCloseTags : true, + styleActiveLine : true, + showTrailingSpace : true, + specialChars : / /, + specialCharPlaceholder : function(char) { + const el = document.createElement('span'); + el.className = 'cm-space'; + el.innerHTML = ' '; + return el; + } }); + closeTag.autoCloseCurlyBraces(CodeMirror, this.codeMirror); // Note: codeMirror passes a copy of itself in this callback. cm === this.codeMirror. Either one works. this.codeMirror.on('change', (cm)=>{this.props.onChange(cm.getValue());}); diff --git a/shared/naturalcrit/codeEditor/codeEditor.less b/shared/naturalcrit/codeEditor/codeEditor.less index 0ad7a27f8..037a326bc 100644 --- a/shared/naturalcrit/codeEditor/codeEditor.less +++ b/shared/naturalcrit/codeEditor/codeEditor.less @@ -1,5 +1,7 @@ @import (less) 'codemirror/lib/codemirror.css'; @import (less) 'codemirror/addon/fold/foldgutter.css'; +@import (less) 'codemirror/addon/search/matchesonscrollbar.css'; +@import (less) 'codemirror/addon/dialog/dialog.css'; .codeEditor{ .CodeMirror-foldmarker { @@ -7,4 +9,14 @@ text-shadow: none; font-weight: 600; } + + .cm-tab { + background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAMCAQAAACOs/baAAAARUlEQVR4nGJgIAG8JkXxUAcCtDWemcGR1lY4MvgzCEKY7jSBjgxBDAG09UEQzAe0AMwMHrSOAwEGRtpaMIwAAAAA//8DAG4ID9EKs6YqAAAAAElFTkSuQmCC) no-repeat right; + } + + .cm-trailingspace { + .cm-space { + background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAQAgMAAABW5NbuAAAACVBMVEVHcEwAAAAAAAAWawmTAAAAA3RSTlMAPBJ6PMxpAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAFUlEQVQI12NgwACcCQysASAEZGAAACMuAX06aCQUAAAAAElFTkSuQmCC) no-repeat right; + } + } } \ No newline at end of file