├── .gitignore ├── README.md ├── code-mirror-editor.js ├── code-mirror-highlight.js ├── code-mirror-highlighting ├── jsparser.js ├── stringstream.js ├── tokenizejs.js └── tokenizer.js ├── edit.js ├── index.html ├── index.js ├── live-compile.js ├── live-editor.gif ├── live-editor.js ├── package.json └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # live coding react components 2 | 3 | This repo provides one webpage and three components. 4 | 5 | ## index.html 6 | 7 | A simple example of `` in use. 8 | 9 | ## code-mirror-editor.js 10 | 11 | A code mirror react component (called `` since code mirror took the name `CodeMirror`). 12 | 13 | ## live-compile.js 14 | 15 | Dynamic compilation of react components. 16 | 17 | ## live-editor.js 18 | 19 | Combination of `` and `` which live-compiles what you type! 20 | 21 | # Running the demo 22 | 23 | ``` 24 | > webpack 25 | > open index.html 26 | ``` 27 | 28 | You should see something like this: 29 | 30 | ![](live-editor.gif) 31 | -------------------------------------------------------------------------------- /code-mirror-editor.js: -------------------------------------------------------------------------------- 1 | var React = require("react"); 2 | var CodeMirror = require('codemirror'); 3 | require('codemirror/mode/javascript/javascript'); 4 | require('codemirror/addon/fold/foldcode'); 5 | import CodeMirrorHighlight from './code-mirror-highlight'; 6 | 7 | 8 | var IS_MOBILE = ( 9 | navigator.userAgent.match(/Android/i) 10 | || navigator.userAgent.match(/webOS/i) 11 | || navigator.userAgent.match(/iPhone/i) 12 | || navigator.userAgent.match(/iPad/i) 13 | || navigator.userAgent.match(/iPod/i) 14 | || navigator.userAgent.match(/BlackBerry/i) 15 | || navigator.userAgent.match(/Windows Phone/i) 16 | ); 17 | 18 | var OPEN_MARK = /{{{/; 19 | var CLOSE_MARK = /}}}/; 20 | 21 | CodeMirror.registerGlobalHelper('fold', 'marked', 22 | function(mode, mirror) { 23 | return mode.name === 'javascript'; 24 | }, 25 | function(mirror, start) { 26 | var lineNo = start.line; 27 | var lineText = mirror.getLine(lineNo); 28 | var lineCount = mirror.lineCount(); 29 | 30 | var openMatch = OPEN_MARK.exec(lineText); 31 | var closeMatch = CLOSE_MARK.exec(lineText); 32 | 33 | if (openMatch) { 34 | // search forwards 35 | for (var i = lineNo; i < lineCount; i++) { 36 | closeMatch = CLOSE_MARK.exec(mirror.getLine(i)); 37 | if (closeMatch) { 38 | return { 39 | from: CodeMirror.Pos(lineNo, openMatch.index), 40 | to: CodeMirror.Pos(i, closeMatch.index + 3) 41 | }; 42 | } 43 | } 44 | 45 | } else if (closeMatch) { 46 | // search backwards 47 | for (var i = lineNo; i >= 0; i--) { 48 | openMatch = OPEN_MARK.exec(mirror.getLine(i)); 49 | if (openMatch) { 50 | return { 51 | from: CodeMirror.Pos(i, openMatch.index), 52 | to: CodeMirror.Pos(lineNo, closeMatch.index + 3) 53 | }; 54 | } 55 | } 56 | } 57 | } 58 | ); 59 | 60 | var CodeMirrorEditor = React.createClass({ 61 | getDefaultProps() { 62 | return { 63 | renderType: IS_MOBILE ? 'pre' : 'textarea', 64 | } 65 | }, 66 | 67 | componentDidMount: function() { 68 | if (this.props.renderType === 'textarea') { 69 | this.instantiateTextarea(); 70 | } 71 | }, 72 | 73 | instantiateTextarea() { 74 | this.editor = CodeMirror.fromTextArea(this.refs.editor, { 75 | mode: 'javascript', 76 | lineNumbers: false, 77 | lineWrapping: true, 78 | smartIndent: false, // javascript mode does bad things with jsx indents 79 | matchBrackets: true, 80 | theme: 'solarized-light', 81 | readOnly: this.props.readOnly 82 | }); 83 | this.editor.foldCode(0, { widget: '...' }); 84 | this.editor.on('change', this.handleChange); 85 | 86 | this.editor.on('beforeSelectionChange', (instance, obj) => { 87 | // why is ranges plural? 88 | var selection = obj.ranges ? 89 | obj.ranges[0] : 90 | obj; 91 | 92 | var noRange = selection.anchor.ch === selection.head.ch && 93 | selection.anchor.line === selection.head.line; 94 | if (!noRange) { 95 | return; 96 | } 97 | 98 | var cursor = selection.anchor; 99 | var line = instance.getLine(cursor.line); 100 | var match = OPEN_MARK.exec(line) || CLOSE_MARK.exec(line); 101 | 102 | // the opening or closing mark appears on this line 103 | if (match && 104 | // and the cursor is on it 105 | // (this is buggy if both occur on the same line) 106 | cursor.ch >= match.index && 107 | cursor.ch < match.index + 3) { 108 | 109 | // TODO(joel) - figure out why this doesn't fold although it 110 | // seems like it should work. 111 | instance.foldCode(cursor, { widget: '...' }); 112 | } 113 | }); 114 | }, 115 | 116 | componentDidUpdate: function() { 117 | if (this.props.readOnly) { 118 | this.editor.setValue(this.props.codeText); 119 | } 120 | }, 121 | 122 | handleChange: function() { 123 | if (!this.props.readOnly && this.props.onChange) { 124 | this.props.onChange(this.editor.getValue()); 125 | } 126 | }, 127 | 128 | render: function() { 129 | // wrap in a div to fully contain CodeMirror 130 | var editor; 131 | 132 | if (this.props.renderType === 'pre') { 133 | editor = ; 134 | } else { 135 | editor =