diff --git a/client/homebrew/brewRenderer/brewRenderer.jsx b/client/homebrew/brewRenderer/brewRenderer.jsx index 5b337301a..58981dbf3 100644 --- a/client/homebrew/brewRenderer/brewRenderer.jsx +++ b/client/homebrew/brewRenderer/brewRenderer.jsx @@ -20,6 +20,7 @@ const BrewRenderer = createClass({ getDefaultProps : function() { return { text : '', + style : '', renderer : 'legacy', errors : [] }; @@ -187,6 +188,10 @@ const BrewRenderer = createClass({
+ {/* Apply CSS from Style tab */} +
${this.props.style} ` }} /> + + {/* Render pages from Markdown tab */} {this.state.isMounted ? this.renderPages() : null} diff --git a/client/homebrew/editor/editor.jsx b/client/homebrew/editor/editor.jsx index 8fdab0854..b9191d35a 100644 --- a/client/homebrew/editor/editor.jsx +++ b/client/homebrew/editor/editor.jsx @@ -19,57 +19,62 @@ const Editor = createClass({ getDefaultProps : function() { return { brew : { - text : '' + text : '', + style : 'sample style' }, - onChange : ()=>{}, - onMetadataChange : ()=>{}, - showMetaButton : true, - renderer : 'legacy' + onTextChange : ()=>{}, + onStyleChange : ()=>{}, + onMetaChange : ()=>{}, + + renderer : 'legacy' }; }, getInitialState : function() { return { - showMetadataEditor : false + view : 'text' //'text', 'style', 'meta' }; }, - cursorPosition : { - line : 0, - ch : 0 - }, + + isText : function() {return this.state.view == 'text';}, + isStyle : function() {return this.state.view == 'style';}, + isMeta : function() {return this.state.view == 'meta';}, componentDidMount : function() { this.updateEditorSize(); this.highlightCustomMarkdown(); window.addEventListener('resize', this.updateEditorSize); }, + componentWillUnmount : function() { window.removeEventListener('resize', this.updateEditorSize); }, updateEditorSize : function() { - let paneHeight = this.refs.main.parentNode.clientHeight; - paneHeight -= SNIPPETBAR_HEIGHT + 1; - this.refs.codeEditor.codeMirror.setSize(null, paneHeight); + if(this.refs.codeEditor) { + let paneHeight = this.refs.main.parentNode.clientHeight; + paneHeight -= SNIPPETBAR_HEIGHT + 1; + this.refs.codeEditor.codeMirror.setSize(null, paneHeight); + } }, - handleTextChange : function(text){ - this.props.onChange(text); - }, - handleCursorActivty : function(curpos){ - this.cursorPosition = curpos; - }, handleInject : function(injectText){ - const lines = this.props.brew.text.split('\n'); - lines[this.cursorPosition.line] = splice(lines[this.cursorPosition.line], this.cursorPosition.ch, injectText); + const text = (this.isText() ? this.props.brew.text : this.props.brew.style); - this.handleTextChange(lines.join('\n')); - this.refs.codeEditor.setCursorPosition(this.cursorPosition.line + injectText.split('\n').length, this.cursorPosition.ch + injectText.length); + const lines = text.split('\n'); + const cursorPos = this.refs.codeEditor.getCursorPosition(); + lines[cursorPos.line] = splice(lines[cursorPos.line], cursorPos.ch, injectText); + + this.refs.codeEditor.setCursorPosition(cursorPos.line + injectText.split('\n').length, cursorPos.ch + injectText.length); + + if(this.isText()) this.props.onTextChange(lines.join('\n')); + if(this.isStyle()) this.props.onStyleChange(lines.join('\n')); }, - handgleToggle : function(){ + + handleViewChange : function(newView){ this.setState({ - showMetadataEditor : !this.state.showMetadataEditor - }); + view : newView + }, this.updateEditorSize); //TODO: not sure if updateeditorsize needed }, getCurrentPage : function(){ @@ -82,72 +87,73 @@ const Editor = createClass({ highlightCustomMarkdown : function(){ if(!this.refs.codeEditor) return; - const codeMirror = this.refs.codeEditor.codeMirror; + if(this.state.view === 'text') { + const codeMirror = this.refs.codeEditor.codeMirror; - //reset custom text styles - const customHighlights = codeMirror.getAllMarks(); - for (let i=0;i{ + const lineNumbers = _.reduce(this.props.brew.text.split('\n'), (r, line, lineNumber)=>{ - //reset custom line styles - codeMirror.removeLineClass(lineNumber, 'background'); - codeMirror.removeLineClass(lineNumber, 'text'); + //reset custom line styles + codeMirror.removeLineClass(lineNumber, 'background'); + codeMirror.removeLineClass(lineNumber, 'text'); - // Legacy Codemirror styling - if(this.props.renderer == 'legacy') { - if(line.includes('\\page')){ - codeMirror.addLineClass(lineNumber, 'background', 'pageLine'); - r.push(lineNumber); - } - } - - // New Codemirror styling for V3 renderer - if(this.props.renderer == 'V3') { - if(line.startsWith('\\page')){ - codeMirror.addLineClass(lineNumber, 'background', 'pageLine'); - r.push(lineNumber); - } - - if(line.match(/^\\column$/)){ - codeMirror.addLineClass(lineNumber, 'text', 'columnSplit'); - r.push(lineNumber); - } - - // Highlight inline spans {{content}} - if(line.includes('{{') && line.includes('}}')){ - const regex = /{{(?:="[\w,\-. ]*"|[^"'\s])*\s*|}}/g; - let match; - let blockCount = 0; - while ((match = regex.exec(line)) != null) { - if(match[0].startsWith('{')) { - blockCount += 1; - } else { - blockCount -= 1; - } - if(blockCount < 0) { - blockCount = 0; - continue; - } - codeMirror.markText({ line: lineNumber, ch: match.index }, { line: lineNumber, ch: match.index + match[0].length }, { className: 'inline-block' }); + // Legacy Codemirror styling + if(this.props.renderer == 'legacy') { + if(line.includes('\\page')){ + codeMirror.addLineClass(lineNumber, 'background', 'pageLine'); + r.push(lineNumber); } - } else if(line.trimLeft().startsWith('{{') || line.trimLeft().startsWith('}}')){ - // Highlight block divs {{\n Content \n}} - let endCh = line.length+1; - - const match = line.match(/^ *{{(?:="[\w,\-. ]*"|[^"'\s])*$|^ *}}$/); - if(match) - endCh = match.index+match[0].length; - codeMirror.markText({ line: lineNumber, ch: 0 }, { line: lineNumber, ch: endCh }, { className: 'block' }); } - } - return r; - }, []); - return lineNumbers; + // New Codemirror styling for V3 renderer + if(this.props.renderer == 'V3') { + if(line.startsWith('\\page')){ + codeMirror.addLineClass(lineNumber, 'background', 'pageLine'); + r.push(lineNumber); + } + + if(line.match(/^\\column$/)){ + codeMirror.addLineClass(lineNumber, 'text', 'columnSplit'); + r.push(lineNumber); + } + + // Highlight inline spans {{content}} + if(line.includes('{{') && line.includes('}}')){ + const regex = /{{(?:="[\w,\-. ]*"|[^"'\s])*\s*|}}/g; + let match; + let blockCount = 0; + while ((match = regex.exec(line)) != null) { + if(match[0].startsWith('{')) { + blockCount += 1; + } else { + blockCount -= 1; + } + if(blockCount < 0) { + blockCount = 0; + continue; + } + codeMirror.markText({ line: lineNumber, ch: match.index }, { line: lineNumber, ch: match.index + match[0].length }, { className: 'inline-block' }); + } + } else if(line.trimLeft().startsWith('{{') || line.trimLeft().startsWith('}}')){ + // Highlight block divs {{\n Content \n}} + let endCh = line.length+1; + + const match = line.match(/^ *{{(?:="[\w,\-. ]*"|[^"'\s])*$|^ *}}$/); + if(match) + endCh = match.index+match[0].length; + codeMirror.markText({ line: lineNumber, ch: 0 }, { line: lineNumber, ch: endCh }, { className: 'block' }); + } + } + + return r; + }, []); + return lineNumbers; + } }, - brewJump : function(){ const currentPage = this.getCurrentPage(); window.location.hash = `p${currentPage}`; @@ -158,12 +164,26 @@ const Editor = createClass({ this.refs.codeEditor.updateSize(); }, - renderMetadataEditor : function(){ - if(!this.state.showMetadataEditor) return; - return ; + renderEditor : function(){ + if(this.isText()){ + return ; + } + if(this.isStyle()){ + return ; + } + if(this.isMeta()){ + return ; + } }, render : function(){ @@ -172,25 +192,13 @@ const Editor = createClass({
- {this.renderMetadataEditor()} - - {/* -
- -
- */} + {this.renderEditor()}
); } diff --git a/client/homebrew/editor/snippetbar/snippetbar.jsx b/client/homebrew/editor/snippetbar/snippetbar.jsx index c42e899d9..0d57fdb60 100644 --- a/client/homebrew/editor/snippetbar/snippetbar.jsx +++ b/client/homebrew/editor/snippetbar/snippetbar.jsx @@ -16,12 +16,13 @@ const execute = function(val, brew){ const Snippetbar = createClass({ getDefaultProps : function() { return { - brew : {}, - onInject : ()=>{}, - onToggle : ()=>{}, - showmeta : false, - showMetaButton : true, - renderer : '' + brew : {}, + view : 'text', + onViewChange : ()=>{}, + onInject : ()=>{}, + onToggle : ()=>{}, + showEditButtons : true, + renderer : 'legacy' }; }, @@ -36,12 +37,17 @@ const Snippetbar = createClass({ }, renderSnippetGroups : function(){ - if(this.props.renderer == 'V3') - Snippets = SnippetsV3; - else - Snippets = SnippetsLegacy; - return _.map(Snippets, (snippetGroup)=>{ + if(this.props.view === 'text') { + if(this.props.renderer === 'V3') + snippets = SnippetsV3; + else + snippets = SnippetsLegacy; + } else { + snippets = []; + } + + return _.map(snippets, (snippetGroup)=>{ return - - Properties + renderEditorButtons : function(){ + if(!this.props.showEditButtons) return; + + return
+
this.props.onViewChange('text')}> + +
+
this.props.onViewChange('style')}> + +
+
this.props.onViewChange('meta')}> + +
; }, render : function(){ return
{this.renderSnippetGroups()} - {this.renderMetadataButton()} + {this.renderEditorButtons()}
; } }); diff --git a/client/homebrew/editor/snippetbar/snippetbar.less b/client/homebrew/editor/snippetbar/snippetbar.less index e84d373b5..ae8962501 100644 --- a/client/homebrew/editor/snippetbar/snippetbar.less +++ b/client/homebrew/editor/snippetbar/snippetbar.less @@ -1,12 +1,40 @@ .snippetBar{ - @height : 25px; + @menuHeight : 25px; position : relative; - height : @height; + height : @menuHeight; background-color : #ddd; + .editors{ + position : absolute; + display : flex; + top : 0px; + right : 0px; + height : @menuHeight; + width : 90px; + justify-content : space-between; + &>div{ + height : @menuHeight; + width : @menuHeight; + cursor : pointer; + line-height : @menuHeight; + text-align : center; + &:hover,&.selected{ + background-color : #999; + } + &.text{ + .tooltipLeft('Brew Editor'); + } + &.style{ + .tooltipLeft('Style Editor'); + } + &.meta{ + .tooltipLeft('Properties'); + } + } + } .snippetBarButton{ - height : @height; - line-height : @height; + height : @menuHeight; + line-height : @menuHeight; display : inline-block; padding : 0px 5px; font-weight : 800; diff --git a/client/homebrew/editor/snippetbar/snippets/snippets.js b/client/homebrew/editor/snippetbar/snippets/snippets.js index 99fe62572..f72817c7a 100644 --- a/client/homebrew/editor/snippetbar/snippets/snippets.js +++ b/client/homebrew/editor/snippetbar/snippets/snippets.js @@ -66,7 +66,7 @@ module.exports = [ { name : 'Auto-incrementing Page Number', icon : 'fas fa-sort-numeric-down', - gen : '{{\npageNumber,auto\n}}\n\n' + gen : '{{pageNumber,auto\n}}\n\n' }, { name : 'Link to page', diff --git a/client/homebrew/pages/editPage/editPage.jsx b/client/homebrew/pages/editPage/editPage.jsx index b60a318be..8b712e055 100644 --- a/client/homebrew/pages/editPage/editPage.jsx +++ b/client/homebrew/pages/editPage/editPage.jsx @@ -31,6 +31,7 @@ const EditPage = createClass({ return { brew : { text : '', + style : '', shareId : null, editId : null, createdAt : null, @@ -106,17 +107,8 @@ const EditPage = createClass({ this.refs.editor.update(); }, - handleMetadataChange : function(metadata){ - this.setState((prevState)=>({ - brew : _.merge({}, prevState.brew, metadata), - isPending : true, - }), ()=>this.trySave()); - - }, - handleTextChange : function(text){ - - //If there are errors, run the validator on everychange to give quick feedback + //If there are errors, run the validator on every change to give quick feedback let htmlErrors = this.state.htmlErrors; if(htmlErrors.length) htmlErrors = Markdown.validate(text); @@ -127,6 +119,21 @@ const EditPage = createClass({ }), ()=>this.trySave()); }, + handleStyleChange : function(style){ + this.setState((prevState)=>({ + brew : _.merge({}, prevState.brew, { style: style }), + isPending : true + }), ()=>this.trySave()); + }, + + handleMetaChange : function(metadata){ + this.setState((prevState)=>({ + brew : _.merge({}, prevState.brew, metadata), + isPending : true, + }), ()=>this.trySave()); + + }, + hasChanges : function(){ return !_.isEqual(this.state.brew, this.savedBrew); }, @@ -141,7 +148,6 @@ const EditPage = createClass({ }, handleGoogleClick : function(){ - console.log('handlegoogleclick'); if(!global.account?.googleId) { this.setState({ alertLoginToTransfer : true @@ -409,11 +415,12 @@ const EditPage = createClass({ - +
; diff --git a/client/homebrew/pages/homePage/homePage.jsx b/client/homebrew/pages/homePage/homePage.jsx index fbff8926c..59d6a22e8 100644 --- a/client/homebrew/pages/homePage/homePage.jsx +++ b/client/homebrew/pages/homePage/homePage.jsx @@ -78,7 +78,13 @@ const HomePage = createClass({
- +
diff --git a/client/homebrew/pages/newPage/newPage.jsx b/client/homebrew/pages/newPage/newPage.jsx index 323744d3c..baffa0cfc 100644 --- a/client/homebrew/pages/newPage/newPage.jsx +++ b/client/homebrew/pages/newPage/newPage.jsx @@ -24,6 +24,7 @@ const NewPage = createClass({ return { brew : { text : '', + style : '', shareId : null, editId : null, createdAt : null, @@ -44,6 +45,7 @@ const NewPage = createClass({ return { brew : { text : this.props.brew.text || '', + style : this.props.brew.style || '', gDrive : false, title : this.props.brew.title || '', description : this.props.brew.description || '', @@ -55,7 +57,8 @@ const NewPage = createClass({ isSaving : false, saveGoogle : (global.account && global.account.googleId ? true : false), - errors : [] + errors : [], + htmlErrors : Markdown.validate(this.props.brew.text) }; }, @@ -66,6 +69,11 @@ const NewPage = createClass({ brew : { text: storage } }); } + + this.setState((prevState)=>({ + htmlErrors : Markdown.validate(prevState.brew.text) + })); + document.addEventListener('keydown', this.handleControlKeys); }, componentWillUnmount : function() { @@ -88,18 +96,29 @@ const NewPage = createClass({ this.refs.editor.update(); }, - handleMetadataChange : function(metadata){ - this.setState({ - brew : _.merge({}, this.state.brew, metadata) - }); + handleTextChange : function(text){ + //If there are errors, run the validator on every change to give quick feedback + let htmlErrors = this.state.htmlErrors; + if(htmlErrors.length) htmlErrors = Markdown.validate(text); + + this.setState((prevState)=>({ + brew : _.merge({}, prevState.brew, { text: text }), + htmlErrors : htmlErrors + })); + localStorage.setItem(KEY, text); }, - handleTextChange : function(text){ - this.setState({ - brew : { text: text }, - errors : Markdown.validate(text) - }); - localStorage.setItem(KEY, text); + handleStyleChange : function(style){ + this.setState((prevState)=>({ + brew : _.merge({}, prevState.brew, { style: style }), + })); + }, + + handleMetaChange : function(metadata){ + this.setState((prevState)=>({ + brew : _.merge({}, prevState.brew, metadata), + })); + }, save : async function(){ @@ -190,10 +209,12 @@ const NewPage = createClass({ - + ; diff --git a/client/homebrew/pages/printPage/printPage.jsx b/client/homebrew/pages/printPage/printPage.jsx index dee417f74..b9bcfda5d 100644 --- a/client/homebrew/pages/printPage/printPage.jsx +++ b/client/homebrew/pages/printPage/printPage.jsx @@ -13,6 +13,7 @@ const PrintPage = createClass({ query : {}, brew : { text : '', + style : '', renderer : 'legacy' } }; @@ -58,6 +59,8 @@ const PrintPage = createClass({ render : function(){ return
+ {/* Apply CSS from Style tab */} +
${this.props.brew.style} ` }} /> {this.renderPages()}
; } diff --git a/client/homebrew/pages/sharePage/sharePage.jsx b/client/homebrew/pages/sharePage/sharePage.jsx index 529c0d9ee..1411c3175 100644 --- a/client/homebrew/pages/sharePage/sharePage.jsx +++ b/client/homebrew/pages/sharePage/sharePage.jsx @@ -19,6 +19,7 @@ const SharePage = createClass({ brew : { title : '', text : '', + style : '', shareId : null, createdAt : null, updatedAt : null, @@ -72,7 +73,7 @@ const SharePage = createClass({
- +
; } diff --git a/server.js b/server.js index c5861238e..b1415f130 100644 --- a/server.js +++ b/server.js @@ -21,11 +21,28 @@ const getBrewFromId = asyncHandler(async (id, accessType)=>{ brew = await GoogleActions.readFileMetadata(config.get('google_api_key'), googleId, id, accessType); } else { brew = await HomebrewModel.get(accessType == 'edit' ? { editId: id } : { shareId: id }); - brew.sanatize(true); + brew = brew.toObject(); + } + + brew = sanitizeBrew(brew, accessType === 'edit' ? false : true); + //Split brew.text into text and style + if(brew.text.startsWith('```css')) { + const index = brew.text.indexOf('```\n\n'); + brew.style = brew.text.slice(7, index - 1); + brew.text = brew.text.slice(index + 5); } return brew; }); +const sanitizeBrew = (brew, full=false)=>{ + delete brew._id; + delete brew.__v; + if(full){ + delete brew.editId; + } + return brew; +}; + app.use('/', serveCompressedStaticAssets(`${__dirname}/build`)); process.chdir(__dirname); @@ -160,7 +177,7 @@ app.get('/share/:id', asyncHandler(async (req, res, next)=>{ await GoogleActions.increaseView(googleId, shareId, 'share', brew) .catch((err)=>{next(err);}); } else { - await brew.increaseView(); + await HomebrewModel.increaseView({ shareId: brew.shareId }); } req.brew = brew; diff --git a/server/googleActions.js b/server/googleActions.js index 184eac2f5..c0a257db6 100644 --- a/server/googleActions.js +++ b/server/googleActions.js @@ -152,17 +152,17 @@ GoogleActions = { fileId : brew.googleId, resource : { name : `${brew.title}.txt`, description : `${brew.description}`, - properties : { title : brew.title, - published : brew.published, - lastViewed : brew.lastViewed, - views : brew.views, - version : brew.version, - renderer : brew.renderer, - tags : brew.tags, - systems : brew.systems.join() } + properties : { title : brew.title, + published : brew.published, + lastViewed : brew.lastViewed, + views : brew.views, + version : brew.version, + renderer : brew.renderer, + tags : brew.tags, + systems : brew.systems.join() } }, media : { mimeType : 'text/plain', - body : brew.text } + body : brew.text } }) .catch((err)=>{ console.log('Error saving to google'); diff --git a/server/homebrew.api.js b/server/homebrew.api.js index 02f88c937..51c9903db 100644 --- a/server/homebrew.api.js +++ b/server/homebrew.api.js @@ -19,14 +19,24 @@ const getGoodBrewTitle = (text)=>{ .slice(0, MAX_TITLE_LENGTH); }; +const mergeBrewText = (text, style)=>{ + text = `\`\`\`css\n` + + `${style}\n` + + `\`\`\`\n\n` + + `${text}`; + return text; +}; + const newBrew = (req, res)=>{ const brew = req.body; - brew.authors = (req.account) ? [req.account.username] : []; if(!brew.title) { brew.title = getGoodBrewTitle(brew.text); } + brew.authors = (req.account) ? [req.account.username] : []; + brew.text = mergeBrewText(brew.text, brew.style); + delete brew.editId; delete brew.shareId; delete brew.googleId; @@ -53,8 +63,10 @@ const updateBrew = (req, res)=>{ HomebrewModel.get({ editId: req.params.id }) .then((brew)=>{ brew = _.merge(brew, req.body); + brew.text = mergeBrewText(brew.text, brew.style); + // Compress brew text to binary before saving - brew.textBin = zlib.deflateRawSync(req.body.text); + brew.textBin = zlib.deflateRawSync(brew.text); // Delete the non-binary text field since it's not needed anymore brew.text = undefined; brew.updatedAt = new Date(); @@ -113,12 +125,14 @@ const newGoogleBrew = async (req, res, next)=>{ try { oAuth2Client = GoogleActions.authCheck(req.account, res); } catch (err) { return res.status(err.status).send(err.message); } const brew = req.body; - brew.authors = (req.account) ? [req.account.username] : []; if(!brew.title) { brew.title = getGoodBrewTitle(brew.text); } + brew.authors = (req.account) ? [req.account.username] : []; + brew.text = mergeBrewText(brew.text, brew.style); + delete brew.editId; delete brew.shareId; delete brew.googleId; @@ -135,7 +149,10 @@ const updateGoogleBrew = async (req, res, next)=>{ try { oAuth2Client = GoogleActions.authCheck(req.account, res); } catch (err) { return res.status(err.status).send(err.message); } - const updatedBrew = await GoogleActions.updateGoogleBrew(oAuth2Client, req.body); + const brew = req.body; + brew.text = mergeBrewText(brew.text, brew.style); + + const updatedBrew = await GoogleActions.updateGoogleBrew(oAuth2Client, brew); return res.status(200).send(updatedBrew); }; diff --git a/server/homebrew.model.js b/server/homebrew.model.js index 9a4b14427..f302149e8 100644 --- a/server/homebrew.model.js +++ b/server/homebrew.model.js @@ -24,28 +24,15 @@ const HomebrewSchema = mongoose.Schema({ version : { type: Number, default: 1 } }, { versionKey: false }); - -HomebrewSchema.methods.sanatize = function(full=false){ - const brew = this.toJSON(); - delete brew._id; - delete brew.__v; - if(full){ - delete brew.editId; - } - return brew; -}; - -HomebrewSchema.methods.increaseView = async function(){ - this.lastViewed = new Date(); - this.views = this.views + 1; - const text = this.text; - this.text = undefined; - await this.save() +HomebrewSchema.statics.increaseView = async function(query) { + const brew = await Homebrew.findOne(query).exec(); + brew.lastViewed = new Date(); + brew.views = brew.views + 1; + await brew.save() .catch((err)=>{ return err; }); - this.text = text; - return this; + return brew; }; HomebrewSchema.statics.get = function(query){ diff --git a/shared/naturalcrit/codeEditor/codeEditor.jsx b/shared/naturalcrit/codeEditor/codeEditor.jsx index 99855e4ce..a40c48436 100644 --- a/shared/naturalcrit/codeEditor/codeEditor.jsx +++ b/shared/naturalcrit/codeEditor/codeEditor.jsx @@ -11,28 +11,42 @@ if(typeof navigator !== 'undefined'){ //Language Modes require('codemirror/mode/gfm/gfm.js'); //Github flavoured markdown + require('codemirror/mode/css/css.js'); require('codemirror/mode/javascript/javascript.js'); } - const CodeEditor = createClass({ getDefaultProps : function() { return { - language : '', - value : '', - wrap : false, - onChange : function(){}, - onCursorActivity : function(){}, + language : '', + value : '', + wrap : true, + onChange : ()=>{} }; }, componentDidMount : function() { + this.buildEditor(); + }, + + componentDidUpdate : function(prevProps) { + if(prevProps.language !== this.props.language){ //rebuild editor when switching tabs + this.buildEditor(); + } + if(this.codeMirror && this.codeMirror.getValue() != this.props.value) { //update editor contents if brew.text is changed from outside + this.codeMirror.setValue(this.props.value); + } + }, + + buildEditor : function() { this.codeMirror = CodeMirror(this.refs.editor, { - value : this.props.value, - lineNumbers : true, - lineWrapping : this.props.wrap, - mode : this.props.language, - extraKeys : { + value : this.props.value, + lineNumbers : true, + lineWrapping : this.props.wrap, + mode : this.props.language, //TODO: CSS MODE DOESN'T SEEM TO LOAD PROPERLY + indentWithTabs : true, + tabSize : 2, + extraKeys : { 'Ctrl-B' : this.makeBold, 'Cmd-B' : this.makeBold, 'Ctrl-I' : this.makeItalic, @@ -42,8 +56,8 @@ const CodeEditor = createClass({ } }); - this.codeMirror.on('change', this.handleChange); - this.codeMirror.on('cursorActivity', this.handleCursorActivity); + // 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());}); this.updateSize(); }, @@ -74,29 +88,20 @@ const CodeEditor = createClass({ } }, - componentDidUpdate : function(prevProps) { - if(this.codeMirror && this.codeMirror.getValue() != this.props.value) { - this.codeMirror.setValue(this.props.value); - } - }, - + //=-- Externally used -==// setCursorPosition : function(line, char){ setTimeout(()=>{ this.codeMirror.focus(); this.codeMirror.doc.setCursor(line, char); }, 10); }, - + getCursorPosition : function(){ + return this.codeMirror.getCursor(); + }, updateSize : function(){ this.codeMirror.refresh(); }, - - handleChange : function(editor){ - this.props.onChange(editor.getValue()); - }, - handleCursorActivity : function(){ - this.props.onCursorActivity(this.codeMirror.doc.getCursor()); - }, + //----------------------// render : function(){ return
;