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 3fdfec252..8f23d984c 100644 --- a/shared/naturalcrit/codeEditor/codeEditor.jsx +++ b/shared/naturalcrit/codeEditor/codeEditor.jsx @@ -3,7 +3,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'){ @@ -17,6 +17,16 @@ if(typeof navigator !== 'undefined'){ //Addons require('codemirror/addon/fold/foldcode.js'); require('codemirror/addon/fold/foldgutter.js'); + require('codemirror/addon/fold/xml-fold.js'); + 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'); + require('codemirror/addon/edit/closetag.js'); + require('codemirror/addon/edit/trailingspace.js'); + require('codemirror/addon/selection/active-line.js'); const foldCode = require('./fold-code'); foldCode.registerHomebreweryHelper(CodeMirror); @@ -74,20 +84,22 @@ const CodeEditor = createClass({ tabSize : 2, historyEventDelay : 250, extraKeys : { - 'Ctrl-B' : this.makeBold, - 'Cmd-B' : this.makeBold, - 'Ctrl-I' : this.makeItalic, - 'Cmd-I' : this.makeItalic, - 'Ctrl-M' : this.makeSpan, - 'Cmd-M' : this.makeSpan, - 'Ctrl-/' : this.makeComment, - 'Cmd-/' : this.makeComment, - 'Ctrl-\\' : this.toggleCodeFolded, - 'Cmd-\\' : this.toggleCodeFolded, - 'Ctrl-[' : this.foldAllCode, - 'Cmd-[' : this.foldAllCode, - 'Ctrl-]' : this.unfoldAllCode, - 'Cmd-]' : this.unfoldAllCode + 'Ctrl-B' : this.makeBold, + 'Cmd-B' : this.makeBold, + 'Ctrl-I' : this.makeItalic, + 'Cmd-I' : this.makeItalic, + 'Ctrl-M' : this.makeSpan, + 'Cmd-M' : this.makeSpan, + 'Ctrl-/' : this.makeComment, + 'Cmd-/' : this.makeComment, + 'Ctrl-\\' : this.toggleCodeFolded, + 'Cmd-\\' : this.toggleCodeFolded, + 'Ctrl-[' : this.foldAllCode, + 'Cmd-[' : this.foldAllCode, + 'Ctrl-]' : this.unfoldAllCode, + 'Cmd-]' : this.unfoldAllCode, + 'Ctrl-Alt-F' : this.findPersistent, + 'Cmd-Opt-F' : this.findPersistent }, foldGutter : true, foldOptions : { @@ -107,8 +119,12 @@ const CodeEditor = createClass({ return `\u21A4${text.substr(0, maxLength)}\u21A6`; } }, - gutters : ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'] + gutters : ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'], + showTrailingSpace : true, + autoCloseTags : true, + styleActiveLine : true }); + 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());}); @@ -163,6 +179,10 @@ const CodeEditor = createClass({ this.codeMirror.execCommand('unfoldAll'); }, + findPersistent : function() { + this.codeMirror.execCommand('findPersistent'); + }, + //=-- Externally used -==// setCursorPosition : function(line, char){ setTimeout(()=>{ diff --git a/shared/naturalcrit/codeEditor/codeEditor.less b/shared/naturalcrit/codeEditor/codeEditor.less index 5c0680349..fa00ba750 100644 --- a/shared/naturalcrit/codeEditor/codeEditor.less +++ b/shared/naturalcrit/codeEditor/codeEditor.less @@ -1,8 +1,16 @@ @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 { font-family: inherit; } + + .cm-trailingspace { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAACCAYAAAB/qH1jAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QUXCToH00Y1UgAAACFJREFUCNdjPMDBUc/AwNDAAAFMTAwMDA0OP34wQgX/AQBYgwYEx4f9lQAAAABJRU5ErkJggg==); + background-position: bottom left; + background-repeat: repeat-x; + } } \ No newline at end of file