0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2026-01-13 15:12:43 +00:00

Merge branch 'styleEditor' into noHtml

This commit is contained in:
Scott Tolksdorf
2017-01-30 10:48:34 -05:00
14 changed files with 220 additions and 92 deletions

View File

@@ -33,7 +33,7 @@ const ContinousSave = React.createClass({
window.onbeforeunload = function(){}; window.onbeforeunload = function(){};
}, },
actionHandler : function(actionType){ actionHandler : function(actionType){
if(actionType == 'UPDATE_BREW_TEXT' || actionType == 'UPDATE_META'){ if(actionType == 'UPDATE_BREW_CODE' || actionType == 'UPDATE_META' || actionType == 'UPDATE_BREW_STYLE'){
Actions.pendingSave(); Actions.pendingSave();
} }
}, },
@@ -51,6 +51,9 @@ const ContinousSave = React.createClass({
Oops! Oops!
<div className='errorContainer'> <div className='errorContainer'>
Looks like there was a problem saving. <br /> Looks like there was a problem saving. <br />
Back up your brew in a text file, just in case.
<br /><br />
Report the issue <a target='_blank' href={'https://github.com/stolksdorf/naturalcrit/issues/new?body='+ encodeURIComponent(errMsg)}> Report the issue <a target='_blank' href={'https://github.com/stolksdorf/naturalcrit/issues/new?body='+ encodeURIComponent(errMsg)}>
here here
</a>. </a>.

View File

@@ -120,7 +120,7 @@
top : 29px; top : 29px;
left : -20px; left : -20px;
z-index : 1000; z-index : 1000;
width : 120px; width : 170px;
padding : 8px; padding : 8px;
background-color : #333; background-color : #333;
a{ a{

View File

@@ -10,6 +10,7 @@
"codemirror", "codemirror",
"codemirror/mode/gfm/gfm.js", "codemirror/mode/gfm/gfm.js",
"codemirror/mode/javascript/javascript.js", "codemirror/mode/javascript/javascript.js",
"codemirror/mode/css/css.js",
"moment", "moment",
"superagent", "superagent",
"marked", "marked",

View File

@@ -10,6 +10,7 @@ const BrewSchema = mongoose.Schema({
editId : {type : String, default: shortid.generate, index: { unique: true }}, editId : {type : String, default: shortid.generate, index: { unique: true }},
text : {type : String, default : ""}, text : {type : String, default : ""},
style : {type : String, default : ""},
title : {type : String, default : ""}, title : {type : String, default : ""},
description : {type : String, default : ""}, description : {type : String, default : ""},
@@ -23,7 +24,7 @@ const BrewSchema = mongoose.Schema({
updatedAt : { type: Date, default: Date.now}, updatedAt : { type: Date, default: Date.now},
lastViewed : { type: Date, default: Date.now}, lastViewed : { type: Date, default: Date.now},
views : {type:Number, default:0}, views : {type:Number, default:0},
version : {type: Number, default:1} version : {type: Number, default:2}
}, { }, {
versionKey: false, versionKey: false,
toJSON : { toJSON : {

View File

@@ -54,10 +54,13 @@ const Actions = {
setBrew : (brew) => { setBrew : (brew) => {
dispatch('SET_BREW', brew); dispatch('SET_BREW', brew);
}, },
updateBrewText : (brewText) => { updateBrewCode : (brewCode) => {
dispatch('UPDATE_BREW_TEXT', brewText) dispatch('UPDATE_BREW_CODE', brewCode)
}, },
updateMetaData : (meta) => { updateBrewStyle : (style) => {
dispatch('UPDATE_BREW_STYLE', style)
},
updateMetadata : (meta) => {
dispatch('UPDATE_META', meta); dispatch('UPDATE_META', meta);
}, },
pendingSave : () => { pendingSave : () => {

View File

@@ -8,6 +8,7 @@ let State = {
brew : { brew : {
text : '', text : '',
style : '',
shareId : undefined, shareId : undefined,
editId : undefined, editId : undefined,
createdAt : undefined, createdAt : undefined,
@@ -29,9 +30,13 @@ const Store = flux.createStore({
SET_BREW : (brew) => { SET_BREW : (brew) => {
State.brew = brew; State.brew = brew;
}, },
UPDATE_BREW_TEXT : (brewText) => { UPDATE_BREW_CODE : (brewCode) => {
State.brew.text = brewText; State.brew.text = brewCode;
State.errors = Markdown.validate(brewText); State.errors = Markdown.validate(brewCode);
},
UPDATE_BREW_STYLE : (style) => {
//TODO: add in an error checker?
State.brew.style = style;
}, },
UPDATE_META : (meta) => { UPDATE_META : (meta) => {
State.brew = _.merge({}, State.brew, meta); State.brew = _.merge({}, State.brew, meta);
@@ -50,11 +55,14 @@ Store.init = (state)=>{
Store.getBrew = ()=>{ Store.getBrew = ()=>{
return State.brew; return State.brew;
}; };
Store.getBrewText = ()=>{ Store.getBrewCode = ()=>{
return State.brew.text; return State.brew.text;
}; };
Store.getBrewStyle = ()=>{
return State.brew.style;
};
Store.getMetaData = ()=>{ Store.getMetaData = ()=>{
return _.omit(State.brew, ['text']); return _.omit(State.brew, ['text', 'style']);
}; };
Store.getErrors = ()=>{ Store.getErrors = ()=>{
return State.errors; return State.errors;

View File

@@ -6,35 +6,36 @@ const CodeEditor = require('naturalcrit/codeEditor/codeEditor.jsx');
const SnippetBar = require('./snippetbar/snippetbar.jsx'); const SnippetBar = require('./snippetbar/snippetbar.jsx');
const MetadataEditor = require('./metadataEditor/metadataEditor.jsx'); const MetadataEditor = require('./metadataEditor/metadataEditor.jsx');
const Menubar = require('./menubar/menubar.jsx');
const splice = function(str, index, inject){ const splice = function(str, index, inject){
return str.slice(0, index) + inject + str.slice(index); return str.slice(0, index) + inject + str.slice(index);
}; };
const SNIPPETBAR_HEIGHT = 25; const MENUBAR_HEIGHT = 25;
const BrewEditor = React.createClass({ const BrewEditor = React.createClass({
getDefaultProps: function() { getDefaultProps: function() {
return { return {
value : '', brew : {
onChange : ()=>{}, text : '',
style : '',
},
metadata : {}, onCodeChange : ()=>{},
onMetadataChange : ()=>{}, onStyleChange : ()=>{},
onMetaChange : ()=>{},
}; };
}, },
getInitialState: function() { getInitialState: function() {
return { return {
showMetadataEditor: false view : 'code', //'code', 'style', 'meta'
}; };
}, },
cursorPosition : {
line : 0,
ch : 0
},
componentDidMount: function() { componentDidMount: function() {
this.updateEditorSize(); this.updateEditorSize();
this.highlightPageLines(); //this.highlightPageLines();
window.addEventListener("resize", this.updateEditorSize); window.addEventListener("resize", this.updateEditorSize);
}, },
componentWillUnmount: function() { componentWillUnmount: function() {
@@ -42,17 +43,15 @@ const BrewEditor = React.createClass({
}, },
updateEditorSize : function() { updateEditorSize : function() {
if(this.refs.codeEditor){
let paneHeight = this.refs.main.parentNode.clientHeight; let paneHeight = this.refs.main.parentNode.clientHeight;
paneHeight -= SNIPPETBAR_HEIGHT + 1; paneHeight -= MENUBAR_HEIGHT + 1;
this.refs.codeEditor.codeMirror.setSize(null, paneHeight); this.refs.codeEditor.codeMirror.setSize(null, paneHeight);
}
}, },
handleTextChange : function(text){
this.props.onChange(text);
},
handleCursorActivty : function(curpos){
this.cursorPosition = curpos;
},
handleInject : function(injectText){ handleInject : function(injectText){
const lines = this.props.value.split('\n'); const lines = this.props.value.split('\n');
lines[this.cursorPosition.line] = splice(lines[this.cursorPosition.line], this.cursorPosition.ch, injectText); lines[this.cursorPosition.line] = splice(lines[this.cursorPosition.line], this.cursorPosition.ch, injectText);
@@ -60,23 +59,32 @@ const BrewEditor = React.createClass({
this.handleTextChange(lines.join('\n')); this.handleTextChange(lines.join('\n'));
this.refs.codeEditor.setCursorPosition(this.cursorPosition.line, this.cursorPosition.ch + injectText.length); this.refs.codeEditor.setCursorPosition(this.cursorPosition.line, this.cursorPosition.ch + injectText.length);
}, },
handgleToggle : function(){
handleViewChange : function(newView){
this.setState({ this.setState({
showMetadataEditor : !this.state.showMetadataEditor view : newView
}) }, this.updateEditorSize);
}, },
brewJump : function(){ brewJump : function(){
const currentPage = this.getCurrentPage(); const currentPage = this.getCurrentPage();
window.location.hash = 'p' + currentPage; window.location.hash = 'p' + currentPage;
}, },
//Called when there are changes to the editor's dimensions //Called when there are changes to the editor's dimensions
/*
update : function(){ update : function(){
this.refs.codeEditor.updateSize(); if(this.refs.codeEditor) this.refs.codeEditor.updateSize();
}, },
*/
//TODO: convert this into a generic function for columns and blocks //TODO: convert this into a generic function for columns and blocks
//MOve this to a util.sj file
highlightPageLines : function(){ highlightPageLines : function(){
if(!this.refs.codeEditor) return; if(!this.refs.codeEditor) return;
const codeMirror = this.refs.codeEditor.codeMirror; const codeMirror = this.refs.codeEditor.codeMirror;
@@ -91,6 +99,7 @@ const BrewEditor = React.createClass({
return lineNumbers return lineNumbers
}, },
/*
renderMetadataEditor : function(){ renderMetadataEditor : function(){
if(!this.state.showMetadataEditor) return; if(!this.state.showMetadataEditor) return;
return <MetadataEditor return <MetadataEditor
@@ -98,25 +107,49 @@ const BrewEditor = React.createClass({
onChange={this.props.onMetadataChange} onChange={this.props.onMetadataChange}
/> />
}, },
*/
renderEditor : function(){
if(this.state.view == 'meta'){
return <MetadataEditor
metadata={this.props.brew}
onChange={this.props.onMetaChange} />
}
if(this.state.view == 'style'){
return <CodeEditor key='style'
ref='codeEditor'
language='css'
value={this.props.brew.style}
onChange={this.props.onStyleChange} />
}
if(this.state.view == 'code'){
return <CodeEditor key='code'
ref='codeEditor'
language='gfm'
value={this.props.brew.text}
onChange={this.props.onCodeChange} />
}
},
render : function(){ render : function(){
this.highlightPageLines();
return <div className='brewEditor' ref='main'> return <div className='brewEditor' ref='main'>
{/*
<SnippetBar <SnippetBar
brew={this.props.value} brew={this.props.value}
onInject={this.handleInject} onInject={this.handleInject}
onToggle={this.handgleToggle} onToggle={this.handgleToggle}
showmeta={this.state.showMetadataEditor} /> showmeta={this.state.showMetadataEditor} />
{this.renderMetadataEditor()} */}
<CodeEditor <Menubar
ref='codeEditor' view={this.state.view}
wrap={true} onViewChange={this.handleViewChange}
language='gfm'
value={this.props.value} />
onChange={this.handleTextChange}
onCursorActivity={this.handleCursorActivty} /> {this.renderEditor()}
</div> </div>
/* /*

View File

@@ -5,9 +5,10 @@ const BrewEditor = require('./brewEditor.jsx')
module.exports = Store.createSmartComponent(BrewEditor, ()=>{ module.exports = Store.createSmartComponent(BrewEditor, ()=>{
return { return {
value : Store.getBrewText(), brew : Store.getBrew(),
onChange : Actions.updateBrewText,
metadata : Store.getMetaData(), onCodeChange : Actions.updateBrewCode,
onMetadataChange : Actions.updateMetaData, onStyleChange : Actions.updateBrewStyle,
onMetaChange : Actions.updateMetadata,
}; };
}); });

View File

@@ -0,0 +1,35 @@
const React = require('react');
const _ = require('lodash');
const cx = require('classnames');
const Menubar = React.createClass({
getDefaultProps: function() {
return {
view : '',
onViewChange : ()=>{},
onSnippetInject : ()=>{},
};
},
render: function(){
return <div className='menubar'>
<div className='editors'>
<div className={cx('code', {selected : this.props.view == 'code'})}
onClick={this.props.onViewChange.bind(null, 'code')}>
<i className='fa fa-beer' />
</div>
<div className={cx('style', {selected : this.props.view == 'style'})}
onClick={this.props.onViewChange.bind(null, 'style')}>
<i className='fa fa-paint-brush' />
</div>
<div className={cx('meta', {selected : this.props.view == 'meta'})}
onClick={this.props.onViewChange.bind(null, 'meta')}>
<i className='fa fa-bars' />
</div>
</div>
</div>
}
});
module.exports = Menubar;

View File

@@ -0,0 +1,35 @@
.menubar{
@menuHeight : 25px;
position : relative;
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;
}
&.code{
.tooltipLeft('Brew Editor');
}
&.style{
.tooltipLeft('Style Editor');
}
&.meta{
.tooltipLeft('Metadata');
}
}
}
}

View File

@@ -1,11 +1,11 @@
.metadataEditor{ .metadataEditor{
position : absolute; position : absolute;
z-index : 10000;
box-sizing : border-box; box-sizing : border-box;
width : 100%; width : 100%;
padding : 25px; padding : 25px;
background-color : #999; // background-color : #999;
background-color: white;
.field{ .field{
display : flex; display : flex;
width : 100%; width : 100%;

View File

@@ -15,12 +15,13 @@ const PPR_THRESHOLD = 50;
const BrewRenderer = React.createClass({ const BrewRenderer = React.createClass({
getDefaultProps: function() { getDefaultProps: function() {
return { return {
brewText : '', value : '',
style : '',
errors : [] errors : []
}; };
}, },
getInitialState: function() { getInitialState: function() {
const pages = this.props.brewText.split('\\page'); const pages = this.props.value.split('\\page');
return { return {
viewablePageNumber: 0, viewablePageNumber: 0,
@@ -45,7 +46,7 @@ const BrewRenderer = React.createClass({
componentWillReceiveProps: function(nextProps) { componentWillReceiveProps: function(nextProps) {
if(this.refs.pages && this.refs.pages.firstChild) this.pageHeight = this.refs.pages.firstChild.clientHeight; if(this.refs.pages && this.refs.pages.firstChild) this.pageHeight = this.refs.pages.firstChild.clientHeight;
const pages = nextProps.brewText.split('\\page'); const pages = nextProps.value.split('\\page');
this.setState({ this.setState({
pages : pages, pages : pages,
usePPR : pages.length >= PPR_THRESHOLD usePPR : pages.length >= PPR_THRESHOLD
@@ -124,6 +125,10 @@ const BrewRenderer = React.createClass({
return this.lastRender; return this.lastRender;
}, },
renderStyle : function(){
},
render : function(){ render : function(){
return <div className='brewRenderer' return <div className='brewRenderer'
onScroll={this.handleScroll} onScroll={this.handleScroll}
@@ -133,6 +138,8 @@ const BrewRenderer = React.createClass({
<ErrorBar errors={this.props.errors} /> <ErrorBar errors={this.props.errors} />
<RenderWarnings /> <RenderWarnings />
<style>{this.props.style}</style>
<div className='pages' ref='pages'> <div className='pages' ref='pages'>
{this.renderPages()} {this.renderPages()}
</div> </div>

View File

@@ -4,7 +4,8 @@ const BrewRenderer = require('./brewRenderer.jsx');
module.exports = Store.createSmartComponent(BrewRenderer, () => { module.exports = Store.createSmartComponent(BrewRenderer, () => {
return { return {
brewText : Store.getBrewText(), value : Store.getBrewCode(),
style : Store.getBrewStyle(),
errors : Store.getErrors() errors : Store.getErrors()
} }
}); });

View File

@@ -1,70 +1,70 @@
var React = require('react'); const React = require('react');
var _ = require('lodash'); const _ = require('lodash');
var cx = require('classnames'); const cx = require('classnames');
let CodeMirror;
var CodeMirror;
if(typeof navigator !== 'undefined'){ if(typeof navigator !== 'undefined'){
var CodeMirror = require('codemirror'); CodeMirror = require('codemirror');
//Language Modes //Language Modes
require('codemirror/mode/gfm/gfm.js'); //Github flavoured markdown require('codemirror/mode/gfm/gfm.js'); //Github flavoured markdown
require('codemirror/mode/javascript/javascript.js'); require('codemirror/mode/javascript/javascript.js');
require('codemirror/mode/css/css.js');
} }
var CodeEditor = React.createClass({ const CodeEditor = React.createClass({
getDefaultProps: function() { getDefaultProps: function() {
return { return {
language : '',
value : '', value : '',
wrap : false,
onChange : function(){}, language : '',
onCursorActivity : function(){}, wrap : true,
onChange : ()=>{},
}; };
}, },
componentWillReceiveProps: function(nextProps){
if(this.props.language !== nextProps.language){
this.buildEditor();
}
if(this.codeMirror && nextProps.value !== undefined && this.codeMirror.getValue() != nextProps.value) {
this.codeMirror.setValue(nextProps.value);
}
},
shouldComponentUpdate: function(nextProps, nextState) {
return false;
},
componentDidMount: function() { componentDidMount: function() {
this.buildEditor();
},
buildEditor : function(){
this.codeMirror = CodeMirror(this.refs.editor,{ this.codeMirror = CodeMirror(this.refs.editor,{
value : this.props.value, value : this.props.value,
lineNumbers : true, lineNumbers : true,
lineWrapping : this.props.wrap, lineWrapping : this.props.wrap,
mode : this.props.language, mode : this.props.language,
indentWithTabs : true,
tabSize : 2
});
this.codeMirror.on('change', ()=>{
this.props.onChange(this.codeMirror.getValue());
}); });
this.codeMirror.on('change', this.handleChange);
this.codeMirror.on('cursorActivity', this.handleCursorActivity);
this.updateSize(); this.updateSize();
}, },
componentWillReceiveProps: function(nextProps){ //Externally Used
if(this.codeMirror && nextProps.value !== undefined && this.codeMirror.getValue() != nextProps.value) {
this.codeMirror.setValue(nextProps.value);
}
},
shouldComponentUpdate: function(nextProps, nextState) {
return false;
},
setCursorPosition : function(line, char){ setCursorPosition : function(line, char){
setTimeout(()=>{ setTimeout(()=>{
this.codeMirror.focus(); this.codeMirror.focus();
this.codeMirror.doc.setCursor(line, char); this.codeMirror.doc.setCursor(line, char);
}, 10); }, 10);
}, },
getCursorPosition : function(){
return this.codeMirror.getCursor();
},
updateSize : function(){ updateSize : function(){
this.codeMirror.refresh(); this.codeMirror.refresh();
}, },
handleChange : function(editor){
this.props.onChange(editor.getValue());
},
handleCursorActivity : function(){
this.props.onCursorActivity(this.codeMirror.doc.getCursor());
},
render : function(){ render : function(){
return <div className='codeEditor' ref='editor' /> return <div className='codeEditor' ref='editor' />
} }