1
0
mirror of https://github.com/stolksdorf/homebrewery.git synced 2025-12-12 23:35:58 +00:00
This commit is contained in:
Scott Tolksdorf
2018-04-08 22:23:03 -04:00
parent bf27250990
commit ed1b5252be
51 changed files with 2837 additions and 2852 deletions

View File

@@ -1,39 +1,38 @@
var React = require('react'); const React = require('react');
var _ = require('lodash'); const _ = require('lodash');
var cx = require('classnames'); const cx = require('classnames');
var HomebrewAdmin = require('./homebrewAdmin/homebrewAdmin.jsx'); const HomebrewAdmin = require('./homebrewAdmin/homebrewAdmin.jsx');
var Admin = React.createClass({ const Admin = React.createClass({
getDefaultProps: function() { getDefaultProps : function() {
return { return {
url : "", url : '',
admin_key : "", admin_key : '',
homebrews : [], homebrews : [],
}; };
}, },
render : function(){ render : function(){
var self = this; return (
return( <div className='admin'>
<div className='admin'>
<header>
<header> <div className='container'>
<div className='container'> <i className='fa fa-rocket' />
<i className='fa fa-rocket' /> naturalcrit admin
naturalcrit admin </div>
</div> </header>
</header>
<div className='container'>
<div className='container'>
<HomebrewAdmin homebrews={this.props.homebrews} admin_key={this.props.admin_key} />
<HomebrewAdmin homebrews={this.props.homebrews} admin_key={this.props.admin_key} /> </div>
</div>
</div>
</div> );
); }
} });
});
module.exports = Admin;
module.exports = Admin;

View File

@@ -7,35 +7,35 @@ const Moment = require('moment');
const BrewLookup = React.createClass({ const BrewLookup = React.createClass({
getDefaultProps: function() { getDefaultProps : function() {
return { return {
adminKey : '', adminKey : '',
}; };
}, },
getInitialState: function() { getInitialState : function() {
return { return {
query:'', query : '',
resultBrew : null, resultBrew : null,
searching : false searching : false
}; };
}, },
handleChange : function(e){ handleChange : function(e){
this.setState({ this.setState({
query : e.target.value query : e.target.value
}) });
}, },
lookup : function(){ lookup : function(){
this.setState({ searching : true }); this.setState({ searching: true });
request.get(`/admin/lookup/${this.state.query}`) request.get(`/admin/lookup/${this.state.query}`)
.query({ admin_key : this.props.adminKey }) .query({ admin_key: this.props.adminKey })
.end((err, res) => { .end((err, res)=>{
this.setState({ this.setState({
searching : false, searching : false,
resultBrew : (err ? null : res.body) resultBrew : (err ? null : res.body)
}); });
}) });
}, },
renderFoundBrew : function(){ renderFoundBrew : function(){
@@ -46,21 +46,21 @@ const BrewLookup = React.createClass({
return <div className='brewRow'> return <div className='brewRow'>
<div>{brew.title}</div> <div>{brew.title}</div>
<div>{brew.authors.join(', ')}</div> <div>{brew.authors.join(', ')}</div>
<div><a href={'/edit/' + brew.editId} target='_blank'>/edit/{brew.editId}</a></div> <div><a href={`/edit/${brew.editId}`} target='_blank'>/edit/{brew.editId}</a></div>
<div><a href={'/share/' + brew.shareId} target='_blank'>/share/{brew.shareId}</a></div> <div><a href={`/share/${brew.shareId}`} target='_blank'>/share/{brew.shareId}</a></div>
<div>{Moment(brew.updatedAt).fromNow()}</div> <div>{Moment(brew.updatedAt).fromNow()}</div>
<div>{brew.views}</div> <div>{brew.views}</div>
</div> </div>;
}, },
render: function(){ render : function(){
return <div className='brewLookup'> return <div className='brewLookup'>
<h1>Brew Lookup</h1> <h1>Brew Lookup</h1>
<input type='text' value={this.state.query} onChange={this.handleChange} placeholder='edit or share id...' /> <input type='text' value={this.state.query} onChange={this.handleChange} placeholder='edit or share id...' />
<button onClick={this.lookup}><i className='fa fa-search'/></button> <button onClick={this.lookup}><i className='fa fa-search'/></button>
{this.renderFoundBrew()} {this.renderFoundBrew()}
</div> </div>;
} }
}); });

View File

@@ -1,72 +1,72 @@
var React = require('react'); const React = require('react');
var _ = require('lodash'); const _ = require('lodash');
var cx = require('classnames'); const cx = require('classnames');
var request = require('superagent'); const request = require('superagent');
var BrewSearch = React.createClass({ const BrewSearch = React.createClass({
getDefaultProps: function() { getDefaultProps : function() {
return { return {
admin_key : '' admin_key : ''
}; };
}, },
getInitialState: function() { getInitialState : function() {
return { return {
searchTerm: '', searchTerm : '',
brew : null, brew : null,
searching : false searching : false
}; };
}, },
search : function(){ search : function(){
this.setState({ this.setState({
searching : true searching : true
}); });
request.get('/homebrew/api/search?id=' + this.state.searchTerm) request.get(`/homebrew/api/search?id=${this.state.searchTerm}`)
.query({ .query({
admin_key : this.props.admin_key, admin_key : this.props.admin_key,
}) })
.end((err, res)=>{ .end((err, res)=>{
console.log(err, res, res.body.brews[0]); console.log(err, res, res.body.brews[0]);
this.setState({ this.setState({
brew : res.body.brews[0], brew : res.body.brews[0],
searching : false searching : false
}) });
}); });
}, },
handleChange : function(e){ handleChange : function(e){
this.setState({ this.setState({
searchTerm : e.target.value searchTerm : e.target.value
}); });
}, },
handleSearchClick : function(){ handleSearchClick : function(){
this.search(); this.search();
}, },
renderBrew : function(){ renderBrew : function(){
if(!this.state.brew) return null; if(!this.state.brew) return null;
return <div className='brew'> return <div className='brew'>
<div>Edit id : {this.state.brew.editId}</div> <div>Edit id : {this.state.brew.editId}</div>
<div>Share id : {this.state.brew.shareId}</div> <div>Share id : {this.state.brew.shareId}</div>
</div> </div>;
}, },
render : function(){ render : function(){
return <div className='search'> return <div className='search'>
<input type='text' value={this.state.searchTerm} onChange={this.handleChange} /> <input type='text' value={this.state.searchTerm} onChange={this.handleChange} />
<button onClick={this.handleSearchClick}>Search</button> <button onClick={this.handleSearchClick}>Search</button>
{this.renderBrew()} {this.renderBrew()}
</div> </div>;
}, },
}); });
module.exports = BrewSearch; module.exports = BrewSearch;

View File

@@ -1,172 +1,170 @@
var React = require('react'); const React = require('react');
var _ = require('lodash'); const _ = require('lodash');
var cx = require('classnames'); const cx = require('classnames');
var request = require('superagent'); const request = require('superagent');
var Moment = require('moment'); const Moment = require('moment');
var BrewSearch = require('./brewSearch.jsx');
const BrewLookup = require('./brewLookup/brewLookup.jsx');
var BrewLookup = require('./brewLookup/brewLookup.jsx');
const HomebrewAdmin = React.createClass({
var HomebrewAdmin = React.createClass({ getDefaultProps : function() {
getDefaultProps: function() { return {
return { admin_key : ''
admin_key : '' };
}; },
},
getInitialState : function() {
getInitialState: function() { return {
return { page : 0,
page: 0, count : 20,
count : 20, brewCache : {},
brewCache : {}, total : 0,
total : 0,
processingOldBrews : false
processingOldBrews : false };
}; },
},
fetchBrews : function(page){
fetchBrews : function(page){ request.get('/api/search')
request.get('/api/search') .query({
.query({ admin_key : this.props.admin_key,
admin_key : this.props.admin_key, count : this.state.count,
count : this.state.count, page : page
page : page })
}) .end((err, res)=>{
.end((err, res)=>{ if(err || !res.body || !res.body.brews) return;
if(err || !res.body || !res.body.brews) return; this.state.brewCache[page] = res.body.brews;
this.state.brewCache[page] = res.body.brews; this.setState({
this.setState({ brewCache : this.state.brewCache,
brewCache : this.state.brewCache, total : res.body.total,
total : res.body.total, count : res.body.count
count : res.body.count });
}) });
}) },
},
componentDidMount : function() {
componentDidMount: function() { this.fetchBrews(this.state.page);
this.fetchBrews(this.state.page); },
},
changePageTo : function(page){
changePageTo : function(page){ if(!this.state.brewCache[page]){
if(!this.state.brewCache[page]){ this.fetchBrews(page);
this.fetchBrews(page); }
} this.setState({
this.setState({ page : page
page : page });
}) },
},
clearInvalidBrews : function(){
clearInvalidBrews : function(){ request.get('/api/invalid')
request.get('/api/invalid') .query({ admin_key: this.props.admin_key })
.query({admin_key : this.props.admin_key}) .end((err, res)=>{
.end((err, res)=>{ if(!confirm(`This will remove ${res.body.count} brews. Are you sure?`)) return;
if(!confirm("This will remove " + res.body.count + " brews. Are you sure?")) return; request.get('/api/invalid')
request.get('/api/invalid') .query({ admin_key: this.props.admin_key, do_it: true })
.query({admin_key : this.props.admin_key, do_it : true}) .end((err, res)=>{
.end((err, res)=>{ alert('Done!');
alert("Done!") });
}); });
}); },
},
deleteBrew : function(brewId){
deleteBrew : function(brewId){ if(!confirm(`Are you sure you want to delete '${brewId}'?`)) return;
if(!confirm("Are you sure you want to delete '" + brewId + "'?")) return; request.get(`/api/remove/${brewId}`)
request.get('/api/remove/' + brewId) .query({ admin_key: this.props.admin_key })
.query({admin_key : this.props.admin_key}) .end(function(err, res){
.end(function(err, res){ window.location.reload();
window.location.reload(); });
}) },
},
handlePageChange : function(dir){
handlePageChange : function(dir){ this.changePageTo(this.state.page + dir);
this.changePageTo(this.state.page + dir); },
},
renderPagnination : function(){
renderPagnination : function(){ let outOf;
var outOf; if(this.state.total){
if(this.state.total){ outOf = `${this.state.page} / ${Math.round(this.state.total/this.state.count)}`;
outOf = this.state.page + ' / ' + Math.round(this.state.total/this.state.count); }
} return <div className='pagnination'>
return <div className='pagnination'> <i className='fa fa-chevron-left' onClick={()=>this.handlePageChange(-1)}/>
<i className='fa fa-chevron-left' onClick={this.handlePageChange.bind(this, -1)}/> {outOf}
{outOf} <i className='fa fa-chevron-right' onClick={()=>this.handlePageChange(1)}/>
<i className='fa fa-chevron-right' onClick={this.handlePageChange.bind(this, 1)}/> </div>;
</div> },
},
renderBrews : function(){
renderBrews : function(){ const brews = this.state.brewCache[this.state.page] || _.times(this.state.count);
var brews = this.state.brewCache[this.state.page] || _.times(this.state.count); return _.map(brews, (brew)=>{
return _.map(brews, (brew)=>{ return <tr className={cx('brewRow', { 'isEmpty': brew.text == 'false' })} key={brew.shareId || brew}>
return <tr className={cx('brewRow', {'isEmpty' : brew.text == "false"})} key={brew.shareId || brew}> <td><a href={`/edit/${brew.editId}`} target='_blank'>{brew.editId}</a></td>
<td><a href={'/edit/' + brew.editId} target='_blank'>{brew.editId}</a></td> <td><a href={`/share/${brew.shareId}`} target='_blank'>{brew.shareId}</a></td>
<td><a href={'/share/' + brew.shareId} target='_blank'>{brew.shareId}</a></td> <td>{Moment(brew.createdAt).fromNow()}</td>
<td>{Moment(brew.createdAt).fromNow()}</td> <td>{Moment(brew.updatedAt).fromNow()}</td>
<td>{Moment(brew.updatedAt).fromNow()}</td> <td>{Moment(brew.lastViewed).fromNow()}</td>
<td>{Moment(brew.lastViewed).fromNow()}</td> <td>{brew.views}</td>
<td>{brew.views}</td> <td>
<td> <div className='deleteButton' onClick={()=>this.deleteBrew(brew.editId)}>
<div className='deleteButton' onClick={this.deleteBrew.bind(this, brew.editId)}> <i className='fa fa-trash' />
<i className='fa fa-trash' /> </div>
</div> </td>
</td> </tr>;
</tr> });
}); },
},
renderBrewTable : function(){
renderBrewTable : function(){ return <div className='brewTable'>
return <div className='brewTable'> <table>
<table> <thead>
<thead> <tr>
<tr> <th>Edit Id</th>
<th>Edit Id</th> <th>Share Id</th>
<th>Share Id</th> <th>Created At</th>
<th>Created At</th> <th>Last Updated</th>
<th>Last Updated</th> <th>Last Viewed</th>
<th>Last Viewed</th> <th>Views</th>
<th>Views</th> </tr>
</tr> </thead>
</thead> <tbody>
<tbody> {this.renderBrews()}
{this.renderBrews()} </tbody>
</tbody> </table>
</table> </div>;
</div> },
},
render : function(){
render : function(){ return <div className='homebrewAdmin'>
var self = this;
return <div className='homebrewAdmin'> <BrewLookup adminKey={this.props.admin_key} />
<BrewLookup adminKey={this.props.admin_key} /> {/*
<h2>
{/* Homebrews - {this.state.total}
<h2> </h2>
Homebrews - {this.state.total}
</h2>
{this.renderPagnination()}
{this.renderBrewTable()}
{this.renderPagnination()}
{this.renderBrewTable()} <button className='clearOldButton' onClick={this.clearInvalidBrews}>
Clear Old
<button className='clearOldButton' onClick={this.clearInvalidBrews}> </button>
Clear Old
</button> <BrewSearch admin_key={this.props.admin_key} />
*/}
<BrewSearch admin_key={this.props.admin_key} /> </div>;
*/} }
</div> });
}
}); module.exports = HomebrewAdmin;
module.exports = HomebrewAdmin;

View File

@@ -6,54 +6,54 @@ const Markdown = require('naturalcrit/markdown.js');
const ErrorBar = require('./errorBar/errorBar.jsx'); const ErrorBar = require('./errorBar/errorBar.jsx');
//TODO: move to the brew renderer //TODO: move to the brew renderer
const RenderWarnings = require('homebrewery/renderWarnings/renderWarnings.jsx') const RenderWarnings = require('homebrewery/renderWarnings/renderWarnings.jsx');
const PAGE_HEIGHT = 1056; const PAGE_HEIGHT = 1056;
const PPR_THRESHOLD = 50; const PPR_THRESHOLD = 50;
const BrewRenderer = React.createClass({ const BrewRenderer = React.createClass({
getDefaultProps: function() { getDefaultProps : function() {
return { return {
text : '', text : '',
errors : [] errors : []
}; };
}, },
getInitialState: function() { getInitialState : function() {
const pages = this.props.text.split('\\page'); const pages = this.props.text.split('\\page');
return { return {
viewablePageNumber: 0, viewablePageNumber : 0,
height : 0, height : 0,
isMounted : false, isMounted : false,
usePPR : true, usePPR : true,
pages : pages, pages : pages,
usePPR : pages.length >= PPR_THRESHOLD, usePPR : pages.length >= PPR_THRESHOLD,
errors : [] errors : []
}; };
}, },
height : 0, height : 0,
pageHeight : PAGE_HEIGHT, pageHeight : PAGE_HEIGHT,
lastRender : <div></div>, lastRender : <div></div>,
componentDidMount: function() { componentDidMount : function() {
this.updateSize(); this.updateSize();
window.addEventListener("resize", this.updateSize); window.addEventListener('resize', this.updateSize);
}, },
componentWillUnmount: function() { componentWillUnmount : function() {
window.removeEventListener("resize", this.updateSize); window.removeEventListener('resize', this.updateSize);
}, },
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.text.split('\\page'); const pages = nextProps.text.split('\\page');
this.setState({ this.setState({
pages : pages, pages : pages,
usePPR : pages.length >= PPR_THRESHOLD usePPR : pages.length >= PPR_THRESHOLD
}) });
}, },
updateSize : function() { updateSize : function() {
@@ -62,7 +62,7 @@ const BrewRenderer = React.createClass({
}, 1); }, 1);
this.setState({ this.setState({
height : this.refs.main.parentNode.clientHeight, height : this.refs.main.parentNode.clientHeight,
isMounted : true isMounted : true
}); });
}, },
@@ -76,7 +76,7 @@ const BrewRenderer = React.createClass({
shouldRender : function(pageText, index){ shouldRender : function(pageText, index){
if(!this.state.isMounted) return false; if(!this.state.isMounted) return false;
var viewIndex = this.state.viewablePageNumber; const viewIndex = this.state.viewablePageNumber;
if(index == viewIndex - 3) return true; if(index == viewIndex - 3) return true;
if(index == viewIndex - 2) return true; if(index == viewIndex - 2) return true;
if(index == viewIndex - 1) return true; if(index == viewIndex - 1) return true;
@@ -94,7 +94,7 @@ const BrewRenderer = React.createClass({
renderPageInfo : function(){ renderPageInfo : function(){
return <div className='pageInfo'> return <div className='pageInfo'>
{this.state.viewablePageNumber + 1} / {this.state.pages.length} {this.state.viewablePageNumber + 1} / {this.state.pages.length}
</div> </div>;
}, },
renderPPRmsg : function(){ renderPPRmsg : function(){
@@ -102,17 +102,17 @@ const BrewRenderer = React.createClass({
return <div className='ppr_msg'> return <div className='ppr_msg'>
Partial Page Renderer enabled, because your brew is so large. May effect rendering. Partial Page Renderer enabled, because your brew is so large. May effect rendering.
</div> </div>;
}, },
renderDummyPage : function(index){ renderDummyPage : function(index){
return <div className='phb' id={`p${index + 1}`} key={index}> return <div className='phb' id={`p${index + 1}`} key={index}>
<i className='fa fa-spinner fa-spin' /> <i className='fa fa-spinner fa-spin' />
</div> </div>;
}, },
renderPage : function(pageText, index){ renderPage : function(pageText, index){
return <div className='phb' id={`p${index + 1}`} dangerouslySetInnerHTML={{__html:Markdown.render(pageText)}} key={index} /> return <div className='phb' id={`p${index + 1}`} dangerouslySetInnerHTML={{ __html: Markdown.render(pageText) }} key={index} />;
}, },
renderPages : function(){ renderPages : function(){
@@ -120,7 +120,7 @@ const BrewRenderer = React.createClass({
return _.map(this.state.pages, (page, index)=>{ return _.map(this.state.pages, (page, index)=>{
if(this.shouldRender(page, index)){ if(this.shouldRender(page, index)){
return this.renderPage(page, index); return this.renderPage(page, index);
}else{ } else {
return this.renderDummyPage(index); return this.renderDummyPage(index);
} }
}); });
@@ -136,7 +136,7 @@ const BrewRenderer = React.createClass({
return <div className='brewRenderer' return <div className='brewRenderer'
onScroll={this.handleScroll} onScroll={this.handleScroll}
ref='main' ref='main'
style={{height : this.state.height}}> style={{ height: this.state.height }}>
<ErrorBar errors={this.props.errors} /> <ErrorBar errors={this.props.errors} />
<RenderWarnings /> <RenderWarnings />
@@ -146,7 +146,7 @@ const BrewRenderer = React.createClass({
</div> </div>
{this.renderPageInfo()} {this.renderPageInfo()}
{this.renderPPRmsg()} {this.renderPPRmsg()}
</div> </div>;
} }
}); });

View File

@@ -1,15 +1,15 @@
var React = require('react'); const React = require('react');
var _ = require('lodash'); const _ = require('lodash');
var cx = require('classnames'); const cx = require('classnames');
var ErrorBar = React.createClass({ const ErrorBar = React.createClass({
getDefaultProps: function() { getDefaultProps : function() {
return { return {
errors : [] errors : []
}; };
}, },
hasOpenError : false, hasOpenError : false,
hasCloseError : false, hasCloseError : false,
hasMatchError : false, hasMatchError : false,
@@ -19,20 +19,20 @@ var ErrorBar = React.createClass({
this.hasMatchError = false; this.hasMatchError = false;
var errors = _.map(this.props.errors, (err, idx) => { const errors = _.map(this.props.errors, (err, idx)=>{
if(err.id == 'OPEN') this.hasOpenError = true; if(err.id == 'OPEN') this.hasOpenError = true;
if(err.id == 'CLOSE') this.hasCloseError = true; if(err.id == 'CLOSE') this.hasCloseError = true;
if(err.id == 'MISMATCH') this.hasMatchError = true; if(err.id == 'MISMATCH') this.hasMatchError = true;
return <li key={idx}> return <li key={idx}>
Line {err.line} : {err.text}, '{err.type}' tag Line {err.line} : {err.text}, '{err.type}' tag
</li> </li>;
}); });
return <ul>{errors}</ul> return <ul>{errors}</ul>;
}, },
renderProtip : function(){ renderProtip : function(){
var msg = []; const msg = [];
if(this.hasOpenError){ if(this.hasOpenError){
msg.push(<div> msg.push(<div>
An unmatched opening tag means there's an opened tag that isn't closed, you need to close a tag, like this {'</div>'}. Make sure to match types! An unmatched opening tag means there's an opened tag that isn't closed, you need to close a tag, like this {'</div>'}. Make sure to match types!
@@ -53,7 +53,7 @@ var ErrorBar = React.createClass({
return <div className='protips'> return <div className='protips'>
<h4>Protips!</h4> <h4>Protips!</h4>
{msg} {msg}
</div> </div>;
}, },
render : function(){ render : function(){
@@ -66,7 +66,7 @@ var ErrorBar = React.createClass({
{this.renderErrors()} {this.renderErrors()}
<hr /> <hr />
{this.renderProtip()} {this.renderProtip()}
</div> </div>;
} }
}); });

View File

@@ -1,145 +1,145 @@
const React = require('react'); const React = require('react');
const _ = require('lodash'); const _ = require('lodash');
const cx = require('classnames'); const cx = require('classnames');
const CodeEditor = require('naturalcrit/codeEditor/codeEditor.jsx'); 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 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 SNIPPETBAR_HEIGHT = 25;
const Editor = React.createClass({ const Editor = React.createClass({
getDefaultProps: function() { getDefaultProps : function() {
return { return {
value : '', value : '',
onChange : ()=>{}, onChange : ()=>{},
metadata : {}, metadata : {},
onMetadataChange : ()=>{}, onMetadataChange : ()=>{},
}; };
}, },
getInitialState: function() { getInitialState : function() {
return { return {
showMetadataEditor: false showMetadataEditor : false
}; };
}, },
cursorPosition : { cursorPosition : {
line : 0, line : 0,
ch : 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() {
window.removeEventListener("resize", this.updateEditorSize); window.removeEventListener('resize', this.updateEditorSize);
}, },
updateEditorSize : function() { updateEditorSize : function() {
let paneHeight = this.refs.main.parentNode.clientHeight; let paneHeight = this.refs.main.parentNode.clientHeight;
paneHeight -= SNIPPETBAR_HEIGHT + 1; paneHeight -= SNIPPETBAR_HEIGHT + 1;
this.refs.codeEditor.codeMirror.setSize(null, paneHeight); this.refs.codeEditor.codeMirror.setSize(null, paneHeight);
}, },
handleTextChange : function(text){ handleTextChange : function(text){
this.props.onChange(text); this.props.onChange(text);
}, },
handleCursorActivty : function(curpos){ handleCursorActivty : function(curpos){
this.cursorPosition = 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);
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(){ handgleToggle : function(){
this.setState({ this.setState({
showMetadataEditor : !this.state.showMetadataEditor showMetadataEditor : !this.state.showMetadataEditor
}) });
}, },
getCurrentPage : function(){ getCurrentPage : function(){
const lines = this.props.value.split('\n').slice(0, this.cursorPosition.line + 1); const lines = this.props.value.split('\n').slice(0, this.cursorPosition.line + 1);
return _.reduce(lines, (r, line) => { return _.reduce(lines, (r, line)=>{
if(line.indexOf('\\page') !== -1) r++; if(line.indexOf('\\page') !== -1) r++;
return r; return r;
}, 1); }, 1);
}, },
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;
const lineNumbers = _.reduce(this.props.value.split('\n'), (r, line, lineNumber)=>{ const lineNumbers = _.reduce(this.props.value.split('\n'), (r, line, lineNumber)=>{
if(line.indexOf('\\page') !== -1){ if(line.indexOf('\\page') !== -1){
codeMirror.addLineClass(lineNumber, 'background', 'pageLine'); codeMirror.addLineClass(lineNumber, 'background', 'pageLine');
r.push(lineNumber); r.push(lineNumber);
} }
return r; return r;
}, []); }, []);
return lineNumbers return lineNumbers;
}, },
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(); this.refs.codeEditor.updateSize();
}, },
renderMetadataEditor : function(){ renderMetadataEditor : function(){
if(!this.state.showMetadataEditor) return; if(!this.state.showMetadataEditor) return;
return <MetadataEditor return <MetadataEditor
metadata={this.props.metadata} metadata={this.props.metadata}
onChange={this.props.onMetadataChange} onChange={this.props.onMetadataChange}
/> />;
}, },
render : function(){ render : function(){
this.highlightPageLines(); this.highlightPageLines();
return( return (
<div className='editor' ref='main'> <div className='editor' 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()} {this.renderMetadataEditor()}
<CodeEditor <CodeEditor
ref='codeEditor' ref='codeEditor'
wrap={true} wrap={true}
language='gfm' language='gfm'
value={this.props.value} value={this.props.value}
onChange={this.handleTextChange} onChange={this.handleTextChange}
onCursorActivity={this.handleCursorActivty} /> onCursorActivity={this.handleCursorActivty} />
{/* {/*
<div className='brewJump' onClick={this.brewJump}> <div className='brewJump' onClick={this.brewJump}>
<i className='fa fa-arrow-right' /> <i className='fa fa-arrow-right' />
</div> </div>
*/} */}
</div> </div>
); );
} }
}); });
module.exports = Editor; module.exports = Editor;

View File

@@ -1,21 +1,21 @@
const React = require('react'); const React = require('react');
const _ = require('lodash'); const _ = require('lodash');
const cx = require('classnames'); const cx = require('classnames');
const request = require("superagent"); const request = require('superagent');
const SYSTEMS = ['5e', '4e', '3.5e', 'Pathfinder'] const SYSTEMS = ['5e', '4e', '3.5e', 'Pathfinder'];
const MetadataEditor = React.createClass({ const MetadataEditor = React.createClass({
getDefaultProps: function() { getDefaultProps : function() {
return { return {
metadata: { metadata : {
editId : null, editId : null,
title : '', title : '',
description : '', description : '',
tags : '', tags : '',
published : false, published : false,
authors : [], authors : [],
systems : [] systems : []
}, },
onChange : ()=>{} onChange : ()=>{}
}; };
@@ -24,12 +24,12 @@ const MetadataEditor = React.createClass({
handleFieldChange : function(name, e){ handleFieldChange : function(name, e){
this.props.onChange(_.merge({}, this.props.metadata, { this.props.onChange(_.merge({}, this.props.metadata, {
[name] : e.target.value [name] : e.target.value
})) }));
}, },
handleSystem : function(system, e){ handleSystem : function(system, e){
if(e.target.checked){ if(e.target.checked){
this.props.metadata.systems.push(system); this.props.metadata.systems.push(system);
}else{ } else {
this.props.metadata.systems = _.without(this.props.metadata.systems, system); this.props.metadata.systems = _.without(this.props.metadata.systems, system);
} }
this.props.onChange(this.props.metadata); this.props.onChange(this.props.metadata);
@@ -41,10 +41,10 @@ const MetadataEditor = React.createClass({
}, },
handleDelete : function(){ handleDelete : function(){
if(!confirm("are you sure you want to delete this brew?")) return; if(!confirm('are you sure you want to delete this brew?')) return;
if(!confirm("are you REALLY sure? You will not be able to recover it")) return; if(!confirm('are you REALLY sure? You will not be able to recover it')) return;
request.get('/api/remove/' + this.props.metadata.editId) request.get(`/api/remove/${this.props.metadata.editId}`)
.send() .send()
.end(function(err, res){ .end(function(err, res){
window.location.href = '/'; window.location.href = '/';
@@ -67,21 +67,21 @@ const MetadataEditor = React.createClass({
<input <input
type='checkbox' type='checkbox'
checked={_.includes(this.props.metadata.systems, val)} checked={_.includes(this.props.metadata.systems, val)}
onChange={this.handleSystem.bind(null, val)} /> onChange={()=>this.handleSystem(val)} />
{val} {val}
</label> </label>;
}); });
}, },
renderPublish : function(){ renderPublish : function(){
if(this.props.metadata.published){ if(this.props.metadata.published){
return <button className='unpublish' onClick={this.handlePublish.bind(null, false)}> return <button className='unpublish' onClick={()=>this.handlePublish(false)}>
<i className='fa fa-ban' /> unpublish <i className='fa fa-ban' /> unpublish
</button> </button>;
}else{ } else {
return <button className='publish' onClick={this.handlePublish.bind(null, true)}> return <button className='publish' onClick={()=>this.handlePublish(true)}>
<i className='fa fa-globe' /> publish <i className='fa fa-globe' /> publish
</button> </button>;
} }
}, },
@@ -95,7 +95,7 @@ const MetadataEditor = React.createClass({
<i className='fa fa-trash' /> delete brew <i className='fa fa-trash' /> delete brew
</button> </button>
</div> </div>
</div> </div>;
}, },
renderAuthors : function(){ renderAuthors : function(){
@@ -108,7 +108,7 @@ const MetadataEditor = React.createClass({
<div className='value'> <div className='value'>
{text} {text}
</div> </div>
</div> </div>;
}, },
renderShareToReddit : function(){ renderShareToReddit : function(){
@@ -123,7 +123,7 @@ const MetadataEditor = React.createClass({
</button> </button>
</a> </a>
</div> </div>
</div> </div>;
}, },
render : function(){ render : function(){
@@ -132,18 +132,18 @@ const MetadataEditor = React.createClass({
<label>title</label> <label>title</label>
<input type='text' className='value' <input type='text' className='value'
value={this.props.metadata.title} value={this.props.metadata.title}
onChange={this.handleFieldChange.bind(null, 'title')} /> onChange={()=>this.handleFieldChange('title')} />
</div> </div>
<div className='field description'> <div className='field description'>
<label>description</label> <label>description</label>
<textarea value={this.props.metadata.description} className='value' <textarea value={this.props.metadata.description} className='value'
onChange={this.handleFieldChange.bind(null, 'description')} /> onChange={()=>this.handleFieldChange('description')} />
</div> </div>
{/*} {/*}
<div className='field tags'> <div className='field tags'>
<label>tags</label> <label>tags</label>
<textarea value={this.props.metadata.tags} <textarea value={this.props.metadata.tags}
onChange={this.handleFieldChange.bind(null, 'tags')} /> onChange={()=>this.handleFieldChange('tags')} />
</div> </div>
*/} */}
@@ -168,7 +168,7 @@ const MetadataEditor = React.createClass({
{this.renderDelete()} {this.renderDelete()}
</div> </div>;
} }
}); });

View File

@@ -8,14 +8,14 @@ const Snippets = require('./snippets/snippets.js');
const execute = function(val, brew){ const execute = function(val, brew){
if(_.isFunction(val)) return val(brew); if(_.isFunction(val)) return val(brew);
return val; return val;
} };
const Snippetbar = React.createClass({ const Snippetbar = React.createClass({
getDefaultProps: function() { getDefaultProps : function() {
return { return {
brew : '', brew : '',
onInject : ()=>{}, onInject : ()=>{},
onToggle : ()=>{}, onToggle : ()=>{},
showmeta : false showmeta : false
@@ -23,7 +23,7 @@ const Snippetbar = React.createClass({
}, },
handleSnippetClick : function(injectedText){ handleSnippetClick : function(injectedText){
this.props.onInject(injectedText) this.props.onInject(injectedText);
}, },
renderSnippetGroups : function(){ renderSnippetGroups : function(){
@@ -35,18 +35,18 @@ const Snippetbar = React.createClass({
snippets={snippetGroup.snippets} snippets={snippetGroup.snippets}
key={snippetGroup.groupName} key={snippetGroup.groupName}
onSnippetClick={this.handleSnippetClick} onSnippetClick={this.handleSnippetClick}
/> />;
}) });
}, },
render : function(){ render : function(){
return <div className='snippetBar'> return <div className='snippetBar'>
{this.renderSnippetGroups()} {this.renderSnippetGroups()}
<div className={cx('toggleMeta', {selected: this.props.showmeta})} <div className={cx('toggleMeta', { selected: this.props.showmeta })}
onClick={this.props.onToggle}> onClick={this.props.onToggle}>
<i className='fa fa-bars' /> <i className='fa fa-bars' />
</div> </div>
</div> </div>;
} }
}); });
@@ -58,12 +58,12 @@ module.exports = Snippetbar;
const SnippetGroup = React.createClass({ const SnippetGroup = React.createClass({
getDefaultProps: function() { getDefaultProps : function() {
return { return {
brew : '', brew : '',
groupName : '', groupName : '',
icon : 'fa-rocket', icon : 'fa-rocket',
snippets : [], snippets : [],
onSnippetClick : function(){}, onSnippetClick : function(){},
}; };
}, },
@@ -72,23 +72,23 @@ const SnippetGroup = React.createClass({
}, },
renderSnippets : function(){ renderSnippets : function(){
return _.map(this.props.snippets, (snippet)=>{ return _.map(this.props.snippets, (snippet)=>{
return <div className='snippet' key={snippet.name} onClick={this.handleSnippetClick.bind(this, snippet)}> return <div className='snippet' key={snippet.name} onClick={()=>this.handleSnippetClick(snippet)}>
<i className={'fa fa-fw ' + snippet.icon} /> <i className={`fa fa-fw ${snippet.icon}`} />
{snippet.name} {snippet.name}
</div> </div>;
}) });
}, },
render : function(){ render : function(){
return <div className='snippetGroup'> return <div className='snippetGroup'>
<div className='text'> <div className='text'>
<i className={'fa fa-fw ' + this.props.icon} /> <i className={`fa fa-fw ${this.props.icon}`} />
<span className='groupName'>{this.props.groupName}</span> <span className='groupName'>{this.props.groupName}</span>
</div> </div>
<div className='dropdown'> <div className='dropdown'>
{this.renderSnippets()} {this.renderSnippets()}
</div> </div>
</div> </div>;
}, },
}); });

View File

@@ -1,42 +1,42 @@
var _ = require('lodash'); const _ = require('lodash');
module.exports = function(classname){ module.exports = function(classname){
classname = _.sample(['archivist', 'fancyman', 'linguist', 'fletcher', classname = _.sample(['archivist', 'fancyman', 'linguist', 'fletcher',
'notary', 'berserker-typist', 'fishmongerer', 'manicurist', 'haberdasher', 'concierge']) 'notary', 'berserker-typist', 'fishmongerer', 'manicurist', 'haberdasher', 'concierge']);
classname = classname.toLowerCase(); classname = classname.toLowerCase();
var hitDie = _.sample([4, 6, 8, 10, 12]); const hitDie = _.sample([4, 6, 8, 10, 12]);
var abilityList = ["Strength", "Dexerity", "Constitution", "Wisdom", "Charisma", "Intelligence"]; const abilityList = ['Strength', 'Dexerity', 'Constitution', 'Wisdom', 'Charisma', 'Intelligence'];
var skillList = ["Acrobatics ", "Animal Handling", "Arcana", "Athletics", "Deception", "History", "Insight", "Intimidation", "Investigation", "Medicine", "Nature", "Perception", "Performance", "Persuasion", "Religion", "Sleight of Hand", "Stealth", "Survival"]; const skillList = ['Acrobatics ', 'Animal Handling', 'Arcana', 'Athletics', 'Deception', 'History', 'Insight', 'Intimidation', 'Investigation', 'Medicine', 'Nature', 'Perception', 'Performance', 'Persuasion', 'Religion', 'Sleight of Hand', 'Stealth', 'Survival'];
return [ return [
"## Class Features", '## Class Features',
"As a " + classname + ", you gain the following class features", `As a ${classname}, you gain the following class features`,
"#### Hit Points", '#### Hit Points',
"___", '___',
"- **Hit Dice:** 1d" + hitDie + " per " + classname + " level", `- **Hit Dice:** 1d${hitDie} per ${classname} level`,
"- **Hit Points at 1st Level:** " + hitDie + " + your Constitution modifier", `- **Hit Points at 1st Level:** ${hitDie} + your Constitution modifier`,
"- **Hit Points at Higher Levels:** 1d" + hitDie + " (or " + (hitDie/2 + 1) + ") + your Constitution modifier per " + classname + " level after 1st", `- **Hit Points at Higher Levels:** 1d${hitDie} (or ${hitDie/2 + 1}) + your Constitution modifier per ${classname} level after 1st`,
"", '',
"#### Proficiencies", '#### Proficiencies',
"___", '___',
"- **Armor:** " + (_.sampleSize(["Light armor", "Medium armor", "Heavy armor", "Shields"], _.random(0,3)).join(', ') || "None"), `- **Armor:** ${_.sampleSize(['Light armor', 'Medium armor', 'Heavy armor', 'Shields'], _.random(0, 3)).join(', ') || 'None'}`,
"- **Weapons:** " + (_.sampleSize(["Squeegee", "Rubber Chicken", "Simple weapons", "Martial weapons"], _.random(0,2)).join(', ') || "None"), `- **Weapons:** ${_.sampleSize(['Squeegee', 'Rubber Chicken', 'Simple weapons', 'Martial weapons'], _.random(0, 2)).join(', ') || 'None'}`,
"- **Tools:** " + (_.sampleSize(["Artian's tools", "one musical instrument", "Thieve's tools"], _.random(0,2)).join(', ') || "None"), `- **Tools:** ${_.sampleSize(['Artian\'s tools', 'one musical instrument', 'Thieve\'s tools'], _.random(0, 2)).join(', ') || 'None'}`,
"", '',
"___", '___',
"- **Saving Throws:** " + (_.sampleSize(abilityList, 2).join(', ')), `- **Saving Throws:** ${_.sampleSize(abilityList, 2).join(', ')}`,
"- **Skills:** Choose two from " + (_.sampleSize(skillList, _.random(4, 6)).join(', ')), `- **Skills:** Choose two from ${_.sampleSize(skillList, _.random(4, 6)).join(', ')}`,
"", '',
"#### Equipment", '#### Equipment',
"You start with the following equipment, in addition to the equipment granted by your background:", 'You start with the following equipment, in addition to the equipment granted by your background:',
"- *(a)* a martial weapon and a shield or *(b)* two martial weapons", '- *(a)* a martial weapon and a shield or *(b)* two martial weapons',
"- *(a)* five javelins or *(b)* any simple melee weapon", '- *(a)* five javelins or *(b)* any simple melee weapon',
"- " + (_.sample(["10 lint fluffs", "1 button", "a cherished lost sock"])), `- ${_.sample(['10 lint fluffs', '1 button', 'a cherished lost sock'])}`,
"\n\n\n" '\n\n\n'
].join('\n'); ].join('\n');
} };

View File

@@ -1,114 +1,114 @@
var _ = require('lodash'); const _ = require('lodash');
var features = [ const features = [
"Astrological Botany", 'Astrological Botany',
"Astrological Chemistry", 'Astrological Chemistry',
"Biochemical Sorcery", 'Biochemical Sorcery',
"Civil Alchemy", 'Civil Alchemy',
"Consecrated Biochemistry", 'Consecrated Biochemistry',
"Demonic Anthropology", 'Demonic Anthropology',
"Divinatory Mineralogy", 'Divinatory Mineralogy',
"Genetic Banishing", 'Genetic Banishing',
"Hermetic Geography", 'Hermetic Geography',
"Immunological Incantations", 'Immunological Incantations',
"Nuclear Illusionism", 'Nuclear Illusionism',
"Ritual Astronomy", 'Ritual Astronomy',
"Seismological Divination", 'Seismological Divination',
"Spiritual Biochemistry", 'Spiritual Biochemistry',
"Statistical Occultism", 'Statistical Occultism',
"Police Necromancer", 'Police Necromancer',
"Sixgun Poisoner", 'Sixgun Poisoner',
"Pharmaceutical Gunslinger", 'Pharmaceutical Gunslinger',
"Infernal Banker", 'Infernal Banker',
"Spell Analyst", 'Spell Analyst',
"Gunslinger Corruptor", 'Gunslinger Corruptor',
"Torque Interfacer", 'Torque Interfacer',
"Exo Interfacer", 'Exo Interfacer',
"Gunpowder Torturer", 'Gunpowder Torturer',
"Orbital Gravedigger", 'Orbital Gravedigger',
"Phased Linguist", 'Phased Linguist',
"Mathematical Pharmacist", 'Mathematical Pharmacist',
"Plasma Outlaw", 'Plasma Outlaw',
"Malefic Chemist", 'Malefic Chemist',
"Police Cultist" 'Police Cultist'
]; ];
var classnames = ['Archivist', 'Fancyman', 'Linguist', 'Fletcher', const classnames = ['Archivist', 'Fancyman', 'Linguist', 'Fletcher',
'Notary', 'Berserker-Typist', 'Fishmongerer', 'Manicurist', 'Haberdasher', 'Concierge']; 'Notary', 'Berserker-Typist', 'Fishmongerer', 'Manicurist', 'Haberdasher', 'Concierge'];
var levels = ["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th", "11th", "12th", "13th", "14th", "15th", "16th", "17th", "18th", "19th", "20th"] const levels = ['1st', '2nd', '3rd', '4th', '5th', '6th', '7th', '8th', '9th', '10th', '11th', '12th', '13th', '14th', '15th', '16th', '17th', '18th', '19th', '20th'];
var profBonus = [2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,6,6,6,6]; const profBonus = [2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6];
var getFeature = (level)=>{ const getFeature = (level)=>{
var res = [] let res = [];
if(_.includes([4,6,8,12,14,16,19], level+1)){ if(_.includes([4, 6, 8, 12, 14, 16, 19], level+1)){
res = ["Ability Score Improvement"] res = ['Ability Score Improvement'];
} }
res = _.union(res, _.sampleSize(features, _.sample([0,1,1,1,1,1]))); res = _.union(res, _.sampleSize(features, _.sample([0, 1, 1, 1, 1, 1])));
if(!res.length) return "─"; if(!res.length) return '─';
return res.join(', '); return res.join(', ');
} };
module.exports = { module.exports = {
full : function(){ full : function(){
var classname = _.sample(classnames) const classname = _.sample(classnames);
var maxes = [4,3,3,3,3,2,2,1,1] const maxes = [4, 3, 3, 3, 3, 2, 2, 1, 1];
var drawSlots = function(Slots){ const drawSlots = function(Slots){
var slots = Number(Slots); let slots = Number(Slots);
return _.times(9, function(i){ return _.times(9, function(i){
var max = maxes[i]; const max = maxes[i];
if(slots < 1) return "—"; if(slots < 1) return '—';
var res = _.min([max, slots]); const res = _.min([max, slots]);
slots -= res; slots -= res;
return res; return res;
}).join(' | ') }).join(' | ');
} };
var cantrips = 3; let cantrips = 3;
var spells = 1; let spells = 1;
var slots = 2; let slots = 2;
return "<div class='classTable wide'>\n##### The " + classname + "\n" + return `<div class='classTable wide'>\n##### The ${classname}\n` +
"| Level | Proficiency Bonus | Features | Cantrips Known | Spells Known | 1st | 2nd | 3rd | 4th | 5th | 6th | 7th | 8th | 9th |\n"+ `| Level | Proficiency Bonus | Features | Cantrips Known | Spells Known | 1st | 2nd | 3rd | 4th | 5th | 6th | 7th | 8th | 9th |\n`+
"|:---:|:---:|:---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|\n" + `|:---:|:---:|:---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|\n${
_.map(levels, function(levelName, level){ _.map(levels, function(levelName, level){
var res = [ const res = [
levelName, levelName,
"+" + profBonus[level], `+${profBonus[level]}`,
getFeature(level), getFeature(level),
cantrips, cantrips,
spells, spells,
drawSlots(slots) drawSlots(slots)
].join(' | '); ].join(' | ');
cantrips += _.random(0,1); cantrips += _.random(0, 1);
spells += _.random(0,1); spells += _.random(0, 1);
slots += _.random(0,2); slots += _.random(0, 2);
return "| " + res + " |"; return `| ${res} |`;
}).join('\n') +'\n</div>\n\n'; }).join('\n')}\n</div>\n\n`;
}, },
half : function(){ half : function(){
var classname = _.sample(classnames) const classname = _.sample(classnames);
var featureScore = 1 let featureScore = 1;
return "<div class='classTable'>\n##### The " + classname + "\n" + return `<div class='classTable'>\n##### The ${classname}\n` +
"| Level | Proficiency Bonus | Features | " + _.sample(features) + "|\n" + `| Level | Proficiency Bonus | Features | ${_.sample(features)}|\n` +
"|:---:|:---:|:---|:---:|\n" + `|:---:|:---:|:---|:---:|\n${
_.map(levels, function(levelName, level){ _.map(levels, function(levelName, level){
var res = [ const res = [
levelName, levelName,
"+" + profBonus[level], `+${profBonus[level]}`,
getFeature(level), getFeature(level),
"+" + featureScore `+${featureScore}`
].join(' | '); ].join(' | ');
featureScore += _.random(0,1); featureScore += _.random(0, 1);
return "| " + res + " |"; return `| ${res} |`;
}).join('\n') +'\n</div>\n\n'; }).join('\n')}\n</div>\n\n`;
} }
}; };

View File

@@ -1,104 +1,104 @@
var _ = require('lodash'); const _ = require('lodash');
var titles = [ const titles = [
"The Burning Gallows", 'The Burning Gallows',
"The Ring of Nenlast", 'The Ring of Nenlast',
"Below the Blind Tavern", 'Below the Blind Tavern',
"Below the Hungering River", 'Below the Hungering River',
"Before Bahamut's Land", 'Before Bahamut\'s Land',
"The Cruel Grave from Within", 'The Cruel Grave from Within',
"The Strength of Trade Road", 'The Strength of Trade Road',
"Through The Raven Queen's Worlds", 'Through The Raven Queen\'s Worlds',
"Within the Settlement", 'Within the Settlement',
"The Crown from Within", 'The Crown from Within',
"The Merchant Within the Battlefield", 'The Merchant Within the Battlefield',
"Ioun's Fading Traveler", 'Ioun\'s Fading Traveler',
"The Legion Ingredient", 'The Legion Ingredient',
"The Explorer Lure", 'The Explorer Lure',
"Before the Charming Badlands", 'Before the Charming Badlands',
"The Living Dead Above the Fearful Cage", 'The Living Dead Above the Fearful Cage',
"Vecna's Hidden Sage", 'Vecna\'s Hidden Sage',
"Bahamut's Demonspawn", 'Bahamut\'s Demonspawn',
"Across Gruumsh's Elemental Chaos", 'Across Gruumsh\'s Elemental Chaos',
"The Blade of Orcus", 'The Blade of Orcus',
"Beyond Revenge", 'Beyond Revenge',
"Brain of Insanity", 'Brain of Insanity',
"Breed Battle!, A New Beginning", 'Breed Battle!, A New Beginning',
"Evil Lake, A New Beginning", 'Evil Lake, A New Beginning',
"Invasion of the Gigantic Cat, Part II", 'Invasion of the Gigantic Cat, Part II',
"Kraken War 2020", 'Kraken War 2020',
"The Body Whisperers", 'The Body Whisperers',
"The Diabolical Tales of the Ape-Women", 'The Diabolical Tales of the Ape-Women',
"The Doctor Immortal", 'The Doctor Immortal',
"The Doctor from Heaven", 'The Doctor from Heaven',
"The Graveyard", 'The Graveyard',
"Azure Core", 'Azure Core',
"Core Battle", 'Core Battle',
"Core of Heaven: The Guardian of Amazement", 'Core of Heaven: The Guardian of Amazement',
"Deadly Amazement III", 'Deadly Amazement III',
"Dry Chaos IX", 'Dry Chaos IX',
"Gate Thunder", 'Gate Thunder',
"Guardian: Skies of the Dark Wizard", 'Guardian: Skies of the Dark Wizard',
"Lute of Eternity", 'Lute of Eternity',
"Mercury's Planet: Brave Evolution", 'Mercury\'s Planet: Brave Evolution',
"Ruby of Atlantis: The Quake of Peace", 'Ruby of Atlantis: The Quake of Peace',
"Sky of Zelda: The Thunder of Force", 'Sky of Zelda: The Thunder of Force',
"Vyse's Skies", 'Vyse\'s Skies',
"White Greatness III", 'White Greatness III',
"Yellow Divinity", 'Yellow Divinity',
"Zidane's Ghost" 'Zidane\'s Ghost'
]; ];
var subtitles = [ const subtitles = [
"In an ominous universe, a botanist opposes terrorism.", 'In an ominous universe, a botanist opposes terrorism.',
"In a demon-haunted city, in an age of lies and hate, a physicist tries to find an ancient treasure and battles a mob of aliens.", 'In a demon-haunted city, in an age of lies and hate, a physicist tries to find an ancient treasure and battles a mob of aliens.',
"In a land of corruption, two cyberneticists and a dungeon delver search for freedom.", 'In a land of corruption, two cyberneticists and a dungeon delver search for freedom.',
"In an evil empire of horror, two rangers battle the forces of hell.", 'In an evil empire of horror, two rangers battle the forces of hell.',
"In a lost city, in an age of sorcery, a librarian quests for revenge.", 'In a lost city, in an age of sorcery, a librarian quests for revenge.',
"In a universe of illusions and danger, three time travellers and an adventurer search for justice.", 'In a universe of illusions and danger, three time travellers and an adventurer search for justice.',
"In a forgotten universe of barbarism, in an era of terror and mysticism, a virtual reality programmer and a spy try to find vengance and battle crime.", 'In a forgotten universe of barbarism, in an era of terror and mysticism, a virtual reality programmer and a spy try to find vengance and battle crime.',
"In a universe of demons, in an era of insanity and ghosts, three bodyguards and a bodyguard try to find vengance.", 'In a universe of demons, in an era of insanity and ghosts, three bodyguards and a bodyguard try to find vengance.',
"In a kingdom of corruption and battle, seven artificial intelligences try to save the last living fertile woman.", 'In a kingdom of corruption and battle, seven artificial intelligences try to save the last living fertile woman.',
"In a universe of virutal reality and agony, in an age of ghosts and ghosts, a fortune-teller and a wanderer try to avert the apocalypse.", 'In a universe of virutal reality and agony, in an age of ghosts and ghosts, a fortune-teller and a wanderer try to avert the apocalypse.',
"In a crime-infested kingdom, three martial artists quest for the truth and oppose evil.", 'In a crime-infested kingdom, three martial artists quest for the truth and oppose evil.',
"In a terrifying universe of lost souls, in an era of lost souls, eight dancers fight evil.", 'In a terrifying universe of lost souls, in an era of lost souls, eight dancers fight evil.',
"In a galaxy of confusion and insanity, three martial artists and a duke battle a mob of psychics.", 'In a galaxy of confusion and insanity, three martial artists and a duke battle a mob of psychics.',
"In an amazing kingdom, a wizard and a secretary hope to prevent the destruction of mankind.", 'In an amazing kingdom, a wizard and a secretary hope to prevent the destruction of mankind.',
"In a kingdom of deception, a reporter searches for fame.", 'In a kingdom of deception, a reporter searches for fame.',
"In a hellish empire, a swordswoman and a duke try to find the ultimate weapon and battle a conspiracy.", 'In a hellish empire, a swordswoman and a duke try to find the ultimate weapon and battle a conspiracy.',
"In an evil galaxy of illusion, in a time of technology and misery, seven psychiatrists battle crime.", 'In an evil galaxy of illusion, in a time of technology and misery, seven psychiatrists battle crime.',
"In a dark city of confusion, three swordswomen and a singer battle lawlessness.", 'In a dark city of confusion, three swordswomen and a singer battle lawlessness.',
"In an ominous empire, in an age of hate, two philosophers and a student try to find justice and battle a mob of mages intent on stealing the souls of the innocent.", 'In an ominous empire, in an age of hate, two philosophers and a student try to find justice and battle a mob of mages intent on stealing the souls of the innocent.',
"In a kingdom of panic, six adventurers oppose lawlessness.", 'In a kingdom of panic, six adventurers oppose lawlessness.',
"In a land of dreams and hopelessness, three hackers and a cyborg search for justice.", 'In a land of dreams and hopelessness, three hackers and a cyborg search for justice.',
"On a planet of mysticism, three travelers and a fire fighter quest for the ultimate weapon and oppose evil.", 'On a planet of mysticism, three travelers and a fire fighter quest for the ultimate weapon and oppose evil.',
"In a wicked universe, five seers fight lawlessness.", 'In a wicked universe, five seers fight lawlessness.',
"In a kingdom of death, in an era of illusion and blood, four colonists search for fame.", 'In a kingdom of death, in an era of illusion and blood, four colonists search for fame.',
"In an amazing kingdom, in an age of sorcery and lost souls, eight space pirates quest for freedom.", 'In an amazing kingdom, in an age of sorcery and lost souls, eight space pirates quest for freedom.',
"In a cursed empire, five inventors oppose terrorism.", 'In a cursed empire, five inventors oppose terrorism.',
"On a crime-ridden planet of conspiracy, a watchman and an artificial intelligence try to find love and oppose lawlessness.", 'On a crime-ridden planet of conspiracy, a watchman and an artificial intelligence try to find love and oppose lawlessness.',
"In a forgotten land, a reporter and a spy try to stop the apocalypse.", 'In a forgotten land, a reporter and a spy try to stop the apocalypse.',
"In a forbidden land of prophecy, a scientist and an archivist oppose a cabal of barbarians intent on stealing the souls of the innocent.", 'In a forbidden land of prophecy, a scientist and an archivist oppose a cabal of barbarians intent on stealing the souls of the innocent.',
"On an infernal world of illusion, a grave robber and a watchman try to find revenge and combat a syndicate of mages intent on stealing the source of all magic.", 'On an infernal world of illusion, a grave robber and a watchman try to find revenge and combat a syndicate of mages intent on stealing the source of all magic.',
"In a galaxy of dark magic, four fighters seek freedom.", 'In a galaxy of dark magic, four fighters seek freedom.',
"In an empire of deception, six tomb-robbers quest for the ultimate weapon and combat an army of raiders.", 'In an empire of deception, six tomb-robbers quest for the ultimate weapon and combat an army of raiders.',
"In a kingdom of corruption and lost souls, in an age of panic, eight planetologists oppose evil.", 'In a kingdom of corruption and lost souls, in an age of panic, eight planetologists oppose evil.',
"In a galaxy of misery and hopelessness, in a time of agony and pain, five planetologists search for vengance.", 'In a galaxy of misery and hopelessness, in a time of agony and pain, five planetologists search for vengance.',
"In a universe of technology and insanity, in a time of sorcery, a computer techician quests for hope.", 'In a universe of technology and insanity, in a time of sorcery, a computer techician quests for hope.',
"On a planet of dark magic and barbarism, in an age of horror and blasphemy, seven librarians search for fame.", 'On a planet of dark magic and barbarism, in an age of horror and blasphemy, seven librarians search for fame.',
"In an empire of dark magic, in a time of blood and illusions, four monks try to find the ultimate weapon and combat terrorism.", 'In an empire of dark magic, in a time of blood and illusions, four monks try to find the ultimate weapon and combat terrorism.',
"In a forgotten empire of dark magic, six kings try to prevent the destruction of mankind.", 'In a forgotten empire of dark magic, six kings try to prevent the destruction of mankind.',
"In a galaxy of dark magic and horror, in an age of hopelessness, four marines and an outlaw combat evil.", 'In a galaxy of dark magic and horror, in an age of hopelessness, four marines and an outlaw combat evil.',
"In a mysterious city of illusion, in an age of computerization, a witch-hunter tries to find the ultimate weapon and opposes an evil corporation.", 'In a mysterious city of illusion, in an age of computerization, a witch-hunter tries to find the ultimate weapon and opposes an evil corporation.',
"In a damned kingdom of technology, a virtual reality programmer and a fighter seek fame.", 'In a damned kingdom of technology, a virtual reality programmer and a fighter seek fame.',
"In a hellish kingdom, in an age of blasphemy and blasphemy, an astrologer searches for fame.", 'In a hellish kingdom, in an age of blasphemy and blasphemy, an astrologer searches for fame.',
"In a damned world of devils, an alien and a ranger quest for love and oppose a syndicate of demons.", 'In a damned world of devils, an alien and a ranger quest for love and oppose a syndicate of demons.',
"In a cursed galaxy, in a time of pain, seven librarians hope to avert the apocalypse.", 'In a cursed galaxy, in a time of pain, seven librarians hope to avert the apocalypse.',
"In a crime-infested galaxy, in an era of hopelessness and panic, three champions and a grave robber try to solve the ultimate crime." 'In a crime-infested galaxy, in an era of hopelessness and panic, three champions and a grave robber try to solve the ultimate crime.'
]; ];
module.exports = () => { module.exports = ()=>{
return `<style> return `<style>
.phb#p1{ text-align:center; } .phb#p1{ text-align:center; }
.phb#p1:after{ display:none; } .phb#p1:after{ display:none; }
@@ -113,5 +113,5 @@ module.exports = () => {
##### ${_.sample(subtitles)} ##### ${_.sample(subtitles)}
</div> </div>
\\page` \\page`;
} };

View File

@@ -1,43 +1,43 @@
var _ = require('lodash'); const _ = require('lodash');
var ClassFeatureGen = require('./classfeature.gen.js'); const ClassFeatureGen = require('./classfeature.gen.js');
var ClassTableGen = require('./classtable.gen.js'); const ClassTableGen = require('./classtable.gen.js');
module.exports = function(){ module.exports = function(){
var classname = _.sample(['Archivist', 'Fancyman', 'Linguist', 'Fletcher', const classname = _.sample(['Archivist', 'Fancyman', 'Linguist', 'Fletcher',
'Notary', 'Berserker-Typist', 'Fishmongerer', 'Manicurist', 'Haberdasher', 'Concierge']) 'Notary', 'Berserker-Typist', 'Fishmongerer', 'Manicurist', 'Haberdasher', 'Concierge']);
var image = _.sample(_.map([ const image = _.sample(_.map([
"http://orig01.deviantart.net/4682/f/2007/099/f/c/bard_stick_figure_by_wrpigeek.png", 'http://orig01.deviantart.net/4682/f/2007/099/f/c/bard_stick_figure_by_wrpigeek.png',
"http://img07.deviantart.net/a3c9/i/2007/099/3/a/archer_stick_figure_by_wrpigeek.png", 'http://img07.deviantart.net/a3c9/i/2007/099/3/a/archer_stick_figure_by_wrpigeek.png',
"http://pre04.deviantart.net/d596/th/pre/f/2007/099/5/2/adventurer_stick_figure_by_wrpigeek.png", 'http://pre04.deviantart.net/d596/th/pre/f/2007/099/5/2/adventurer_stick_figure_by_wrpigeek.png',
"http://img13.deviantart.net/d501/i/2007/099/d/4/black_mage_stick_figure_by_wrpigeek.png", 'http://img13.deviantart.net/d501/i/2007/099/d/4/black_mage_stick_figure_by_wrpigeek.png',
"http://img09.deviantart.net/5cf3/i/2007/099/d/d/dark_knight_stick_figure_by_wrpigeek.png", 'http://img09.deviantart.net/5cf3/i/2007/099/d/d/dark_knight_stick_figure_by_wrpigeek.png',
"http://pre01.deviantart.net/7a34/th/pre/f/2007/099/6/3/monk_stick_figure_by_wrpigeek.png", 'http://pre01.deviantart.net/7a34/th/pre/f/2007/099/6/3/monk_stick_figure_by_wrpigeek.png',
"http://img11.deviantart.net/5dcc/i/2007/099/d/1/mystic_knight_stick_figure_by_wrpigeek.png", 'http://img11.deviantart.net/5dcc/i/2007/099/d/1/mystic_knight_stick_figure_by_wrpigeek.png',
"http://pre08.deviantart.net/ad45/th/pre/f/2007/099/a/0/thief_stick_figure_by_wrpigeek.png", 'http://pre08.deviantart.net/ad45/th/pre/f/2007/099/a/0/thief_stick_figure_by_wrpigeek.png',
], function(url){ ], function(url){
return "<img src = '" + url + "' style='max-width:8cm;max-height:25cm' />" return `<img src = '${url}' style='max-width:8cm;max-height:25cm' />`;
})) }));
return [ return `${[
image, image,
"", '',
"```", '```',
"```", '```',
"<div style='margin-top:240px'></div>\n\n", '<div style=\'margin-top:240px\'></div>\n\n',
"## " + classname, `## ${classname}`,
"Cool intro stuff will go here", 'Cool intro stuff will go here',
"\\page", '\\page',
ClassTableGen(classname), ClassTableGen(classname),
ClassFeatureGen(classname), ClassFeatureGen(classname),
].join('\n') + '\n\n\n'; ].join('\n')}\n\n\n`;
}; };

View File

@@ -1,60 +1,60 @@
var _ = require('lodash'); const _ = require('lodash');
var spellNames = [ const spellNames = [
"Astral Rite of Acne", 'Astral Rite of Acne',
"Create Acne", 'Create Acne',
"Cursed Ramen Erruption", 'Cursed Ramen Erruption',
"Dark Chant of the Dentists", 'Dark Chant of the Dentists',
"Erruption of Immaturity", 'Erruption of Immaturity',
"Flaming Disc of Inconvenience", 'Flaming Disc of Inconvenience',
"Heal Bad Hygene", 'Heal Bad Hygene',
"Heavenly Transfiguration of the Cream Devil", 'Heavenly Transfiguration of the Cream Devil',
"Hellish Cage of Mucus", 'Hellish Cage of Mucus',
"Irritate Peanut Butter Fairy", 'Irritate Peanut Butter Fairy',
"Luminous Erruption of Tea", 'Luminous Erruption of Tea',
"Mystic Spell of the Poser", 'Mystic Spell of the Poser',
"Sorcerous Enchantment of the Chimneysweep", 'Sorcerous Enchantment of the Chimneysweep',
"Steak Sauce Ray", 'Steak Sauce Ray',
"Talk to Groupie", 'Talk to Groupie',
"Astonishing Chant of Chocolate", 'Astonishing Chant of Chocolate',
"Astounding Pasta Puddle", 'Astounding Pasta Puddle',
"Ball of Annoyance", 'Ball of Annoyance',
"Cage of Yarn", 'Cage of Yarn',
"Control Noodles Elemental", 'Control Noodles Elemental',
"Create Nervousness", 'Create Nervousness',
"Cure Baldness", 'Cure Baldness',
"Cursed Ritual of Bad Hair", 'Cursed Ritual of Bad Hair',
"Dispell Piles in Dentist", 'Dispell Piles in Dentist',
"Eliminate Florists", 'Eliminate Florists',
"Illusionary Transfiguration of the Babysitter", 'Illusionary Transfiguration of the Babysitter',
"Necromantic Armor of Salad Dressing", 'Necromantic Armor of Salad Dressing',
"Occult Transfiguration of Foot Fetish", 'Occult Transfiguration of Foot Fetish',
"Protection from Mucus Giant", 'Protection from Mucus Giant',
"Tinsel Blast", 'Tinsel Blast',
"Alchemical Evocation of the Goths", 'Alchemical Evocation of the Goths',
"Call Fangirl", 'Call Fangirl',
"Divine Spell of Crossdressing", 'Divine Spell of Crossdressing',
"Dominate Ramen Giant", 'Dominate Ramen Giant',
"Eliminate Vindictiveness in Gym Teacher", 'Eliminate Vindictiveness in Gym Teacher',
"Extra-Planar Spell of Irritation", 'Extra-Planar Spell of Irritation',
"Induce Whining in Babysitter", 'Induce Whining in Babysitter',
"Invoke Complaining", 'Invoke Complaining',
"Magical Enchantment of Arrogance", 'Magical Enchantment of Arrogance',
"Occult Globe of Salad Dressing", 'Occult Globe of Salad Dressing',
"Overwhelming Enchantment of the Chocolate Fairy", 'Overwhelming Enchantment of the Chocolate Fairy',
"Sorcerous Dandruff Globe", 'Sorcerous Dandruff Globe',
"Spiritual Invocation of the Costumers", 'Spiritual Invocation of the Costumers',
"Ultimate Rite of the Confetti Angel", 'Ultimate Rite of the Confetti Angel',
"Ultimate Ritual of Mouthwash", 'Ultimate Ritual of Mouthwash',
]; ];
module.exports = { module.exports = {
spellList : function(){ spellList : function(){
var levels = ['Cantrips (0 Level)', '2nd Level', '3rd Level', '4th Level', '5th Level', '6th Level', '7th Level', '8th Level', '9th Level']; const levels = ['Cantrips (0 Level)', '2nd Level', '3rd Level', '4th Level', '5th Level', '6th Level', '7th Level', '8th Level', '9th Level'];
var content = _.map(levels, (level)=>{ const content = _.map(levels, (level)=>{
var spells = _.map(_.sampleSize(spellNames, _.random(5, 15)), (spell)=>{ const spells = _.map(_.sampleSize(spellNames, _.random(5, 15)), (spell)=>{
return `- ${spell}`; return `- ${spell}`;
}).join('\n'); }).join('\n');
return `##### ${level} \n${spells} \n`; return `##### ${level} \n${spells} \n`;
@@ -64,28 +64,28 @@ module.exports = {
}, },
spell : function(){ spell : function(){
var level = ["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th"]; const level = ['1st', '2nd', '3rd', '4th', '5th', '6th', '7th', '8th', '9th'];
var spellSchools = ["abjuration", "conjuration", "divination", "enchantment", "evocation", "illusion", "necromancy", "transmutation"]; const spellSchools = ['abjuration', 'conjuration', 'divination', 'enchantment', 'evocation', 'illusion', 'necromancy', 'transmutation'];
var components = _.sampleSize(["V", "S", "M"], _.random(1,3)).join(', '); let components = _.sampleSize(['V', 'S', 'M'], _.random(1, 3)).join(', ');
if(components.indexOf("M") !== -1){ if(components.indexOf('M') !== -1){
components += " (" + _.sampleSize(['a small doll', 'a crushed button worth at least 1cp', 'discarded gum wrapper'], _.random(1,3)).join(', ') + ")" components += ` (${_.sampleSize(['a small doll', 'a crushed button worth at least 1cp', 'discarded gum wrapper'], _.random(1, 3)).join(', ')})`;
} }
return [ return [
"#### " + _.sample(spellNames), `#### ${_.sample(spellNames)}`,
"*" + _.sample(level) + "-level " + _.sample(spellSchools) + "*", `*${_.sample(level)}-level ${_.sample(spellSchools)}*`,
"___", '___',
"- **Casting Time:** 1 action", '- **Casting Time:** 1 action',
"- **Range:** " + _.sample(["Self", "Touch", "30 feet", "60 feet"]), `- **Range:** ${_.sample(['Self', 'Touch', '30 feet', '60 feet'])}`,
"- **Components:** " + components, `- **Components:** ${components}`,
"- **Duration:** " + _.sample(["Until dispelled", "1 round", "Instantaneous", "Concentration, up to 10 minutes", "1 hour"]), `- **Duration:** ${_.sample(['Until dispelled', '1 round', 'Instantaneous', 'Concentration, up to 10 minutes', '1 hour'])}`,
"", '',
"A flame, equivalent in brightness to a torch, springs from from an object that you touch. ", 'A flame, equivalent in brightness to a torch, springs from from an object that you touch. ',
"The effect look like a regular flame, but it creates no heat and doesn't use oxygen. ", 'The effect look like a regular flame, but it creates no heat and doesn\'t use oxygen. ',
"A *continual flame* can be covered or hidden but not smothered or quenched.", 'A *continual flame* can be covered or hidden but not smothered or quenched.',
"\n\n\n" '\n\n\n'
].join('\n'); ].join('\n');
} }
} };

View File

@@ -1,196 +1,196 @@
var _ = require('lodash'); const _ = require('lodash');
var genList = function(list, max){ const genList = function(list, max){
return _.sampleSize(list, _.random(0,max)).join(', ') || "None"; return _.sampleSize(list, _.random(0, max)).join(', ') || 'None';
} };
var getMonsterName = function(){ const getMonsterName = function(){
return _.sample([ return _.sample([
"All-devouring Baseball Imp", 'All-devouring Baseball Imp',
"All-devouring Gumdrop Wraith", 'All-devouring Gumdrop Wraith',
"Chocolate Hydra", 'Chocolate Hydra',
"Devouring Peacock", 'Devouring Peacock',
"Economy-sized Colossus of the Lemonade Stand", 'Economy-sized Colossus of the Lemonade Stand',
"Ghost Pigeon", 'Ghost Pigeon',
"Gibbering Duck", 'Gibbering Duck',
"Sparklemuffin Peacock Spider", 'Sparklemuffin Peacock Spider',
"Gum Elemental", 'Gum Elemental',
"Illiterate Construct of the Candy Store", 'Illiterate Construct of the Candy Store',
"Ineffable Chihuahua", 'Ineffable Chihuahua',
"Irritating Death Hamster", 'Irritating Death Hamster',
"Irritating Gold Mouse", 'Irritating Gold Mouse',
"Juggernaut Snail", 'Juggernaut Snail',
"Juggernaut of the Sock Drawer", 'Juggernaut of the Sock Drawer',
"Koala of the Cosmos", 'Koala of the Cosmos',
"Mad Koala of the West", 'Mad Koala of the West',
"Milk Djinni of the Lemonade Stand", 'Milk Djinni of the Lemonade Stand',
"Mind Ferret", 'Mind Ferret',
"Mystic Salt Spider", 'Mystic Salt Spider',
"Necrotic Halitosis Angel", 'Necrotic Halitosis Angel',
"Pinstriped Famine Sheep", 'Pinstriped Famine Sheep',
"Ritalin Leech", 'Ritalin Leech',
"Shocker Kangaroo", 'Shocker Kangaroo',
"Stellar Tennis Juggernaut", 'Stellar Tennis Juggernaut',
"Wailing Quail of the Sun", 'Wailing Quail of the Sun',
"Angel Pigeon", 'Angel Pigeon',
"Anime Sphinx", 'Anime Sphinx',
"Bored Avalanche Sheep of the Wasteland", 'Bored Avalanche Sheep of the Wasteland',
"Devouring Nougat Sphinx of the Sock Drawer", 'Devouring Nougat Sphinx of the Sock Drawer',
"Djinni of the Footlocker", 'Djinni of the Footlocker',
"Ectoplasmic Jazz Devil", 'Ectoplasmic Jazz Devil',
"Flatuent Angel", 'Flatuent Angel',
"Gelatinous Duck of the Dream-Lands", 'Gelatinous Duck of the Dream-Lands',
"Gelatinous Mouse", 'Gelatinous Mouse',
"Golem of the Footlocker", 'Golem of the Footlocker',
"Lich Wombat", 'Lich Wombat',
"Mechanical Sloth of the Past", 'Mechanical Sloth of the Past',
"Milkshake Succubus", 'Milkshake Succubus',
"Puffy Bone Peacock of the East", 'Puffy Bone Peacock of the East',
"Rainbow Manatee", 'Rainbow Manatee',
"Rune Parrot", 'Rune Parrot',
"Sand Cow", 'Sand Cow',
"Sinister Vanilla Dragon", 'Sinister Vanilla Dragon',
"Snail of the North", 'Snail of the North',
"Spider of the Sewer", 'Spider of the Sewer',
"Stellar Sawdust Leech", 'Stellar Sawdust Leech',
"Storm Anteater of Hell", 'Storm Anteater of Hell',
"Stupid Spirit of the Brewery", 'Stupid Spirit of the Brewery',
"Time Kangaroo", 'Time Kangaroo',
"Tomb Poodle", 'Tomb Poodle',
]); ]);
} };
var getType = function(){ const getType = function(){
return _.sample(['Tiny', 'Small', 'Medium', 'Large', 'Gargantuan', 'Stupidly vast']) + " " + _.sample(['beast', 'fiend', 'annoyance', 'guy', 'cutie']) return `${_.sample(['Tiny', 'Small', 'Medium', 'Large', 'Gargantuan', 'Stupidly vast'])} ${_.sample(['beast', 'fiend', 'annoyance', 'guy', 'cutie'])}`;
} };
var getAlignment = function(){ const getAlignment = function(){
return _.sample([ return _.sample([
"annoying evil", 'annoying evil',
"chaotic gossipy", 'chaotic gossipy',
"chaotic sloppy", 'chaotic sloppy',
"depressed neutral", 'depressed neutral',
"lawful bogus", 'lawful bogus',
"lawful coy", 'lawful coy',
"manic-depressive evil", 'manic-depressive evil',
"narrow-minded neutral", 'narrow-minded neutral',
"neutral annoying", 'neutral annoying',
"neutral ignorant", 'neutral ignorant',
"oedpipal neutral", 'oedpipal neutral',
"silly neutral", 'silly neutral',
"unoriginal neutral", 'unoriginal neutral',
"weird neutral", 'weird neutral',
"wordy evil", 'wordy evil',
"unaligned" 'unaligned'
]); ]);
}; };
var getStats = function(){ const getStats = function(){
return '>|' + _.times(6, function(){ return `>|${_.times(6, function(){
var num = _.random(1,20); const num = _.random(1, 20);
var mod = Math.ceil(num/2 - 5) const mod = Math.ceil(num/2 - 5);
return num + " (" + (mod >= 0 ? '+'+mod : mod ) + ")" return `${num} (${mod >= 0 ? `+${mod}` : mod})`;
}).join('|') + '|'; }).join('|')}|`;
} };
var genAbilities = function(){ const genAbilities = function(){
return _.sample([ return _.sample([
"> ***Pack Tactics.*** These guys work together. Like super well, you don't even know.", '> ***Pack Tactics.*** These guys work together. Like super well, you don\'t even know.',
"> ***False Appearance. *** While the armor reamin motionless, it is indistinguishable from a normal suit of armor.", '> ***False Appearance. *** While the armor reamin motionless, it is indistinguishable from a normal suit of armor.',
]); ]);
} };
var genAction = function(){ const genAction = function(){
var name = _.sample([ const name = _.sample([
"Abdominal Drop", 'Abdominal Drop',
"Airplane Hammer", 'Airplane Hammer',
"Atomic Death Throw", 'Atomic Death Throw',
"Bulldog Rake", 'Bulldog Rake',
"Corkscrew Strike", 'Corkscrew Strike',
"Crossed Splash", 'Crossed Splash',
"Crossface Suplex", 'Crossface Suplex',
"DDT Powerbomb", 'DDT Powerbomb',
"Dual Cobra Wristlock", 'Dual Cobra Wristlock',
"Dual Throw", 'Dual Throw',
"Elbow Hold", 'Elbow Hold',
"Gory Body Sweep", 'Gory Body Sweep',
"Heel Jawbreaker", 'Heel Jawbreaker',
"Jumping Driver", 'Jumping Driver',
"Open Chin Choke", 'Open Chin Choke',
"Scorpion Flurry", 'Scorpion Flurry',
"Somersault Stump Fists", 'Somersault Stump Fists',
"Suffering Wringer", 'Suffering Wringer',
"Super Hip Submission", 'Super Hip Submission',
"Super Spin", 'Super Spin',
"Team Elbow", 'Team Elbow',
"Team Foot", 'Team Foot',
"Tilt-a-whirl Chin Sleeper", 'Tilt-a-whirl Chin Sleeper',
"Tilt-a-whirl Eye Takedown", 'Tilt-a-whirl Eye Takedown',
"Turnbuckle Roll" 'Turnbuckle Roll'
]) ]);
return "> ***" + name + ".*** *Melee Weapon Attack:* +4 to hit, reach 5ft., one target. *Hit* 5 (1d6 + 2) "; return `> ***${name}.*** *Melee Weapon Attack:* +4 to hit, reach 5ft., one target. *Hit* 5 (1d6 + 2) `;
} };
module.exports = { module.exports = {
full : function(){ full : function(){
return [ return `${[
"___", '___',
"___", '___',
"> ## " + getMonsterName(), `> ## ${getMonsterName()}`,
">*" + getType() + ", " + getAlignment() + "*", `>*${getType()}, ${getAlignment()}*`,
"> ___", '> ___',
"> - **Armor Class** " + _.random(10,20), `> - **Armor Class** ${_.random(10, 20)}`,
"> - **Hit Points** " + _.random(1, 150) + "(1d4 + 5)", `> - **Hit Points** ${_.random(1, 150)}(1d4 + 5)`,
"> - **Speed** " + _.random(0,50) + "ft.", `> - **Speed** ${_.random(0, 50)}ft.`,
">___", '>___',
">|STR|DEX|CON|INT|WIS|CHA|", '>|STR|DEX|CON|INT|WIS|CHA|',
">|:---:|:---:|:---:|:---:|:---:|:---:|", '>|:---:|:---:|:---:|:---:|:---:|:---:|',
getStats(), getStats(),
">___", '>___',
"> - **Condition Immunities** " + genList(["groggy", "swagged", "weak-kneed", "buzzed", "groovy", "melancholy", "drunk"], 3), `> - **Condition Immunities** ${genList(['groggy', 'swagged', 'weak-kneed', 'buzzed', 'groovy', 'melancholy', 'drunk'], 3)}`,
"> - **Senses** passive Perception " + _.random(3, 20), `> - **Senses** passive Perception ${_.random(3, 20)}`,
"> - **Languages** " + genList(["Common", "Pottymouth", "Gibberish", "Latin", "Jive"], 2), `> - **Languages** ${genList(['Common', 'Pottymouth', 'Gibberish', 'Latin', 'Jive'], 2)}`,
"> - **Challenge** " + _.random(0, 15) + " (" + _.random(10,10000)+ " XP)", `> - **Challenge** ${_.random(0, 15)} (${_.random(10, 10000)} XP)`,
"> ___", '> ___',
_.times(_.random(3,6), function(){ _.times(_.random(3, 6), function(){
return genAbilities() return genAbilities();
}).join('\n>\n'), }).join('\n>\n'),
"> ### Actions", '> ### Actions',
_.times(_.random(4,6), function(){ _.times(_.random(4, 6), function(){
return genAction() return genAction();
}).join('\n>\n'), }).join('\n>\n'),
].join('\n') + '\n\n\n'; ].join('\n')}\n\n\n`;
}, },
half : function(){ half : function(){
return [ return `${[
"___", '___',
"> ## " + getMonsterName(), `> ## ${getMonsterName()}`,
">*" + getType() + ", " + getAlignment() + "*", `>*${getType()}, ${getAlignment()}*`,
"> ___", '> ___',
"> - **Armor Class** " + _.random(10,20), `> - **Armor Class** ${_.random(10, 20)}`,
"> - **Hit Points** " + _.random(1, 150) + "(1d4 + 5)", `> - **Hit Points** ${_.random(1, 150)}(1d4 + 5)`,
"> - **Speed** " + _.random(0,50) + "ft.", `> - **Speed** ${_.random(0, 50)}ft.`,
">___", '>___',
">|STR|DEX|CON|INT|WIS|CHA|", '>|STR|DEX|CON|INT|WIS|CHA|',
">|:---:|:---:|:---:|:---:|:---:|:---:|", '>|:---:|:---:|:---:|:---:|:---:|:---:|',
getStats(), getStats(),
">___", '>___',
"> - **Condition Immunities** " + genList(["groggy", "swagged", "weak-kneed", "buzzed", "groovy", "melancholy", "drunk"], 3), `> - **Condition Immunities** ${genList(['groggy', 'swagged', 'weak-kneed', 'buzzed', 'groovy', 'melancholy', 'drunk'], 3)}`,
"> - **Senses** passive Perception " + _.random(3, 20), `> - **Senses** passive Perception ${_.random(3, 20)}`,
"> - **Languages** " + genList(["Common", "Pottymouth", "Gibberish", "Latin", "Jive"], 2), `> - **Languages** ${genList(['Common', 'Pottymouth', 'Gibberish', 'Latin', 'Jive'], 2)}`,
"> - **Challenge** " + _.random(0, 15) + " (" + _.random(10,10000)+ " XP)", `> - **Challenge** ${_.random(0, 15)} (${_.random(10, 10000)} XP)`,
"> ___", '> ___',
_.times(_.random(0,2), function(){ _.times(_.random(0, 2), function(){
return genAbilities() return genAbilities();
}).join('\n>\n'), }).join('\n>\n'),
"> ### Actions", '> ### Actions',
_.times(_.random(1,2), function(){ _.times(_.random(1, 2), function(){
return genAction() return genAction();
}).join('\n>\n'), }).join('\n>\n'),
].join('\n') + '\n\n\n'; ].join('\n')}\n\n\n`;
} }
} };

View File

@@ -1,80 +1,81 @@
var MagicGen = require('./magic.gen.js'); /* eslint-disable max-lines */
var ClassTableGen = require('./classtable.gen.js');
var MonsterBlockGen = require('./monsterblock.gen.js'); const MagicGen = require('./magic.gen.js');
var ClassFeatureGen = require('./classfeature.gen.js'); const ClassTableGen = require('./classtable.gen.js');
var FullClassGen = require('./fullclass.gen.js'); const MonsterBlockGen = require('./monsterblock.gen.js');
var CoverPageGen = require('./coverpage.gen.js'); const ClassFeatureGen = require('./classfeature.gen.js');
var TableOfContentsGen = require('./tableOfContents.gen.js'); const CoverPageGen = require('./coverpage.gen.js');
const TableOfContentsGen = require('./tableOfContents.gen.js');
module.exports = [ module.exports = [
{ {
groupName : 'Editor', groupName : 'Editor',
icon : 'fa-pencil', icon : 'fa-pencil',
snippets : [ snippets : [
{ {
name : "Column Break", name : 'Column Break',
icon : 'fa-columns', icon : 'fa-columns',
gen : "```\n```\n\n" gen : '```\n```\n\n'
}, },
{ {
name : "New Page", name : 'New Page',
icon : 'fa-file-text', icon : 'fa-file-text',
gen : "\\page\n\n" gen : '\\page\n\n'
}, },
{ {
name : "Vertical Spacing", name : 'Vertical Spacing',
icon : 'fa-arrows-v', icon : 'fa-arrows-v',
gen : "<div style='margin-top:140px'></div>\n\n" gen : '<div style=\'margin-top:140px\'></div>\n\n'
}, },
{ {
name : "Wide Block", name : 'Wide Block',
icon : 'fa-arrows-h', icon : 'fa-arrows-h',
gen : "<div class='wide'>\nEverything in here will be extra wide. Tables, text, everything! Beware though, CSS columns can behave a bit weird sometimes.\n</div>\n" gen : '<div class=\'wide\'>\nEverything in here will be extra wide. Tables, text, everything! Beware though, CSS columns can behave a bit weird sometimes.\n</div>\n'
}, },
{ {
name : "Image", name : 'Image',
icon : 'fa-image', icon : 'fa-image',
gen : [ gen : [
"<img ", '<img ',
" src='https://s-media-cache-ak0.pinimg.com/736x/4a/81/79/4a8179462cfdf39054a418efd4cb743e.jpg' ", ' src=\'https://s-media-cache-ak0.pinimg.com/736x/4a/81/79/4a8179462cfdf39054a418efd4cb743e.jpg\' ',
" style='width:325px' />", ' style=\'width:325px\' />',
"Credit: Kyounghwan Kim" 'Credit: Kyounghwan Kim'
].join('\n') ].join('\n')
}, },
{ {
name : "Background Image", name : 'Background Image',
icon : 'fa-tree', icon : 'fa-tree',
gen : [ gen : [
"<img ", '<img ',
" src='http://i.imgur.com/hMna6G0.png' ", ' src=\'http://i.imgur.com/hMna6G0.png\' ',
" style='position:absolute; top:50px; right:30px; width:280px' />" ' style=\'position:absolute; top:50px; right:30px; width:280px\' />'
].join('\n') ].join('\n')
}, },
{ {
name : "Page Number", name : 'Page Number',
icon : 'fa-bookmark', icon : 'fa-bookmark',
gen : "<div class='pageNumber'>1</div>\n<div class='footnote'>PART 1 | FANCINESS</div>\n\n" gen : '<div class=\'pageNumber\'>1</div>\n<div class=\'footnote\'>PART 1 | FANCINESS</div>\n\n'
}, },
{ {
name : "Auto-incrementing Page Number", name : 'Auto-incrementing Page Number',
icon : 'fa-sort-numeric-asc', icon : 'fa-sort-numeric-asc',
gen : "<div class='pageNumber auto'></div>\n" gen : '<div class=\'pageNumber auto\'></div>\n'
}, },
{ {
name : "Link to page", name : 'Link to page',
icon : 'fa-link', icon : 'fa-link',
gen : "[Click here](#p3) to go to page 3\n" gen : '[Click here](#p3) to go to page 3\n'
}, },
{ {
name : "Table of Contents", name : 'Table of Contents',
icon : 'fa-book', icon : 'fa-book',
gen : TableOfContentsGen gen : TableOfContentsGen
}, },
@@ -86,63 +87,63 @@ module.exports = [
{ {
groupName : 'PHB', groupName : 'PHB',
icon : 'fa-book', icon : 'fa-book',
snippets : [ snippets : [
{ {
name : 'Spell', name : 'Spell',
icon : 'fa-magic', icon : 'fa-magic',
gen : MagicGen.spell, gen : MagicGen.spell,
}, },
{ {
name : 'Spell List', name : 'Spell List',
icon : 'fa-list', icon : 'fa-list',
gen : MagicGen.spellList, gen : MagicGen.spellList,
}, },
{ {
name : 'Class Feature', name : 'Class Feature',
icon : 'fa-trophy', icon : 'fa-trophy',
gen : ClassFeatureGen, gen : ClassFeatureGen,
}, },
{ {
name : 'Note', name : 'Note',
icon : 'fa-sticky-note', icon : 'fa-sticky-note',
gen : function(){ gen : function(){
return [ return [
"> ##### Time to Drop Knowledge", '> ##### Time to Drop Knowledge',
"> Use notes to point out some interesting information. ", '> Use notes to point out some interesting information. ',
"> ", '> ',
"> **Tables and lists** both work within a note." '> **Tables and lists** both work within a note.'
].join('\n'); ].join('\n');
}, },
}, },
{ {
name : 'Descriptive Text Box', name : 'Descriptive Text Box',
icon : 'fa-sticky-note-o', icon : 'fa-sticky-note-o',
gen : function(){ gen : function(){
return [ return [
"<div class='descriptive'>", '<div class=\'descriptive\'>',
"##### Time to Drop Knowledge", '##### Time to Drop Knowledge',
"Use notes to point out some interesting information. ", 'Use notes to point out some interesting information. ',
"", '',
"**Tables and lists** both work within a note.", '**Tables and lists** both work within a note.',
"</div>" '</div>'
].join('\n'); ].join('\n');
}, },
}, },
{ {
name : 'Monster Stat Block', name : 'Monster Stat Block',
icon : 'fa-bug', icon : 'fa-bug',
gen : MonsterBlockGen.half, gen : MonsterBlockGen.half,
}, },
{ {
name : 'Wide Monster Stat Block', name : 'Wide Monster Stat Block',
icon : 'fa-paw', icon : 'fa-paw',
gen : MonsterBlockGen.full, gen : MonsterBlockGen.full,
}, },
{ {
name : 'Cover Page', name : 'Cover Page',
icon : 'fa-file-word-o', icon : 'fa-file-word-o',
gen : CoverPageGen, gen : CoverPageGen,
}, },
] ]
}, },
@@ -153,77 +154,77 @@ module.exports = [
{ {
groupName : 'Tables', groupName : 'Tables',
icon : 'fa-table', icon : 'fa-table',
snippets : [ snippets : [
{ {
name : "Class Table", name : 'Class Table',
icon : 'fa-table', icon : 'fa-table',
gen : ClassTableGen.full, gen : ClassTableGen.full,
}, },
{ {
name : "Half Class Table", name : 'Half Class Table',
icon : 'fa-list-alt', icon : 'fa-list-alt',
gen : ClassTableGen.half, gen : ClassTableGen.half,
}, },
{ {
name : 'Table', name : 'Table',
icon : 'fa-th-list', icon : 'fa-th-list',
gen : function(){ gen : function(){
return [ return [
"##### Cookie Tastiness", '##### Cookie Tastiness',
"| Tastiness | Cookie Type |", '| Tastiness | Cookie Type |',
"|:----:|:-------------|", '|:----:|:-------------|',
"| -5 | Raisin |", '| -5 | Raisin |',
"| 8th | Chocolate Chip |", '| 8th | Chocolate Chip |',
"| 11th | 2 or lower |", '| 11th | 2 or lower |',
"| 14th | 3 or lower |", '| 14th | 3 or lower |',
"| 17th | 4 or lower |\n\n", '| 17th | 4 or lower |\n\n',
].join('\n'); ].join('\n');
}, },
}, },
{ {
name : 'Wide Table', name : 'Wide Table',
icon : 'fa-list', icon : 'fa-list',
gen : function(){ gen : function(){
return [ return [
"<div class='wide'>", '<div class=\'wide\'>',
"##### Cookie Tastiness", '##### Cookie Tastiness',
"| Tastiness | Cookie Type |", '| Tastiness | Cookie Type |',
"|:----:|:-------------|", '|:----:|:-------------|',
"| -5 | Raisin |", '| -5 | Raisin |',
"| 8th | Chocolate Chip |", '| 8th | Chocolate Chip |',
"| 11th | 2 or lower |", '| 11th | 2 or lower |',
"| 14th | 3 or lower |", '| 14th | 3 or lower |',
"| 17th | 4 or lower |", '| 17th | 4 or lower |',
"</div>\n\n" '</div>\n\n'
].join('\n'); ].join('\n');
}, },
}, },
{ {
name : 'Split Table', name : 'Split Table',
icon : 'fa-th-large', icon : 'fa-th-large',
gen : function(){ gen : function(){
return [ return [
"<div style='column-count:2'>", '<div style=\'column-count:2\'>',
"| d10 | Damage Type |", '| d10 | Damage Type |',
"|:---:|:------------|", '|:---:|:------------|',
"| 1 | Acid |", '| 1 | Acid |',
"| 2 | Cold |", '| 2 | Cold |',
"| 3 | Fire |", '| 3 | Fire |',
"| 4 | Force |", '| 4 | Force |',
"| 5 | Lightning |", '| 5 | Lightning |',
"", '',
"```", '```',
"```", '```',
"", '',
"| d10 | Damage Type |", '| d10 | Damage Type |',
"|:---:|:------------|", '|:---:|:------------|',
"| 6 | Necrotic |", '| 6 | Necrotic |',
"| 7 | Poison |", '| 7 | Poison |',
"| 8 | Psychic |", '| 8 | Psychic |',
"| 9 | Radiant |", '| 9 | Radiant |',
"| 10 | Thunder |", '| 10 | Thunder |',
"</div>\n\n", '</div>\n\n',
].join('\n'); ].join('\n');
}, },
} }
@@ -237,12 +238,12 @@ module.exports = [
{ {
groupName : 'Print', groupName : 'Print',
icon : 'fa-print', icon : 'fa-print',
snippets : [ snippets : [
{ {
name : "A4 PageSize", name : 'A4 PageSize',
icon : 'fa-file-o', icon : 'fa-file-o',
gen : ['<style>', gen : ['<style>',
' .phb{', ' .phb{',
' width : 210mm;', ' width : 210mm;',
' height : 296.8mm;', ' height : 296.8mm;',
@@ -251,9 +252,9 @@ module.exports = [
].join('\n') ].join('\n')
}, },
{ {
name : "Ink Friendly", name : 'Ink Friendly',
icon : 'fa-tint', icon : 'fa-tint',
gen : ['<style>', gen : ['<style>',
' .phb{ background : white;}', ' .phb{ background : white;}',
' .phb img{ display : none;}', ' .phb img{ display : none;}',
' .phb hr+blockquote{background : white;}', ' .phb hr+blockquote{background : white;}',
@@ -264,4 +265,4 @@ module.exports = [
] ]
}, },
] ];

View File

@@ -1,38 +1,38 @@
const _ = require('lodash'); const _ = require('lodash');
const getTOC = (pages) => { const getTOC = (pages)=>{
const add1 = (title, page)=>{ const add1 = (title, page)=>{
res.push({ res.push({
title : title, title : title,
page : page + 1, page : page + 1,
children : [] children : []
}); });
} };
const add2 = (title, page)=>{ const add2 = (title, page)=>{
if(!_.last(res)) add1('', page); if(!_.last(res)) add1('', page);
_.last(res).children.push({ _.last(res).children.push({
title : title, title : title,
page : page + 1, page : page + 1,
children : [] children : []
}); });
} };
const add3 = (title, page)=>{ const add3 = (title, page)=>{
if(!_.last(res)) add1('', page); if(!_.last(res)) add1('', page);
if(!_.last(_.last(res).children)) add2('', page); if(!_.last(_.last(res).children)) add2('', page);
_.last(_.last(res).children).children.push({ _.last(_.last(res).children).children.push({
title : title, title : title,
page : page + 1, page : page + 1,
children : [] children : []
}); });
} };
let res = []; const res = [];
_.each(pages, (page, pageNum)=>{ _.each(pages, (page, pageNum)=>{
const lines = page.split('\n'); const lines = page.split('\n');
_.each(lines, (line) => { _.each(lines, (line)=>{
if(_.startsWith(line, '# ')){ if(_.startsWith(line, '# ')){
const title = line.replace('# ', ''); const title = line.replace('# ', '');
add1(title, pageNum) add1(title, pageNum);
} }
if(_.startsWith(line, '## ')){ if(_.startsWith(line, '## ')){
const title = line.replace('## ', ''); const title = line.replace('## ', '');
@@ -42,21 +42,21 @@ const getTOC = (pages) => {
const title = line.replace('### ', ''); const title = line.replace('### ', '');
add3(title, pageNum); add3(title, pageNum);
} }
}) });
}); });
return res; return res;
} };
module.exports = function(brew){ module.exports = function(brew){
const pages = brew.split('\\page'); const pages = brew.split('\\page');
const TOC = getTOC(pages); const TOC = getTOC(pages);
const markdown = _.reduce(TOC, (r, g1, idx1)=>{ const markdown = _.reduce(TOC, (r, g1, idx1)=>{
r.push(`- **[${idx1 + 1} ${g1.title}](#p${g1.page})**`) r.push(`- **[${idx1 + 1} ${g1.title}](#p${g1.page})**`);
if(g1.children.length){ if(g1.children.length){
_.each(g1.children, (g2, idx2) => { _.each(g1.children, (g2, idx2)=>{
r.push(` - [${idx1 + 1}.${idx2 + 1} ${g2.title}](#p${g2.page})`); r.push(` - [${idx1 + 1}.${idx2 + 1} ${g2.title}](#p${g2.page})`);
if(g2.children.length){ if(g2.children.length){
_.each(g2.children, (g3, idx3) => { _.each(g2.children, (g3, idx3)=>{
r.push(` - [${idx1 + 1}.${idx2 + 1}.${idx3 + 1} ${g3.title}](#p${g3.page})`); r.push(` - [${idx1 + 1}.${idx2 + 1}.${idx3 + 1} ${g3.title}](#p${g3.page})`);
}); });
} }
@@ -69,4 +69,4 @@ module.exports = function(brew){
##### Table Of Contents ##### Table Of Contents
${markdown} ${markdown}
</div>\n`; </div>\n`;
} };

View File

@@ -1,89 +1,89 @@
const React = require('react'); const React = require('react');
const _ = require('lodash'); const _ = require('lodash');
const cx = require('classnames'); const cx = require('classnames');
const CreateRouter = require('pico-router').createRouter; const CreateRouter = require('pico-router').createRouter;
const HomePage = require('./pages/homePage/homePage.jsx'); const HomePage = require('./pages/homePage/homePage.jsx');
const EditPage = require('./pages/editPage/editPage.jsx'); const EditPage = require('./pages/editPage/editPage.jsx');
const UserPage = require('./pages/userPage/userPage.jsx'); const UserPage = require('./pages/userPage/userPage.jsx');
const SharePage = require('./pages/sharePage/sharePage.jsx'); const SharePage = require('./pages/sharePage/sharePage.jsx');
const NewPage = require('./pages/newPage/newPage.jsx'); const NewPage = require('./pages/newPage/newPage.jsx');
const ErrorPage = require('./pages/errorPage/errorPage.jsx'); const ErrorPage = require('./pages/errorPage/errorPage.jsx');
const PrintPage = require('./pages/printPage/printPage.jsx'); const PrintPage = require('./pages/printPage/printPage.jsx');
let Router; let Router;
const Homebrew = React.createClass({ const Homebrew = React.createClass({
getDefaultProps: function() { getDefaultProps : function() {
return { return {
url : '', url : '',
welcomeText : '', welcomeText : '',
changelog : '', changelog : '',
version : '0.0.0', version : '0.0.0',
account : null, account : null,
brew : { brew : {
title : '', title : '',
text : '', text : '',
shareId : null, shareId : null,
editId : null, editId : null,
createdAt : null, createdAt : null,
updatedAt : null, updatedAt : null,
} }
}; };
}, },
componentWillMount: function() { componentWillMount : function() {
global.account = this.props.account; global.account = this.props.account;
global.version = this.props.version; global.version = this.props.version;
Router = CreateRouter({ Router = CreateRouter({
'/edit/:id' : (args) => { '/edit/:id' : (args)=>{
if(!this.props.brew.editId){ if(!this.props.brew.editId){
return <ErrorPage errorId={args.id}/> return <ErrorPage errorId={args.id}/>;
} }
return <EditPage return <EditPage
id={args.id} id={args.id}
brew={this.props.brew} /> brew={this.props.brew} />;
}, },
'/share/:id' : (args) => { '/share/:id' : (args)=>{
if(!this.props.brew.shareId){ if(!this.props.brew.shareId){
return <ErrorPage errorId={args.id}/> return <ErrorPage errorId={args.id}/>;
} }
return <SharePage return <SharePage
id={args.id} id={args.id}
brew={this.props.brew} /> brew={this.props.brew} />;
}, },
'/user/:username' : (args) => { '/user/:username' : (args)=>{
return <UserPage return <UserPage
username={args.username} username={args.username}
brews={this.props.brews} brews={this.props.brews}
/> />;
}, },
'/print/:id' : (args, query) => { '/print/:id' : (args, query)=>{
return <PrintPage brew={this.props.brew} query={query}/>; return <PrintPage brew={this.props.brew} query={query}/>;
}, },
'/print' : (args, query) => { '/print' : (args, query)=>{
return <PrintPage query={query}/>; return <PrintPage query={query}/>;
}, },
'/new' : (args) => { '/new' : (args)=>{
return <NewPage /> return <NewPage />;
}, },
'/changelog' : (args) => { '/changelog' : (args)=>{
return <SharePage return <SharePage
brew={{title : 'Changelog', text : this.props.changelog}} /> brew={{ title: 'Changelog', text: this.props.changelog }} />;
}, },
'*' : <HomePage '*' : <HomePage
welcomeText={this.props.welcomeText} />, welcomeText={this.props.welcomeText} />,
}); });
}, },
render : function(){ render : function(){
return <div className='homebrew'> return <div className='homebrew'>
<Router initialUrl={this.props.url}/> <Router initialUrl={this.props.url}/>
</div> </div>;
} }
}); });
module.exports = Homebrew; module.exports = Homebrew;

View File

@@ -1,17 +1,17 @@
const React = require('react'); const React = require('react');
const Nav = require('naturalcrit/nav/nav.jsx'); const Nav = require('naturalcrit/nav/nav.jsx');
module.exports = function(props){ module.exports = function(props){
if(global.account){ if(global.account){
return <Nav.item href={`/user/${global.account.username}`} color='yellow' icon='fa-user'> return <Nav.item href={`/user/${global.account.username}`} color='yellow' icon='fa-user'>
{global.account.username} {global.account.username}
</Nav.item> </Nav.item>;
} }
let url = ''; let url = '';
if(typeof window !== 'undefined'){ if(typeof window !== 'undefined'){
url = window.location.href url = window.location.href;
} }
return <Nav.item href={`http://naturalcrit.com/login?redirect=${url}`} color='teal' icon='fa-sign-in'> return <Nav.item href={`http://naturalcrit.com/login?redirect=${url}`} color='teal' icon='fa-sign-in'>
login login
</Nav.item> </Nav.item>;
}; };

View File

@@ -1,33 +1,33 @@
var React = require('react'); const React = require('react');
var _ = require('lodash'); const _ = require('lodash');
var cx = require('classnames'); const cx = require('classnames');
var Nav = require('naturalcrit/nav/nav.jsx'); const Nav = require('naturalcrit/nav/nav.jsx');
const MAX_TITLE_LENGTH = 50; const MAX_TITLE_LENGTH = 50;
var EditTitle = React.createClass({ const EditTitle = React.createClass({
getDefaultProps: function() { getDefaultProps : function() {
return { return {
title : '', title : '',
onChange : function(){} onChange : function(){}
}; };
}, },
handleChange : function(e){ handleChange : function(e){
if(e.target.value.length > MAX_TITLE_LENGTH) return; if(e.target.value.length > MAX_TITLE_LENGTH) return;
this.props.onChange(e.target.value); this.props.onChange(e.target.value);
}, },
render : function(){ render : function(){
return <Nav.item className='editTitle'> return <Nav.item className='editTitle'>
<input placeholder='Brew Title' type='text' value={this.props.title} onChange={this.handleChange} /> <input placeholder='Brew Title' type='text' value={this.props.title} onChange={this.handleChange} />
<div className={cx('charCount', {'max' : this.props.title.length >= MAX_TITLE_LENGTH})}> <div className={cx('charCount', { 'max': this.props.title.length >= MAX_TITLE_LENGTH })}>
{this.props.title.length}/{MAX_TITLE_LENGTH} {this.props.title.length}/{MAX_TITLE_LENGTH}
</div> </div>
</Nav.item> </Nav.item>;
}, },
}); });
module.exports = EditTitle; module.exports = EditTitle;

View File

@@ -1,8 +1,8 @@
var React = require('react'); const React = require('react');
var Nav = require('naturalcrit/nav/nav.jsx'); const Nav = require('naturalcrit/nav/nav.jsx');
module.exports = function(props){ module.exports = function(props){
return <Nav.item newTab={true} href='https://www.reddit.com/r/homebrewery/submit?selftext=true&title=[Issue]' color='red' icon='fa-bug'> return <Nav.item newTab={true} href='https://www.reddit.com/r/homebrewery/submit?selftext=true&title=[Issue]' color='red' icon='fa-bug'>
report issue report issue
</Nav.item> </Nav.item>;
}; };

View File

@@ -1,49 +1,49 @@
const React = require('react'); const React = require('react');
const _ = require('lodash'); const _ = require('lodash');
const Nav = require('naturalcrit/nav/nav.jsx'); const Nav = require('naturalcrit/nav/nav.jsx');
const Navbar = React.createClass({ const Navbar = React.createClass({
getInitialState: function() { getInitialState : function() {
return { return {
//showNonChromeWarning : false, //showNonChromeWarning : false,
ver : '0.0.0' ver : '0.0.0'
}; };
}, },
componentDidMount: function() { componentDidMount : function() {
//const isChrome = /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor); //const isChrome = /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor);
this.setState({ this.setState({
//showNonChromeWarning : !isChrome, //showNonChromeWarning : !isChrome,
ver : window.version ver : window.version
}) });
}, },
/* /*
renderChromeWarning : function(){ renderChromeWarning : function(){
if(!this.state.showNonChromeWarning) return; if(!this.state.showNonChromeWarning) return;
return <Nav.item className='warning' icon='fa-exclamation-triangle'> return <Nav.item className='warning' icon='fa-exclamation-triangle'>
Optimized for Chrome Optimized for Chrome
<div className='dropdown'> <div className='dropdown'>
If you are experiencing rendering issues, use Chrome instead If you are experiencing rendering issues, use Chrome instead
</div> </div>
</Nav.item> </Nav.item>
}, },
*/ */
render : function(){ render : function(){
return <Nav.base> return <Nav.base>
<Nav.section> <Nav.section>
<Nav.logo /> <Nav.logo />
<Nav.item href='/' className='homebrewLogo'> <Nav.item href='/' className='homebrewLogo'>
<div>The Homebrewery</div> <div>The Homebrewery</div>
</Nav.item> </Nav.item>
<Nav.item>{`v${this.state.ver}`}</Nav.item> <Nav.item>{`v${this.state.ver}`}</Nav.item>
{/*this.renderChromeWarning()*/} {/*this.renderChromeWarning()*/}
</Nav.section> </Nav.section>
{this.props.children} {this.props.children}
</Nav.base> </Nav.base>;
} }
}); });
module.exports = Navbar; module.exports = Navbar;

View File

@@ -1,13 +1,13 @@
var React = require('react'); const React = require('react');
var Nav = require('naturalcrit/nav/nav.jsx'); const Nav = require('naturalcrit/nav/nav.jsx');
module.exports = function(props){ module.exports = function(props){
return <Nav.item return <Nav.item
className='patreon' className='patreon'
newTab={true} newTab={true}
href='https://www.patreon.com/stolksdorf' href='https://www.patreon.com/stolksdorf'
color='green' color='green'
icon='fa-heart'> icon='fa-heart'>
help out help out
</Nav.item> </Nav.item>;
}; };

View File

@@ -1,8 +1,8 @@
var React = require('react'); const React = require('react');
var Nav = require('naturalcrit/nav/nav.jsx'); const Nav = require('naturalcrit/nav/nav.jsx');
module.exports = function(props){ module.exports = function(props){
return <Nav.item newTab={true} href={'/print/' + props.shareId +'?dialog=true'} color='purple' icon='fa-file-pdf-o'> return <Nav.item newTab={true} href={`/print/${props.shareId}?dialog=true`} color='purple' icon='fa-file-pdf-o'>
get PDF get PDF
</Nav.item> </Nav.item>;
}; };

View File

@@ -1,199 +1,199 @@
var React = require('react'); const React = require('react');
var _ = require('lodash'); const _ = require('lodash');
var cx = require('classnames'); const cx = require('classnames');
var Moment = require('moment'); const Moment = require('moment');
var Nav = require('naturalcrit/nav/nav.jsx'); const Nav = require('naturalcrit/nav/nav.jsx');
const VIEW_KEY = 'homebrewery-recently-viewed'; const VIEW_KEY = 'homebrewery-recently-viewed';
const EDIT_KEY = 'homebrewery-recently-edited'; const EDIT_KEY = 'homebrewery-recently-edited';
var BaseItem = React.createClass({ const BaseItem = React.createClass({
getDefaultProps: function() { getDefaultProps : function() {
return { return {
storageKey : '', storageKey : '',
text : '', text : '',
currentBrew:{ currentBrew : {
title : '', title : '',
id : '', id : '',
url : '' url : ''
} }
}; };
}, },
getInitialState: function() { getInitialState : function() {
return { return {
showDropdown: false, showDropdown : false,
brews : [] brews : []
}; };
}, },
componentDidMount: function() { componentDidMount : function() {
var brews = JSON.parse(localStorage.getItem(this.props.storageKey) || '[]'); let brews = JSON.parse(localStorage.getItem(this.props.storageKey) || '[]');
brews = _.filter(brews, (brew)=>{ brews = _.filter(brews, (brew)=>{
return brew.id !== this.props.currentBrew.id; return brew.id !== this.props.currentBrew.id;
}); });
if(this.props.currentBrew.id){ if(this.props.currentBrew.id){
brews.unshift({ brews.unshift({
id : this.props.currentBrew.id, id : this.props.currentBrew.id,
url : this.props.currentBrew.url, url : this.props.currentBrew.url,
title : this.props.currentBrew.title, title : this.props.currentBrew.title,
ts : Date.now() ts : Date.now()
}); });
} }
brews = _.slice(brews, 0, 8); brews = _.slice(brews, 0, 8);
localStorage.setItem(this.props.storageKey, JSON.stringify(brews)); localStorage.setItem(this.props.storageKey, JSON.stringify(brews));
this.setState({ this.setState({
brews : brews brews : brews
}); });
}, },
handleDropdown : function(show){ handleDropdown : function(show){
this.setState({ this.setState({
showDropdown : show showDropdown : show
}) });
}, },
renderDropdown : function(){ renderDropdown : function(){
if(!this.state.showDropdown) return null; if(!this.state.showDropdown) return null;
var items = _.map(this.state.brews, (brew)=>{ const items = _.map(this.state.brews, (brew)=>{
return <a href={brew.url} className='item' key={brew.id} target='_blank'> return <a href={brew.url} className='item' key={brew.id} target='_blank'>
<span className='title'>{brew.title}</span> <span className='title'>{brew.title}</span>
<span className='time'>{Moment(brew.ts).fromNow()}</span> <span className='time'>{Moment(brew.ts).fromNow()}</span>
</a> </a>;
}); });
return <div className='dropdown'>{items}</div> return <div className='dropdown'>{items}</div>;
}, },
render : function(){ render : function(){
return <Nav.item icon='fa-clock-o' color='grey' className='recent' return <Nav.item icon='fa-clock-o' color='grey' className='recent'
onMouseEnter={this.handleDropdown.bind(null, true)} onMouseEnter={()=>this.handleDropdown(true)}
onMouseLeave={this.handleDropdown.bind(null, false)}> onMouseLeave={()=>this.handleDropdown(false)}>
{this.props.text} {this.props.text}
{this.renderDropdown()} {this.renderDropdown()}
</Nav.item> </Nav.item>;
}, },
}); });
module.exports = { module.exports = {
viewed : React.createClass({ viewed : React.createClass({
getDefaultProps: function() { getDefaultProps : function() {
return { return {
brew : { brew : {
title : '', title : '',
shareId : '' shareId : ''
} }
}; };
}, },
render : function(){ render : function(){
return <BaseItem text='recently viewed' storageKey={VIEW_KEY} return <BaseItem text='recently viewed' storageKey={VIEW_KEY}
currentBrew={{ currentBrew={{
id : this.props.brew.shareId, id : this.props.brew.shareId,
title : this.props.brew.title, title : this.props.brew.title,
url : `/share/${this.props.brew.shareId}` url : `/share/${this.props.brew.shareId}`
}} }}
/> />;
}, },
}), }),
edited : React.createClass({ edited : React.createClass({
getDefaultProps: function() { getDefaultProps : function() {
return { return {
brew : { brew : {
title : '', title : '',
editId : '' editId : ''
} }
}; };
}, },
render : function(){ render : function(){
return <BaseItem text='recently edited' storageKey={EDIT_KEY} return <BaseItem text='recently edited' storageKey={EDIT_KEY}
currentBrew={{ currentBrew={{
id : this.props.brew.editId, id : this.props.brew.editId,
title : this.props.brew.title, title : this.props.brew.title,
url : `/edit/${this.props.brew.editId}` url : `/edit/${this.props.brew.editId}`
}} }}
/> />;
}, },
}), }),
both : React.createClass({ both : React.createClass({
getDefaultProps: function() { getDefaultProps : function() {
return { return {
errorId : null errorId : null
}; };
}, },
getInitialState: function() { getInitialState : function() {
return { return {
showDropdown: false, showDropdown : false,
edit : [], edit : [],
view : [] view : []
}; };
}, },
componentDidMount: function() { componentDidMount : function() {
var edited = JSON.parse(localStorage.getItem(EDIT_KEY) || '[]'); let edited = JSON.parse(localStorage.getItem(EDIT_KEY) || '[]');
var viewed = JSON.parse(localStorage.getItem(VIEW_KEY) || '[]'); let viewed = JSON.parse(localStorage.getItem(VIEW_KEY) || '[]');
if(this.props.errorId){ if(this.props.errorId){
edited = _.filter(edited, (edit) => { edited = _.filter(edited, (edit)=>{
return edit.id !== this.props.errorId; return edit.id !== this.props.errorId;
}); });
viewed = _.filter(viewed, (view) => { viewed = _.filter(viewed, (view)=>{
return view.id !== this.props.errorId; return view.id !== this.props.errorId;
}); });
localStorage.setItem(EDIT_KEY, JSON.stringify(edited)); localStorage.setItem(EDIT_KEY, JSON.stringify(edited));
localStorage.setItem(VIEW_KEY, JSON.stringify(viewed)); localStorage.setItem(VIEW_KEY, JSON.stringify(viewed));
} }
this.setState({ this.setState({
edit : edited, edit : edited,
view : viewed view : viewed
}); });
}, },
handleDropdown : function(show){ handleDropdown : function(show){
this.setState({ this.setState({
showDropdown : show showDropdown : show
}) });
}, },
renderDropdown : function(){ renderDropdown : function(){
if(!this.state.showDropdown) return null; if(!this.state.showDropdown) return null;
var makeItems = (brews) => { const makeItems = (brews)=>{
return _.map(brews, (brew)=>{ return _.map(brews, (brew)=>{
return <a href={brew.url} className='item' key={brew.id} target='_blank'> return <a href={brew.url} className='item' key={brew.id} target='_blank'>
<span className='title'>{brew.title}</span> <span className='title'>{brew.title}</span>
<span className='time'>{Moment(brew.ts).fromNow()}</span> <span className='time'>{Moment(brew.ts).fromNow()}</span>
</a> </a>;
}); });
}; };
return <div className='dropdown'> return <div className='dropdown'>
<h4>edited</h4> <h4>edited</h4>
{makeItems(this.state.edit)} {makeItems(this.state.edit)}
<h4>viewed</h4> <h4>viewed</h4>
{makeItems(this.state.view)} {makeItems(this.state.view)}
</div> </div>;
}, },
render : function(){ render : function(){
return <Nav.item icon='fa-clock-o' color='grey' className='recent' return <Nav.item icon='fa-clock-o' color='grey' className='recent'
onMouseEnter={this.handleDropdown.bind(null, true)} onMouseEnter={()=>this.handleDropdown(true)}
onMouseLeave={this.handleDropdown.bind(null, false)}> onMouseLeave={()=>this.handleDropdown(false)}>
Recent brews Recent brews
{this.renderDropdown()} {this.renderDropdown()}
</Nav.item> </Nav.item>;
} }
}) })
} };

View File

@@ -1,51 +1,51 @@
var React = require('react'); const React = require('react');
var _ = require('lodash'); const _ = require('lodash');
var cx = require('classnames'); const cx = require('classnames');
//var striptags = require('striptags'); //var striptags = require('striptags');
var Nav = require('naturalcrit/nav/nav.jsx'); const Nav = require('naturalcrit/nav/nav.jsx');
const MAX_URL_SIZE = 2083; const MAX_URL_SIZE = 2083;
const MAIN_URL = "https://www.reddit.com/r/UnearthedArcana/submit?selftext=true" const MAIN_URL = 'https://www.reddit.com/r/UnearthedArcana/submit?selftext=true';
var RedditShare = React.createClass({ const RedditShare = React.createClass({
getDefaultProps: function() { getDefaultProps : function() {
return { return {
brew : { brew : {
title : '', title : '',
sharedId : '', sharedId : '',
text : '' text : ''
} }
}; };
}, },
getText : function(){ getText : function(){
}, },
handleClick : function(){ handleClick : function(){
var url = [ const url = [
MAIN_URL, MAIN_URL,
'title=' + encodeURIComponent(this.props.brew.title ? this.props.brew.title : 'Check out my brew!'), `title=${encodeURIComponent(this.props.brew.title ? this.props.brew.title : 'Check out my brew!')}`,
'text=' + encodeURIComponent(this.props.brew.text) `text=${encodeURIComponent(this.props.brew.text)}`
].join('&'); ].join('&');
window.open(url, '_blank'); window.open(url, '_blank');
}, },
render : function(){ render : function(){
return <Nav.item icon='fa-reddit-alien' color='red' onClick={this.handleClick}> return <Nav.item icon='fa-reddit-alien' color='red' onClick={this.handleClick}>
share on reddit share on reddit
</Nav.item> </Nav.item>;
}, },
}); });
module.exports = RedditShare; module.exports = RedditShare;

View File

@@ -1,230 +1,230 @@
const React = require('react'); const React = require('react');
const _ = require('lodash'); const _ = require('lodash');
const cx = require('classnames'); const cx = require('classnames');
const request = require("superagent"); const request = require('superagent');
const Nav = require('naturalcrit/nav/nav.jsx'); const Nav = require('naturalcrit/nav/nav.jsx');
const Navbar = require('../../navbar/navbar.jsx'); const Navbar = require('../../navbar/navbar.jsx');
const ReportIssue = require('../../navbar/issue.navitem.jsx'); const ReportIssue = require('../../navbar/issue.navitem.jsx');
const PrintLink = require('../../navbar/print.navitem.jsx'); const PrintLink = require('../../navbar/print.navitem.jsx');
const Account = require('../../navbar/account.navitem.jsx'); const Account = require('../../navbar/account.navitem.jsx');
//const RecentlyEdited = require('../../navbar/recent.navitem.jsx').edited; //const RecentlyEdited = require('../../navbar/recent.navitem.jsx').edited;
const SplitPane = require('naturalcrit/splitPane/splitPane.jsx'); const SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
const Editor = require('../../editor/editor.jsx'); const Editor = require('../../editor/editor.jsx');
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx'); const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
const Markdown = require('naturalcrit/markdown.js'); const Markdown = require('naturalcrit/markdown.js');
const SAVE_TIMEOUT = 3000; const SAVE_TIMEOUT = 3000;
const EditPage = React.createClass({ const EditPage = React.createClass({
getDefaultProps: function() { getDefaultProps : function() {
return { return {
brew : { brew : {
text : '', text : '',
shareId : null, shareId : null,
editId : null, editId : null,
createdAt : null, createdAt : null,
updatedAt : null, updatedAt : null,
title : '', title : '',
description : '', description : '',
tags : '', tags : '',
published : false, published : false,
authors : [], authors : [],
systems : [] systems : []
} }
}; };
}, },
getInitialState: function() { getInitialState : function() {
return { return {
brew : this.props.brew, brew : this.props.brew,
isSaving : false, isSaving : false,
isPending : false, isPending : false,
errors : null, errors : null,
htmlErrors : Markdown.validate(this.props.brew.text), htmlErrors : Markdown.validate(this.props.brew.text),
lastUpdated : this.props.brew.updatedAt lastUpdated : this.props.brew.updatedAt
}; };
}, },
savedBrew : null, savedBrew : null,
componentDidMount: function(){ componentDidMount : function(){
this.trySave(); this.trySave();
window.onbeforeunload = ()=>{ window.onbeforeunload = ()=>{
if(this.state.isSaving || this.state.isPending){ if(this.state.isSaving || this.state.isPending){
return 'You have unsaved changes!'; return 'You have unsaved changes!';
} }
}; };
this.setState({ this.setState({
htmlErrors : Markdown.validate(this.state.brew.text) htmlErrors : Markdown.validate(this.state.brew.text)
}) });
document.addEventListener('keydown', this.handleControlKeys); document.addEventListener('keydown', this.handleControlKeys);
}, },
componentWillUnmount: function() { componentWillUnmount : function() {
window.onbeforeunload = function(){}; window.onbeforeunload = function(){};
document.removeEventListener('keydown', this.handleControlKeys); document.removeEventListener('keydown', this.handleControlKeys);
}, },
handleControlKeys : function(e){ handleControlKeys : function(e){
if(!(e.ctrlKey || e.metaKey)) return; if(!(e.ctrlKey || e.metaKey)) return;
const S_KEY = 83; const S_KEY = 83;
const P_KEY = 80; const P_KEY = 80;
if(e.keyCode == S_KEY) this.save(); if(e.keyCode == S_KEY) this.save();
if(e.keyCode == P_KEY) window.open(`/print/${this.props.brew.shareId}?dialog=true`, '_blank').focus(); if(e.keyCode == P_KEY) window.open(`/print/${this.props.brew.shareId}?dialog=true`, '_blank').focus();
if(e.keyCode == P_KEY || e.keyCode == S_KEY){ if(e.keyCode == P_KEY || e.keyCode == S_KEY){
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
} }
}, },
handleSplitMove : function(){ handleSplitMove : function(){
this.refs.editor.update(); this.refs.editor.update();
}, },
handleMetadataChange : function(metadata){ handleMetadataChange : function(metadata){
this.setState({ this.setState({
brew : _.merge({}, this.state.brew, metadata), brew : _.merge({}, this.state.brew, metadata),
isPending : true, isPending : true,
}, ()=>{ }, ()=>{
this.trySave(); this.trySave();
}); });
}, },
handleTextChange : function(text){ handleTextChange : function(text){
//If there are errors, run the validator on everychange to give quick feedback //If there are errors, run the validator on everychange to give quick feedback
var htmlErrors = this.state.htmlErrors; let htmlErrors = this.state.htmlErrors;
if(htmlErrors.length) htmlErrors = Markdown.validate(text); if(htmlErrors.length) htmlErrors = Markdown.validate(text);
this.setState({ this.setState({
brew : _.merge({}, this.state.brew, {text : text}), brew : _.merge({}, this.state.brew, { text: text }),
isPending : true, isPending : true,
htmlErrors : htmlErrors htmlErrors : htmlErrors
}); });
this.trySave(); this.trySave();
}, },
hasChanges : function(){ hasChanges : function(){
if(this.savedBrew){ if(this.savedBrew){
return !_.isEqual(this.state.brew, this.savedBrew) return !_.isEqual(this.state.brew, this.savedBrew);
}else{ } else {
return !_.isEqual(this.state.brew, this.props.brew) return !_.isEqual(this.state.brew, this.props.brew);
} }
return false; return false;
}, },
trySave : function(){ trySave : function(){
if(!this.debounceSave) this.debounceSave = _.debounce(this.save, SAVE_TIMEOUT); if(!this.debounceSave) this.debounceSave = _.debounce(this.save, SAVE_TIMEOUT);
if(this.hasChanges()){ if(this.hasChanges()){
this.debounceSave(); this.debounceSave();
}else{ } else {
this.debounceSave.cancel(); this.debounceSave.cancel();
} }
}, },
save : function(){ save : function(){
if(this.debounceSave && this.debounceSave.cancel) this.debounceSave.cancel(); if(this.debounceSave && this.debounceSave.cancel) this.debounceSave.cancel();
this.setState({ this.setState({
isSaving : true, isSaving : true,
errors : null, errors : null,
htmlErrors : Markdown.validate(this.state.brew.text) htmlErrors : Markdown.validate(this.state.brew.text)
}); });
request request
.put('/api/update/' + this.props.brew.editId) .put(`/api/update/${this.props.brew.editId}`)
.send(this.state.brew) .send(this.state.brew)
.end((err, res) => { .end((err, res)=>{
if(err){ if(err){
this.setState({ this.setState({
errors : err, errors : err,
}) });
}else{ } else {
this.savedBrew = res.body; this.savedBrew = res.body;
this.setState({ this.setState({
isPending : false, isPending : false,
isSaving : false, isSaving : false,
lastUpdated : res.body.updatedAt lastUpdated : res.body.updatedAt
}) });
} }
}) });
}, },
renderSaveButton : function(){ renderSaveButton : function(){
if(this.state.errors){ if(this.state.errors){
var errMsg = ''; let errMsg = '';
try{ try {
errMsg += this.state.errors.toString() + '\n\n'; errMsg += `${this.state.errors.toString()}\n\n`;
errMsg += '```\n' + JSON.stringify(this.state.errors.response.error, null, ' ') + '\n```'; errMsg += `\`\`\`\n${JSON.stringify(this.state.errors.response.error, null, ' ')}\n\`\`\``;
}catch(e){} } catch (e){}
return <Nav.item className='save error' icon="fa-warning"> return <Nav.item className='save error' icon='fa-warning'>
Oops! Oops!
<div className='errorContainer'> <div className='errorContainer'>
Looks like there was a problem saving. <br /> Looks like there was a problem saving. <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>.
</div> </div>
</Nav.item> </Nav.item>;
} }
if(this.state.isSaving){ if(this.state.isSaving){
return <Nav.item className='save' icon="fa-spinner fa-spin">saving...</Nav.item> return <Nav.item className='save' icon='fa-spinner fa-spin'>saving...</Nav.item>;
} }
if(this.state.isPending && this.hasChanges()){ if(this.state.isPending && this.hasChanges()){
return <Nav.item className='save' onClick={this.save} color='blue' icon='fa-save'>Save Now</Nav.item> return <Nav.item className='save' onClick={this.save} color='blue' icon='fa-save'>Save Now</Nav.item>;
} }
if(!this.state.isPending && !this.state.isSaving){ if(!this.state.isPending && !this.state.isSaving){
return <Nav.item className='save saved'>saved.</Nav.item> return <Nav.item className='save saved'>saved.</Nav.item>;
} }
}, },
renderNavbar : function(){ renderNavbar : function(){
return <Navbar> return <Navbar>
<Nav.section> <Nav.section>
<Nav.item className='brewTitle'>{this.state.brew.title}</Nav.item> <Nav.item className='brewTitle'>{this.state.brew.title}</Nav.item>
</Nav.section> </Nav.section>
<Nav.section> <Nav.section>
{this.renderSaveButton()} {this.renderSaveButton()}
{/*<RecentlyEdited brew={this.props.brew} />*/} {/*<RecentlyEdited brew={this.props.brew} />*/}
<ReportIssue /> <ReportIssue />
<Nav.item newTab={true} href={'/share/' + this.props.brew.shareId} color='teal' icon='fa-share-alt'> <Nav.item newTab={true} href={`/share/${this.props.brew.shareId}`} color='teal' icon='fa-share-alt'>
Share Share
</Nav.item> </Nav.item>
<PrintLink shareId={this.props.brew.shareId} /> <PrintLink shareId={this.props.brew.shareId} />
<Account /> <Account />
</Nav.section> </Nav.section>
</Navbar> </Navbar>;
}, },
render : function(){ render : function(){
return <div className='editPage page'> return <div className='editPage page'>
{this.renderNavbar()} {this.renderNavbar()}
<div className='content'> <div className='content'>
<SplitPane onDragFinish={this.handleSplitMove} ref='pane'> <SplitPane onDragFinish={this.handleSplitMove} ref='pane'>
<Editor <Editor
ref='editor' ref='editor'
value={this.state.brew.text} value={this.state.brew.text}
onChange={this.handleTextChange} onChange={this.handleTextChange}
metadata={this.state.brew} metadata={this.state.brew}
onMetadataChange={this.handleMetadataChange} onMetadataChange={this.handleMetadataChange}
/> />
<BrewRenderer text={this.state.brew.text} errors={this.state.htmlErrors} /> <BrewRenderer text={this.state.brew.text} errors={this.state.htmlErrors} />
</SplitPane> </SplitPane>
</div> </div>
</div> </div>;
} }
}); });
module.exports = EditPage; module.exports = EditPage;

View File

@@ -1,20 +1,20 @@
var React = require('react'); const React = require('react');
var _ = require('lodash'); const _ = require('lodash');
var cx = require('classnames'); const cx = require('classnames');
var Nav = require('naturalcrit/nav/nav.jsx'); const Nav = require('naturalcrit/nav/nav.jsx');
var Navbar = require('../../navbar/navbar.jsx'); const Navbar = require('../../navbar/navbar.jsx');
var PatreonNavItem = require('../../navbar/patreon.navitem.jsx'); const PatreonNavItem = require('../../navbar/patreon.navitem.jsx');
var IssueNavItem = require('../../navbar/issue.navitem.jsx'); const IssueNavItem = require('../../navbar/issue.navitem.jsx');
var RecentNavItem = require('../../navbar/recent.navitem.jsx'); const RecentNavItem = require('../../navbar/recent.navitem.jsx');
var BrewRenderer = require('../../brewRenderer/brewRenderer.jsx'); const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
var ErrorPage = React.createClass({ const ErrorPage = React.createClass({
getDefaultProps: function() { getDefaultProps : function() {
return { return {
ver : '0.0.0', ver : '0.0.0',
errorId: '' errorId : ''
}; };
}, },
@@ -39,7 +39,7 @@ var ErrorPage = React.createClass({
<div className='content'> <div className='content'>
<BrewRenderer text={this.text} /> <BrewRenderer text={this.text} />
</div> </div>
</div> </div>;
} }
}); });

View File

@@ -4,7 +4,7 @@ module.exports = function(shareId){
return function(event){ return function(event){
event = event || window.event; event = event || window.event;
if((event.ctrlKey || event.metaKey) && event.keyCode == 80){ if((event.ctrlKey || event.metaKey) && event.keyCode == 80){
var win = window.open(`/homebrew/print/${shareId}?dialog=true`, '_blank'); const win = window.open(`/homebrew/print/${shareId}?dialog=true`, '_blank');
win.focus(); win.focus();
event.preventDefault(); event.preventDefault();
} }

View File

@@ -1,92 +1,92 @@
const React = require('react'); const React = require('react');
const _ = require('lodash'); const _ = require('lodash');
const cx = require('classnames'); const cx = require('classnames');
const request = require("superagent"); const request = require('superagent');
const Nav = require('naturalcrit/nav/nav.jsx'); const Nav = require('naturalcrit/nav/nav.jsx');
const Navbar = require('../../navbar/navbar.jsx'); const Navbar = require('../../navbar/navbar.jsx');
const PatreonNavItem = require('../../navbar/patreon.navitem.jsx'); const PatreonNavItem = require('../../navbar/patreon.navitem.jsx');
const IssueNavItem = require('../../navbar/issue.navitem.jsx'); const IssueNavItem = require('../../navbar/issue.navitem.jsx');
const RecentNavItem = require('../../navbar/recent.navitem.jsx'); const RecentNavItem = require('../../navbar/recent.navitem.jsx');
const AccountNavItem = require('../../navbar/account.navitem.jsx'); const AccountNavItem = require('../../navbar/account.navitem.jsx');
const SplitPane = require('naturalcrit/splitPane/splitPane.jsx'); const SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
const Editor = require('../../editor/editor.jsx'); const Editor = require('../../editor/editor.jsx');
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx'); const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
const HomePage = React.createClass({ const HomePage = React.createClass({
getDefaultProps: function() { getDefaultProps : function() {
return { return {
welcomeText : '', welcomeText : '',
ver : '0.0.0' ver : '0.0.0'
}; };
}, },
getInitialState: function() { getInitialState : function() {
return { return {
text: this.props.welcomeText text : this.props.welcomeText
}; };
}, },
handleSave : function(){ handleSave : function(){
request.post('/api') request.post('/api')
.send({ .send({
text : this.state.text text : this.state.text
}) })
.end((err, res)=>{ .end((err, res)=>{
if(err) return; if(err) return;
var brew = res.body; const brew = res.body;
window.location = '/edit/' + brew.editId; window.location = `/edit/${brew.editId}`;
}); });
}, },
handleSplitMove : function(){ handleSplitMove : function(){
this.refs.editor.update(); this.refs.editor.update();
}, },
handleTextChange : function(text){ handleTextChange : function(text){
this.setState({ this.setState({
text : text text : text
}); });
}, },
renderNavbar : function(){ renderNavbar : function(){
return <Navbar ver={this.props.ver}> return <Navbar ver={this.props.ver}>
<Nav.section> <Nav.section>
<PatreonNavItem /> <PatreonNavItem />
<IssueNavItem /> <IssueNavItem />
<Nav.item newTab={true} href='/changelog' color='purple' icon='fa-file-text-o'> <Nav.item newTab={true} href='/changelog' color='purple' icon='fa-file-text-o'>
Changelog Changelog
</Nav.item> </Nav.item>
<RecentNavItem.both /> <RecentNavItem.both />
<AccountNavItem /> <AccountNavItem />
{/*} {/*}
<Nav.item href='/new' color='green' icon='fa-external-link'> <Nav.item href='/new' color='green' icon='fa-external-link'>
New Brew New Brew
</Nav.item> </Nav.item>
*/} */}
</Nav.section> </Nav.section>
</Navbar> </Navbar>;
}, },
render : function(){ render : function(){
return <div className='homePage page'> return <div className='homePage page'>
{this.renderNavbar()} {this.renderNavbar()}
<div className='content'> <div className='content'>
<SplitPane onDragFinish={this.handleSplitMove} ref='pane'> <SplitPane onDragFinish={this.handleSplitMove} ref='pane'>
<Editor value={this.state.text} onChange={this.handleTextChange} ref='editor'/> <Editor value={this.state.text} onChange={this.handleTextChange} ref='editor'/>
<BrewRenderer text={this.state.text} /> <BrewRenderer text={this.state.text} />
</SplitPane> </SplitPane>
</div> </div>
<div className={cx('floatingSaveButton', {show : this.props.welcomeText != this.state.text})} onClick={this.handleSave}> <div className={cx('floatingSaveButton', { show: this.props.welcomeText != this.state.text })} onClick={this.handleSave}>
Save current <i className='fa fa-save' /> Save current <i className='fa fa-save' />
</div> </div>
<a href='/new' className='floatingNewButton'> <a href='/new' className='floatingNewButton'>
Create your own <i className='fa fa-magic' /> Create your own <i className='fa fa-magic' />
</a> </a>
</div> </div>;
} }
}); });
module.exports = HomePage; module.exports = HomePage;

View File

@@ -1,161 +1,161 @@
const React = require('react'); const React = require('react');
const _ = require('lodash'); const _ = require('lodash');
const cx = require('classnames'); const cx = require('classnames');
const request = require("superagent"); const request = require('superagent');
const Markdown = require('naturalcrit/markdown.js'); const Markdown = require('naturalcrit/markdown.js');
const Nav = require('naturalcrit/nav/nav.jsx'); const Nav = require('naturalcrit/nav/nav.jsx');
const Navbar = require('../../navbar/navbar.jsx'); const Navbar = require('../../navbar/navbar.jsx');
const AccountNavItem = require('../../navbar/account.navitem.jsx'); const AccountNavItem = require('../../navbar/account.navitem.jsx');
const IssueNavItem = require('../../navbar/issue.navitem.jsx'); const IssueNavItem = require('../../navbar/issue.navitem.jsx');
const SplitPane = require('naturalcrit/splitPane/splitPane.jsx'); const SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
const Editor = require('../../editor/editor.jsx'); const Editor = require('../../editor/editor.jsx');
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx'); const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
const KEY = 'homebrewery-new'; const KEY = 'homebrewery-new';
const NewPage = React.createClass({ const NewPage = React.createClass({
getInitialState: function() { getInitialState : function() {
return { return {
metadata : { metadata : {
title : '', title : '',
description : '', description : '',
tags : '', tags : '',
published : false, published : false,
authors : [], authors : [],
systems : [] systems : []
}, },
text: '', text : '',
isSaving : false, isSaving : false,
errors : [] errors : []
}; };
}, },
componentDidMount: function() { componentDidMount : function() {
const storage = localStorage.getItem(KEY); const storage = localStorage.getItem(KEY);
if(storage){ if(storage){
this.setState({ this.setState({
text : storage text : storage
}) });
} }
document.addEventListener('keydown', this.handleControlKeys); document.addEventListener('keydown', this.handleControlKeys);
}, },
componentWillUnmount: function() { componentWillUnmount : function() {
document.removeEventListener('keydown', this.handleControlKeys); document.removeEventListener('keydown', this.handleControlKeys);
}, },
handleControlKeys : function(e){ handleControlKeys : function(e){
if(!(e.ctrlKey || e.metaKey)) return; if(!(e.ctrlKey || e.metaKey)) return;
const S_KEY = 83; const S_KEY = 83;
const P_KEY = 80; const P_KEY = 80;
if(e.keyCode == S_KEY) this.save(); if(e.keyCode == S_KEY) this.save();
if(e.keyCode == P_KEY) this.print(); if(e.keyCode == P_KEY) this.print();
if(e.keyCode == P_KEY || e.keyCode == S_KEY){ if(e.keyCode == P_KEY || e.keyCode == S_KEY){
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
} }
}, },
handleSplitMove : function(){ handleSplitMove : function(){
this.refs.editor.update(); this.refs.editor.update();
}, },
handleMetadataChange : function(metadata){ handleMetadataChange : function(metadata){
this.setState({ this.setState({
metadata : _.merge({}, this.state.metadata, metadata) metadata : _.merge({}, this.state.metadata, metadata)
}); });
}, },
handleTextChange : function(text){ handleTextChange : function(text){
this.setState({ this.setState({
text : text, text : text,
errors : Markdown.validate(text) errors : Markdown.validate(text)
}); });
localStorage.setItem(KEY, text); localStorage.setItem(KEY, text);
}, },
save : function(){ save : function(){
this.setState({ this.setState({
isSaving : true isSaving : true
}); });
request.post('/api') request.post('/api')
.send(_.merge({}, this.state.metadata, { .send(_.merge({}, this.state.metadata, {
text : this.state.text text : this.state.text
})) }))
.end((err, res)=>{ .end((err, res)=>{
if(err){ if(err){
this.setState({ this.setState({
isSaving : false isSaving : false
}); });
return; return;
} }
window.onbeforeunload = function(){}; window.onbeforeunload = function(){};
const brew = res.body; const brew = res.body;
localStorage.removeItem(KEY); localStorage.removeItem(KEY);
window.location = '/edit/' + brew.editId; window.location = `/edit/${brew.editId}`;
}) });
}, },
renderSaveButton : function(){ renderSaveButton : function(){
if(this.state.isSaving){ if(this.state.isSaving){
return <Nav.item icon='fa-spinner fa-spin' className='saveButton'> return <Nav.item icon='fa-spinner fa-spin' className='saveButton'>
save... save...
</Nav.item> </Nav.item>;
}else{ } else {
return <Nav.item icon='fa-save' className='saveButton' onClick={this.save}> return <Nav.item icon='fa-save' className='saveButton' onClick={this.save}>
save save
</Nav.item> </Nav.item>;
} }
}, },
print : function(){ print : function(){
localStorage.setItem('print', this.state.text); localStorage.setItem('print', this.state.text);
window.open('/print?dialog=true&local=print','_blank'); window.open('/print?dialog=true&local=print', '_blank');
}, },
renderLocalPrintButton : function(){ renderLocalPrintButton : function(){
return <Nav.item color='purple' icon='fa-file-pdf-o' onClick={this.print}> return <Nav.item color='purple' icon='fa-file-pdf-o' onClick={this.print}>
get PDF get PDF
</Nav.item> </Nav.item>;
}, },
renderNavbar : function(){ renderNavbar : function(){
return <Navbar> return <Navbar>
<Nav.section> <Nav.section>
<Nav.item className='brewTitle'>{this.state.metadata.title}</Nav.item> <Nav.item className='brewTitle'>{this.state.metadata.title}</Nav.item>
</Nav.section> </Nav.section>
<Nav.section> <Nav.section>
{this.renderSaveButton()} {this.renderSaveButton()}
{this.renderLocalPrintButton()} {this.renderLocalPrintButton()}
<IssueNavItem /> <IssueNavItem />
<AccountNavItem /> <AccountNavItem />
</Nav.section> </Nav.section>
</Navbar> </Navbar>;
}, },
render : function(){ render : function(){
return <div className='newPage page'> return <div className='newPage page'>
{this.renderNavbar()} {this.renderNavbar()}
<div className='content'> <div className='content'>
<SplitPane onDragFinish={this.handleSplitMove} ref='pane'> <SplitPane onDragFinish={this.handleSplitMove} ref='pane'>
<Editor <Editor
ref='editor' ref='editor'
value={this.state.text} value={this.state.text}
onChange={this.handleTextChange} onChange={this.handleTextChange}
metadata={this.state.metadata} metadata={this.state.metadata}
onMetadataChange={this.handleMetadataChange} onMetadataChange={this.handleMetadataChange}
/> />
<BrewRenderer text={this.state.text} errors={this.state.errors} /> <BrewRenderer text={this.state.text} errors={this.state.errors} />
</SplitPane> </SplitPane>
</div> </div>
</div> </div>;
} }
}); });
module.exports = NewPage; module.exports = NewPage;

View File

@@ -4,35 +4,35 @@ const cx = require('classnames');
const Markdown = require('naturalcrit/markdown.js'); const Markdown = require('naturalcrit/markdown.js');
const PrintPage = React.createClass({ const PrintPage = React.createClass({
getDefaultProps: function() { getDefaultProps : function() {
return { return {
query : {}, query : {},
brew : { brew : {
text : '', text : '',
} }
}; };
}, },
getInitialState: function() { getInitialState : function() {
return { return {
brewText: this.props.brew.text brewText : this.props.brew.text
}; };
}, },
componentDidMount: function() { componentDidMount : function() {
if(this.props.query.local){ if(this.props.query.local){
this.setState({ brewText : localStorage.getItem(this.props.query.local)}); this.setState({ brewText: localStorage.getItem(this.props.query.local) });
} }
if(this.props.query.dialog) window.print(); if(this.props.query.dialog) window.print();
}, },
renderPages : function(){ renderPages : function(){
return _.map(this.state.brewText.split('\\page'), (page, index) => { return _.map(this.state.brewText.split('\\page'), (page, index)=>{
return <div return <div
className='phb' className='phb'
id={`p${index + 1}`} id={`p${index + 1}`}
dangerouslySetInnerHTML={{__html:Markdown.render(page)}} dangerouslySetInnerHTML={{ __html: Markdown.render(page) }}
key={index} />; key={index} />;
}); });
}, },
@@ -40,7 +40,7 @@ const PrintPage = React.createClass({
render : function(){ render : function(){
return <div> return <div>
{this.renderPages()} {this.renderPages()}
</div> </div>;
} }
}); });

View File

@@ -1,71 +1,71 @@
const React = require('react'); const React = require('react');
const _ = require('lodash'); const _ = require('lodash');
const cx = require('classnames'); const cx = require('classnames');
const Nav = require('naturalcrit/nav/nav.jsx'); const Nav = require('naturalcrit/nav/nav.jsx');
const Navbar = require('../../navbar/navbar.jsx'); const Navbar = require('../../navbar/navbar.jsx');
const PrintLink = require('../../navbar/print.navitem.jsx'); const PrintLink = require('../../navbar/print.navitem.jsx');
const ReportIssue = require('../../navbar/issue.navitem.jsx'); const ReportIssue = require('../../navbar/issue.navitem.jsx');
//const RecentlyViewed = require('../../navbar/recent.navitem.jsx').viewed; //const RecentlyViewed = require('../../navbar/recent.navitem.jsx').viewed;
const Account = require('../../navbar/account.navitem.jsx'); const Account = require('../../navbar/account.navitem.jsx');
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx'); const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
const SharePage = React.createClass({ const SharePage = React.createClass({
getDefaultProps: function() { getDefaultProps : function() {
return { return {
brew : { brew : {
title : '', title : '',
text : '', text : '',
shareId : null, shareId : null,
createdAt : null, createdAt : null,
updatedAt : null, updatedAt : null,
views : 0 views : 0
} }
}; };
}, },
componentDidMount: function() { componentDidMount : function() {
document.addEventListener('keydown', this.handleControlKeys); document.addEventListener('keydown', this.handleControlKeys);
}, },
componentWillUnmount: function() { componentWillUnmount : function() {
document.removeEventListener('keydown', this.handleControlKeys); document.removeEventListener('keydown', this.handleControlKeys);
}, },
handleControlKeys : function(e){ handleControlKeys : function(e){
if(!(e.ctrlKey || e.metaKey)) return; if(!(e.ctrlKey || e.metaKey)) return;
const P_KEY = 80; const P_KEY = 80;
if(e.keyCode == P_KEY){ if(e.keyCode == P_KEY){
window.open(`/print/${this.props.brew.shareId}?dialog=true`, '_blank').focus(); window.open(`/print/${this.props.brew.shareId}?dialog=true`, '_blank').focus();
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
} }
}, },
render : function(){ render : function(){
return <div className='sharePage page'> return <div className='sharePage page'>
<Navbar> <Navbar>
<Nav.section> <Nav.section>
<Nav.item className='brewTitle'>{this.props.brew.title}</Nav.item> <Nav.item className='brewTitle'>{this.props.brew.title}</Nav.item>
</Nav.section> </Nav.section>
<Nav.section> <Nav.section>
<ReportIssue /> <ReportIssue />
{/*<RecentlyViewed brew={this.props.brew} />*/} {/*<RecentlyViewed brew={this.props.brew} />*/}
<PrintLink shareId={this.props.brew.shareId} /> <PrintLink shareId={this.props.brew.shareId} />
<Nav.item href={'/source/' + this.props.brew.shareId} color='teal' icon='fa-code'> <Nav.item href={`/source/${this.props.brew.shareId}`} color='teal' icon='fa-code'>
source source
</Nav.item> </Nav.item>
<Account /> <Account />
</Nav.section> </Nav.section>
</Navbar> </Navbar>
<div className='content'> <div className='content'>
<BrewRenderer text={this.props.brew.text} /> <BrewRenderer text={this.props.brew.text} />
</div> </div>
</div> </div>;
} }
}); });
module.exports = SharePage; module.exports = SharePage;

View File

@@ -2,13 +2,13 @@ const React = require('react');
const _ = require('lodash'); const _ = require('lodash');
const cx = require('classnames'); const cx = require('classnames');
const moment = require('moment'); const moment = require('moment');
const request = require("superagent"); const request = require('superagent');
const BrewItem = React.createClass({ const BrewItem = React.createClass({
getDefaultProps: function() { getDefaultProps : function() {
return { return {
brew : { brew : {
title : '', title : '',
description : '', description : '',
authors : [] authors : []
@@ -17,29 +17,29 @@ const BrewItem = React.createClass({
}, },
deleteBrew : function(){ deleteBrew : function(){
if(!confirm("are you sure you want to delete this brew?")) return; if(!confirm('are you sure you want to delete this brew?')) return;
if(!confirm("are you REALLY sure? You will not be able to recover it")) return; if(!confirm('are you REALLY sure? You will not be able to recover it')) return;
request.get('/api/remove/' + this.props.brew.editId) request.get(`/api/remove/${this.props.brew.editId}`)
.send() .send()
.end(function(err, res){ .end(function(err, res){
location.reload(); location.reload();
}); });
}, },
renderDeleteBrewLink: function(){ renderDeleteBrewLink : function(){
if(!this.props.brew.editId) return; if(!this.props.brew.editId) return;
return <a onClick={this.deleteBrew}> return <a onClick={this.deleteBrew}>
<i className='fa fa-trash' /> <i className='fa fa-trash' />
</a> </a>;
}, },
renderEditLink: function(){ renderEditLink : function(){
if(!this.props.brew.editId) return; if(!this.props.brew.editId) return;
return <a href={`/edit/${this.props.brew.editId}`} target='_blank'> return <a href={`/edit/${this.props.brew.editId}`} target='_blank'>
<i className='fa fa-pencil' /> <i className='fa fa-pencil' />
</a> </a>;
}, },
render : function(){ render : function(){
@@ -68,7 +68,7 @@ const BrewItem = React.createClass({
{this.renderEditLink()} {this.renderEditLink()}
{this.renderDeleteBrewLink()} {this.renderDeleteBrewLink()}
</div> </div>
</div> </div>;
} }
}); });

View File

@@ -9,19 +9,19 @@ const RecentNavItem = require('../../navbar/recent.navitem.jsx');
const Account = require('../../navbar/account.navitem.jsx'); const Account = require('../../navbar/account.navitem.jsx');
const BrewItem = require('./brewItem/brewItem.jsx'); const BrewItem = require('./brewItem/brewItem.jsx');
const brew = { // const brew = {
title : 'SUPER Long title woah now', // title : 'SUPER Long title woah now',
authors : [] // authors : []
} // };
const BREWS = _.times(25, ()=>{ return brew}); //const BREWS = _.times(25, ()=>{ return brew;});
const UserPage = React.createClass({ const UserPage = React.createClass({
getDefaultProps: function() { getDefaultProps : function() {
return { return {
username : '', username : '',
brews : [] brews : []
}; };
}, },
@@ -30,14 +30,14 @@ const UserPage = React.createClass({
const sortedBrews = _.sortBy(brews, (brew)=>{ return brew.title; }); const sortedBrews = _.sortBy(brews, (brew)=>{ return brew.title; });
return _.map(sortedBrews, (brew, idx) => { return _.map(sortedBrews, (brew, idx)=>{
return <BrewItem brew={brew} key={idx}/> return <BrewItem brew={brew} key={idx}/>;
}); });
}, },
getSortedBrews : function(){ getSortedBrews : function(){
return _.groupBy(this.props.brews, (brew)=>{ return _.groupBy(this.props.brews, (brew)=>{
return (brew.published ? 'published' : 'private') return (brew.published ? 'published' : 'private');
}); });
}, },
@@ -68,7 +68,7 @@ const UserPage = React.createClass({
{this.renderPrivateBrews(brews.private)} {this.renderPrivateBrews(brews.private)}
</div> </div>
</div> </div>
</div> </div>;
} }
}); });

View File

@@ -1,21 +1,21 @@
module.exports = function(vitreum){ module.exports = function(vitreum){
return ` return `
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<link href="//netdna.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" /> <link href="//netdna.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" />
<link href="//fonts.googleapis.com/css?family=Open+Sans:400,300,600,700" rel="stylesheet" type="text/css" /> <link href="//fonts.googleapis.com/css?family=Open+Sans:400,300,600,700" rel="stylesheet" type="text/css" />
<link rel="icon" href="/assets/homebrew/favicon.ico" type="image/x-icon" /> <link rel="icon" href="/assets/homebrew/favicon.ico" type="image/x-icon" />
<title>The Homebrewery - NaturalCrit</title> <title>The Homebrewery - NaturalCrit</title>
${vitreum.head} ${vitreum.head}
</head> </head>
<body> <body>
<main id="reactRoot">${vitreum.body}</main> <main id="reactRoot">${vitreum.body}</main>
</body> </body>
${vitreum.js} ${vitreum.js}
</html> </html>
`; `;
} };

View File

@@ -38,5 +38,6 @@ If you're updating dependencies, please make sure you use npm@5.6.0 and commit t
- For ambitious tasks, you should try to get your work in front of the community for feedback as soon as possible. Open a pull request as soon as you have done the minimum needed to demonstrate your idea. At this early stage, don't worry about making things perfect, or 100% complete. Add a [WIP] prefix to the title, and describe what you still need to do. This lets reviewers know not to nit-pick small details or point out improvements you already know you need to make. - For ambitious tasks, you should try to get your work in front of the community for feedback as soon as possible. Open a pull request as soon as you have done the minimum needed to demonstrate your idea. At this early stage, don't worry about making things perfect, or 100% complete. Add a [WIP] prefix to the title, and describe what you still need to do. This lets reviewers know not to nit-pick small details or point out improvements you already know you need to make.
- New features should be accompanied with tests and documentation if applicable. - New features should be accompanied with tests and documentation if applicable.
- Lint and test before submitting the pull request by running `$ npm run verify`. - Lint and test before submitting the pull request by running `$ npm run verify`.
- If your code is not passing Linting checks due to a non-fixable warning, and you feel it's valid (eg. we lint on a file being too long, but sometimes a file just _has_ to be long), add `/* eslint-disable [rule-name] */` to the top of the file. Be sure to justfiy your lint override in your PR description.
- Use a clear and descriptive title for the pull request and commits. - Use a clear and descriptive title for the pull request and commits.
- You might be asked to do changes to your pull request. There's never a need to open another pull request. [Just update the existing one.](https://github.com/RichardLitt/knowledge/blob/master/github/amending-a-commit-guide.md) - You might be asked to do changes to your pull request. There's never a need to open another pull request. [Just update the existing one.](https://github.com/RichardLitt/knowledge/blob/master/github/amending-a-commit-guide.md)

View File

@@ -20,4 +20,4 @@ Promise.resolve()
.then(livereload()) .then(livereload())
.then(server('./server.js', ['server'])) .then(server('./server.js', ['server']))
.then(console.timeEnd.bind(console, label)) .then(console.timeEnd.bind(console, label))
.catch(console.error) .catch(console.error);

View File

@@ -1,10 +1,10 @@
const less = require('less'); const less = require('less');
const fs = require('fs'); const fs = require('fs');
less.render(fs.readFileSync('./client/homebrew/phbStyle/phb.style.less', 'utf8'), {compress : true}) less.render(fs.readFileSync('./client/homebrew/phbStyle/phb.style.less', 'utf8'), { compress: true })
.then((output) => { .then((output)=>{
fs.writeFileSync('./phb.standalone.css', output.css); fs.writeFileSync('./phb.standalone.css', output.css);
console.log('phb.standalone.css created!'); console.log('phb.standalone.css created!');
}, (err) => { }, (err)=>{
console.error(err); console.error(err);
}); });

View File

@@ -1,10 +1,10 @@
const _ = require('lodash'); const _ = require('lodash');
const jwt = require('jwt-simple'); const jwt = require('jwt-simple');
const express = require("express"); const express = require('express');
const app = express(); const app = express();
app.use(express.static(__dirname + '/build'));'' app.use(express.static(`${__dirname}/build`));'';
app.use(require('body-parser').json({limit: '25mb'})); app.use(require('body-parser').json({ limit: '25mb' }));
app.use(require('cookie-parser')()); app.use(require('cookie-parser')());
const config = require('nconf') const config = require('nconf')
@@ -16,18 +16,18 @@ const config = require('nconf')
//DB //DB
require('mongoose') require('mongoose')
.connect(process.env.MONGODB_URI || process.env.MONGOLAB_URI || 'mongodb://localhost/naturalcrit') .connect(process.env.MONGODB_URI || process.env.MONGOLAB_URI || 'mongodb://localhost/naturalcrit')
.connection.on('error', () => { .connection.on('error', ()=>{
console.log('Error : Could not connect to a Mongo Database.'); console.log('Error : Could not connect to a Mongo Database.');
console.log(' If you are running locally, make sure mongodb.exe is running.'); console.log(' If you are running locally, make sure mongodb.exe is running.');
}); });
//Account MIddleware //Account MIddleware
app.use((req, res, next) => { app.use((req, res, next)=>{
if(req.cookies && req.cookies.nc_session){ if(req.cookies && req.cookies.nc_session){
try{ try {
req.account = jwt.decode(req.cookies.nc_session, config.get('secret')); req.account = jwt.decode(req.cookies.nc_session, config.get('secret'));
}catch(e){} } catch (e){}
} }
return next(); return next();
}); });
@@ -43,9 +43,9 @@ const changelogText = require('fs').readFileSync('./changelog.md', 'utf8');
//Source page //Source page
String.prototype.replaceAll = function(s,r){return this.split(s).join(r)} String.prototype.replaceAll = function(s, r){return this.split(s).join(r);};
app.get('/source/:id', (req, res)=>{ app.get('/source/:id', (req, res)=>{
HomebrewModel.get({shareId : req.params.id}) HomebrewModel.get({ shareId: req.params.id })
.then((brew)=>{ .then((brew)=>{
const text = brew.text.replaceAll('<', '&lt;').replaceAll('>', '&gt;'); const text = brew.text.replaceAll('<', '&lt;').replaceAll('>', '&gt;');
return res.send(`<code><pre>${text}</pre></code>`); return res.send(`<code><pre>${text}</pre></code>`);
@@ -53,25 +53,25 @@ app.get('/source/:id', (req, res)=>{
.catch((err)=>{ .catch((err)=>{
console.log(err); console.log(err);
return res.status(404).send('Could not find Homebrew with that id'); return res.status(404).send('Could not find Homebrew with that id');
}) });
}); });
app.get('/user/:username', (req, res, next) => { app.get('/user/:username', (req, res, next)=>{
const fullAccess = req.account && (req.account.username == req.params.username); const fullAccess = req.account && (req.account.username == req.params.username);
HomebrewModel.getByUser(req.params.username, fullAccess) HomebrewModel.getByUser(req.params.username, fullAccess)
.then((brews) => { .then((brews)=>{
req.brews = brews; req.brews = brews;
return next(); return next();
}) })
.catch((err) => { .catch((err)=>{
console.log(err); console.log(err);
}) });
}) });
app.get('/edit/:id', (req, res, next)=>{ app.get('/edit/:id', (req, res, next)=>{
HomebrewModel.get({editId : req.params.id}) HomebrewModel.get({ editId: req.params.id })
.then((brew)=>{ .then((brew)=>{
req.brew = brew.sanatize(); req.brew = brew.sanatize();
return next(); return next();
@@ -84,7 +84,7 @@ app.get('/edit/:id', (req, res, next)=>{
//Share Page //Share Page
app.get('/share/:id', (req, res, next)=>{ app.get('/share/:id', (req, res, next)=>{
HomebrewModel.get({shareId : req.params.id}) HomebrewModel.get({ shareId: req.params.id })
.then((brew)=>{ .then((brew)=>{
return brew.increaseView(); return brew.increaseView();
}) })
@@ -100,7 +100,7 @@ app.get('/share/:id', (req, res, next)=>{
//Print Page //Print Page
app.get('/print/:id', (req, res, next)=>{ app.get('/print/:id', (req, res, next)=>{
HomebrewModel.get({shareId : req.params.id}) HomebrewModel.get({ shareId: req.params.id })
.then((brew)=>{ .then((brew)=>{
req.brew = brew.sanatize(true); req.brew = brew.sanatize(true);
return next(); return next();
@@ -115,20 +115,20 @@ app.get('/print/:id', (req, res, next)=>{
//Render Page //Render Page
const render = require('vitreum/steps/render'); const render = require('vitreum/steps/render');
const templateFn = require('./client/template.js'); const templateFn = require('./client/template.js');
app.use((req, res) => { app.use((req, res)=>{
render('homebrew', templateFn, { render('homebrew', templateFn, {
version : require('./package.json').version, version : require('./package.json').version,
url: req.originalUrl, url : req.originalUrl,
welcomeText : welcomeText, welcomeText : welcomeText,
changelog : changelogText, changelog : changelogText,
brew : req.brew, brew : req.brew,
brews : req.brews, brews : req.brews,
account : req.account account : req.account
})
.then((page)=>{
return res.send(page);
}) })
.then((page) => { .catch((err)=>{
return res.send(page)
})
.catch((err) => {
console.log(err); console.log(err);
return res.sendStatus(500); return res.sendStatus(500);
}); });

View File

@@ -20,9 +20,9 @@ process.env.ADMIN_KEY = process.env.ADMIN_KEY || 'admin_key';
//Removes all empty brews that are older than 3 days and that are shorter than a tweet //Removes all empty brews that are older than 3 days and that are shorter than a tweet
router.get('/api/invalid', mw.adminOnly, (req, res)=>{ router.get('/api/invalid', mw.adminOnly, (req, res)=>{
const invalidBrewQuery = HomebrewModel.find({ const invalidBrewQuery = HomebrewModel.find({
'$where' : "this.text.length < 140", '$where' : 'this.text.length < 140',
createdAt: { createdAt : {
$lt: Moment().subtract(3, 'days').toDate() $lt : Moment().subtract(3, 'days').toDate()
} }
}); });
@@ -30,28 +30,28 @@ router.get('/api/invalid', mw.adminOnly, (req, res)=>{
invalidBrewQuery.remove().exec((err, objs)=>{ invalidBrewQuery.remove().exec((err, objs)=>{
refreshCount(); refreshCount();
return res.send(200); return res.send(200);
}) });
}else{ } else {
invalidBrewQuery.exec((err, objs)=>{ invalidBrewQuery.exec((err, objs)=>{
if(err) console.log(err); if(err) console.log(err);
return res.json({ return res.json({
count : objs.length count : objs.length
}) });
}) });
} }
}); });
router.get('/admin/lookup/:id', mw.adminOnly, (req, res, next) => { router.get('/admin/lookup/:id', mw.adminOnly, (req, res, next)=>{
//search for mathcing edit id //search for mathcing edit id
//search for matching share id //search for matching share id
// search for partial match // search for partial match
HomebrewModel.findOne({ $or:[ HomebrewModel.findOne({ $or : [
{editId : { "$regex": req.params.id, "$options": "i" }}, { editId: { '$regex': req.params.id, '$options': 'i' } },
{shareId : { "$regex": req.params.id, "$options": "i" }}, { shareId: { '$regex': req.params.id, '$options': 'i' } },
]}).exec((err, brew) => { ] }).exec((err, brew)=>{
return res.json(brew); return res.json(brew);
}); });
}); });
@@ -61,19 +61,19 @@ router.get('/admin/lookup/:id', mw.adminOnly, (req, res, next) => {
const render = require('vitreum/steps/render'); const render = require('vitreum/steps/render');
const templateFn = require('../client/template.js'); const templateFn = require('../client/template.js');
router.get('/admin', function(req, res){ router.get('/admin', function(req, res){
const credentials = auth(req) const credentials = auth(req);
if (!credentials || credentials.name !== process.env.ADMIN_USER || credentials.pass !== process.env.ADMIN_PASS) { if(!credentials || credentials.name !== process.env.ADMIN_USER || credentials.pass !== process.env.ADMIN_PASS) {
res.setHeader('WWW-Authenticate', 'Basic realm="example"') res.setHeader('WWW-Authenticate', 'Basic realm="example"');
return res.status(401).send('Access denied') return res.status(401).send('Access denied');
} }
render('admin', templateFn, { render('admin', templateFn, {
url: req.originalUrl, url : req.originalUrl,
admin_key : process.env.ADMIN_KEY, admin_key : process.env.ADMIN_KEY,
})
.then((page)=>{
return res.send(page);
}) })
.then((page) => { .catch((err)=>{
return res.send(page)
})
.catch((err) => {
console.log(err); console.log(err);
return res.sendStatus(500); return res.sendStatus(500);
}); });

View File

@@ -1,144 +1,130 @@
const _ = require('lodash'); const _ = require('lodash');
const Moment = require('moment'); const HomebrewModel = require('./homebrew.model.js').model;
const HomebrewModel = require('./homebrew.model.js').model; const router = require('express').Router();
const router = require('express').Router();
// const getTopBrews = (cb)=>{
// HomebrewModel.find().sort({ views: -1 }).limit(5).exec(function(err, brews) {
// cb(brews);
//TODO: Possiblity remove // });
let homebrewTotal = 0; // };
const refreshCount = ()=>{
HomebrewModel.count({}, (err, total)=>{ const getGoodBrewTitle = (text)=>{
homebrewTotal = total; const titlePos = text.indexOf('# ');
}); if(titlePos !== -1){
}; const ending = text.indexOf('\n', titlePos);
refreshCount(); return text.substring(titlePos + 2, ending);
} else {
return _.find(text.split('\n'), (line)=>{
return line;
const getTopBrews = (cb)=>{ });
HomebrewModel.find().sort({views: -1}).limit(5).exec(function(err, brews) { }
cb(brews); };
});
}
const getGoodBrewTitle = (text) => { router.post('/api', (req, res)=>{
const titlePos = text.indexOf('# ');
if(titlePos !== -1){ let authors = [];
const ending = text.indexOf('\n', titlePos); if(req.account) authors = [req.account.username];
return text.substring(titlePos + 2, ending);
}else{ const newHomebrew = new HomebrewModel(_.merge({},
return _.find(text.split('\n'), (line)=>{ req.body,
return line; { authors: authors }
}); ));
} if(!newHomebrew.title){
}; newHomebrew.title = getGoodBrewTitle(newHomebrew.text);
}
newHomebrew.save((err, obj)=>{
if(err){
router.post('/api', (req, res)=>{ console.error(err, err.toString(), err.stack);
return res.status(500).send(`Error while creating new brew, ${err.toString()}`);
let authors = []; }
if(req.account) authors = [req.account.username]; return res.json(obj);
});
const newHomebrew = new HomebrewModel(_.merge({}, });
req.body,
{authors : authors} router.put('/api/update/:id', (req, res)=>{
)); HomebrewModel.get({ editId: req.params.id })
if(!newHomebrew.title){ .then((brew)=>{
newHomebrew.title = getGoodBrewTitle(newHomebrew.text); brew = _.merge(brew, req.body);
} brew.updatedAt = new Date();
newHomebrew.save((err, obj)=>{ if(req.account) brew.authors = _.uniq(_.concat(brew.authors, req.account.username));
if(err){
console.error(err, err.toString(), err.stack); brew.markModified('authors');
return res.status(500).send(`Error while creating new brew, ${err.toString()}`); brew.markModified('systems');
}
return res.json(obj); brew.save((err, obj)=>{
}) if(err) throw err;
}); return res.status(200).send(obj);
});
router.put('/api/update/:id', (req, res)=>{ })
HomebrewModel.get({editId : req.params.id}) .catch((err)=>{
.then((brew)=>{ console.log(err);
brew = _.merge(brew, req.body); return res.status(500).send('Error while saving');
brew.updatedAt = new Date(); });
if(req.account) brew.authors = _.uniq(_.concat(brew.authors, req.account.username)); });
brew.markModified('authors'); router.get('/api/remove/:id', (req, res)=>{
brew.markModified('systems'); HomebrewModel.find({ editId: req.params.id }, (err, objs)=>{
if(!objs.length || err) return res.status(404).send('Can not find homebrew with that id');
brew.save((err, obj)=>{ const resEntry = objs[0];
if(err) throw err; resEntry.remove((err)=>{
return res.status(200).send(obj); if(err) return res.status(500).send('Error while removing');
}) return res.status(200).send();
}) });
.catch((err)=>{ });
console.log(err); });
return res.status(500).send("Error while saving");
});
}); module.exports = router;
router.get('/api/remove/:id', (req, res)=>{ /*
HomebrewModel.find({editId : req.params.id}, (err, objs)=>{
if(!objs.length || err) return res.status(404).send("Can not find homebrew with that id");
var resEntry = objs[0];
resEntry.remove((err)=>{ module.exports = function(app){
if(err) return res.status(500).send("Error while removing");
return res.status(200).send(); app;
})
});
});
app.get('/api/search', mw.adminOnly, function(req, res){
module.exports = router;
var page = req.query.page || 0;
/* var count = req.query.count || 20;
var query = {};
if(req.query && req.query.id){
module.exports = function(app){ query = {
"$or" : [{
app; editId : req.query.id
},{
shareId : req.query.id
}]
};
app.get('/api/search', mw.adminOnly, function(req, res){ }
var page = req.query.page || 0; HomebrewModel.find(query, {
var count = req.query.count || 20; text : 0 //omit the text
}, {
var query = {}; skip: page*count,
if(req.query && req.query.id){ limit: count*1
query = { }, function(err, objs){
"$or" : [{ if(err) console.log(err);
editId : req.query.id return res.json({
},{ page : page,
shareId : req.query.id count : count,
}] total : homebrewTotal,
}; brews : objs
} });
HomebrewModel.find(query, { });
text : 0 //omit the text })
}, {
skip: page*count,
limit: count*1
}, function(err, objs){
if(err) console.log(err); return app;
return res.json({ }
page : page,
count : count,
total : homebrewTotal,
brews : objs
});
});
})
return app;
}
*/ */

View File

@@ -1,81 +1,81 @@
var mongoose = require('mongoose'); const mongoose = require('mongoose');
var shortid = require('shortid'); const shortid = require('shortid');
var _ = require('lodash'); const _ = require('lodash');
var HomebrewSchema = mongoose.Schema({ const HomebrewSchema = mongoose.Schema({
shareId : {type : String, default: shortid.generate, index: { unique: true }}, shareId : { type: String, default: shortid.generate, index: { unique: true } },
editId : {type : String, default: shortid.generate, index: { unique: true }}, editId : { type: String, default: shortid.generate, index: { unique: true } },
title : {type : String, default : ""}, title : { type: String, default: '' },
text : {type : String, default : ""}, text : { type: String, default: '' },
description : {type : String, default : ""}, description : { type: String, default: '' },
tags : {type : String, default : ""}, tags : { type: String, default: '' },
systems : [String], systems : [String],
authors : [String], authors : [String],
published : {type : Boolean, default : false}, published : { type: Boolean, default: false },
createdAt : { type: Date, default: Date.now }, createdAt : { type: Date, default: Date.now },
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: 1 }
}, { versionKey: false }); }, { versionKey: false });
HomebrewSchema.methods.sanatize = function(full=false){ HomebrewSchema.methods.sanatize = function(full=false){
const brew = this.toJSON(); const brew = this.toJSON();
delete brew._id; delete brew._id;
delete brew.__v; delete brew.__v;
if(full){ if(full){
delete brew.editId; delete brew.editId;
} }
return brew; return brew;
}; };
HomebrewSchema.methods.increaseView = function(){ HomebrewSchema.methods.increaseView = function(){
return new Promise((resolve, reject) => { return new Promise((resolve, reject)=>{
this.lastViewed = new Date(); this.lastViewed = new Date();
this.views = this.views + 1; this.views = this.views + 1;
this.save((err) => { this.save((err)=>{
if(err) return reject(err); if(err) return reject(err);
return resolve(this); return resolve(this);
}); });
}); });
}; };
HomebrewSchema.statics.get = function(query){ HomebrewSchema.statics.get = function(query){
return new Promise((resolve, reject) => { return new Promise((resolve, reject)=>{
Homebrew.find(query, (err, brews)=>{ Homebrew.find(query, (err, brews)=>{
if(err || !brews.length) return reject('Can not find brew'); if(err || !brews.length) return reject('Can not find brew');
return resolve(brews[0]); return resolve(brews[0]);
}); });
}); });
}; };
HomebrewSchema.statics.getByUser = function(username, allowAccess=false){ HomebrewSchema.statics.getByUser = function(username, allowAccess=false){
return new Promise((resolve, reject) => { return new Promise((resolve, reject)=>{
let query = {authors : username, published : true}; const query = { authors: username, published: true };
if(allowAccess){ if(allowAccess){
delete query.published; delete query.published;
} }
Homebrew.find(query, (err, brews)=>{ Homebrew.find(query, (err, brews)=>{
if(err) return reject('Can not find brew'); if(err) return reject('Can not find brew');
return resolve(_.map(brews, (brew)=>{ return resolve(_.map(brews, (brew)=>{
return brew.sanatize(!allowAccess); return brew.sanatize(!allowAccess);
})); }));
}); });
}); });
}; };
var Homebrew = mongoose.model('Homebrew', HomebrewSchema); const Homebrew = mongoose.model('Homebrew', HomebrewSchema);
module.exports = { module.exports = {
schema : HomebrewSchema, schema : HomebrewSchema,
model : Homebrew, model : Homebrew,
} };

View File

@@ -6,16 +6,16 @@ const cx = require('classnames');
const DISMISS_KEY = 'dismiss_render_warning'; const DISMISS_KEY = 'dismiss_render_warning';
const RenderWarnings = React.createClass({ const RenderWarnings = React.createClass({
getInitialState: function() { getInitialState : function() {
return { return {
warnings: {} warnings : {}
}; };
}, },
componentDidMount: function() { componentDidMount : function() {
this.checkWarnings(); this.checkWarnings();
window.addEventListener('resize', this.checkWarnings); window.addEventListener('resize', this.checkWarnings);
}, },
componentWillUnmount: function() { componentWillUnmount : function() {
window.removeEventListener('resize', this.checkWarnings); window.removeEventListener('resize', this.checkWarnings);
}, },
warnings : { warnings : {
@@ -43,21 +43,21 @@ const RenderWarnings = React.createClass({
}, },
checkWarnings : function(){ checkWarnings : function(){
const hideDismiss = localStorage.getItem(DISMISS_KEY); const hideDismiss = localStorage.getItem(DISMISS_KEY);
if(hideDismiss) return this.setState({warnings : {}}); if(hideDismiss) return this.setState({ warnings: {} });
this.setState({ this.setState({
warnings : _.reduce(this.warnings, (r, fn, type) => { warnings : _.reduce(this.warnings, (r, fn, type)=>{
const element = fn(); const element = fn();
if(element) r[type] = element; if(element) r[type] = element;
return r; return r;
}, {}) }, {})
}) });
}, },
dismiss : function(){ dismiss : function(){
localStorage.setItem(DISMISS_KEY, true); localStorage.setItem(DISMISS_KEY, true);
this.checkWarnings(); this.checkWarnings();
}, },
render: function(){ render : function(){
if(_.isEmpty(this.state.warnings)) return null; if(_.isEmpty(this.state.warnings)) return null;
return <div className='renderWarnings'> return <div className='renderWarnings'>
@@ -66,7 +66,7 @@ const RenderWarnings = React.createClass({
<h3>Render Warnings</h3> <h3>Render Warnings</h3>
<small>If this homebrew is rendering badly if might be because of the following:</small> <small>If this homebrew is rendering badly if might be because of the following:</small>
<ul>{_.values(this.state.warnings)}</ul> <ul>{_.values(this.state.warnings)}</ul>
</div> </div>;
} }
}); });

View File

@@ -1,73 +1,73 @@
var React = require('react'); const React = require('react');
var _ = require('lodash'); const _ = require('lodash');
var cx = require('classnames'); const cx = require('classnames');
var CodeMirror; let 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');
} }
var CodeEditor = React.createClass({ const CodeEditor = React.createClass({
getDefaultProps: function() { getDefaultProps : function() {
return { return {
language : '', language : '',
value : '', value : '',
wrap : false, wrap : false,
onChange : function(){}, onChange : function(){},
onCursorActivity : function(){}, onCursorActivity : function(){},
}; };
}, },
componentDidMount: function() { componentDidMount : 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
}); });
this.codeMirror.on('change', this.handleChange); this.codeMirror.on('change', this.handleChange);
this.codeMirror.on('cursorActivity', this.handleCursorActivity); this.codeMirror.on('cursorActivity', this.handleCursorActivity);
this.updateSize(); this.updateSize();
}, },
componentWillReceiveProps: function(nextProps){ componentWillReceiveProps : function(nextProps){
if(this.codeMirror && nextProps.value !== undefined && this.codeMirror.getValue() != nextProps.value) { if(this.codeMirror && nextProps.value !== undefined && this.codeMirror.getValue() != nextProps.value) {
this.codeMirror.setValue(nextProps.value); this.codeMirror.setValue(nextProps.value);
} }
}, },
shouldComponentUpdate: function(nextProps, nextState) { shouldComponentUpdate : function(nextProps, nextState) {
return false; 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);
}, },
updateSize : function(){ updateSize : function(){
this.codeMirror.refresh(); this.codeMirror.refresh();
}, },
handleChange : function(editor){ handleChange : function(editor){
this.props.onChange(editor.getValue()); this.props.onChange(editor.getValue());
}, },
handleCursorActivity : function(){ handleCursorActivity : function(){
this.props.onCursorActivity(this.codeMirror.doc.getCursor()); this.props.onCursorActivity(this.codeMirror.doc.getCursor());
}, },
render : function(){ render : function(){
return <div className='codeEditor' ref='editor' /> return <div className='codeEditor' ref='editor' />;
} }
}); });
module.exports = CodeEditor; module.exports = CodeEditor;

View File

@@ -1,11 +1,11 @@
var _ = require('lodash'); const _ = require('lodash');
var Markdown = require('marked'); const Markdown = require('marked');
var renderer = new Markdown.Renderer(); const renderer = new Markdown.Renderer();
//Processes the markdown within an HTML block if it's just a class-wrapper //Processes the markdown within an HTML block if it's just a class-wrapper
renderer.html = function (html) { renderer.html = function (html) {
if(_.startsWith(_.trim(html), '<div') && _.endsWith(_.trim(html), '</div>')){ if(_.startsWith(_.trim(html), '<div') && _.endsWith(_.trim(html), '</div>')){
var openTag = html.substring(0, html.indexOf('>')+1); const openTag = html.substring(0, html.indexOf('>')+1);
html = html.substring(html.indexOf('>')+1); html = html.substring(html.indexOf('>')+1);
html = html.substring(0, html.lastIndexOf('</div>')); html = html.substring(0, html.lastIndexOf('</div>'));
return `${openTag} ${Markdown(html)} </div>`; return `${openTag} ${Markdown(html)} </div>`;
@@ -15,23 +15,23 @@ renderer.html = function (html) {
const tagTypes = ['div', 'span', 'a']; const tagTypes = ['div', 'span', 'a'];
const tagRegex = new RegExp('(' + const tagRegex = new RegExp(`(${
_.map(tagTypes, (type)=>{ _.map(tagTypes, (type)=>{
return `\\<${type}|\\</${type}>`; return `\\<${type}|\\</${type}>`;
}).join('|') + ')', 'g'); }).join('|')})`, 'g');
module.exports = { module.exports = {
marked : Markdown, marked : Markdown,
render : (rawBrewText)=>{ render : (rawBrewText)=>{
return Markdown(rawBrewText, {renderer : renderer}) return Markdown(rawBrewText, { renderer: renderer });
}, },
validate : (rawBrewText) => { validate : (rawBrewText)=>{
var errors = []; const errors = [];
var leftovers = _.reduce(rawBrewText.split('\n'), (acc, line, _lineNumber) => { const leftovers = _.reduce(rawBrewText.split('\n'), (acc, line, _lineNumber)=>{
var lineNumber = _lineNumber + 1; const lineNumber = _lineNumber + 1;
var matches = line.match(tagRegex); const matches = line.match(tagRegex);
if(!matches || !matches.length) return acc; if(!matches || !matches.length) return acc;
_.each(matches, (match)=>{ _.each(matches, (match)=>{
@@ -48,16 +48,16 @@ module.exports = {
line : lineNumber, line : lineNumber,
type : type, type : type,
text : 'Unmatched closing tag', text : 'Unmatched closing tag',
id : 'CLOSE' id : 'CLOSE'
}); });
}else if(_.last(acc).type == type){ } else if(_.last(acc).type == type){
acc.pop(); acc.pop();
}else{ } else {
errors.push({ errors.push({
line : _.last(acc).line + ' to ' + lineNumber, line : `${_.last(acc).line} to ${lineNumber}`,
type : type, type : type,
text : 'Type mismatch on closing tag', text : 'Type mismatch on closing tag',
id : 'MISMATCH' id : 'MISMATCH'
}); });
acc.pop(); acc.pop();
} }
@@ -71,9 +71,9 @@ module.exports = {
errors.push({ errors.push({
line : unmatched.line, line : unmatched.line,
type : unmatched.type, type : unmatched.type,
text : "Unmatched opening tag", text : 'Unmatched opening tag',
id : 'OPEN' id : 'OPEN'
}) });
}); });
return errors; return errors;

View File

@@ -1,72 +1,72 @@
var React = require('react'); const React = require('react');
var _ = require('lodash'); const _ = require('lodash');
var cx = require('classnames'); const cx = require('classnames');
var NaturalCritIcon = require('naturalcrit/svg/naturalcrit.svg.jsx'); const NaturalCritIcon = require('naturalcrit/svg/naturalcrit.svg.jsx');
var Nav = { const Nav = {
base : React.createClass({ base : React.createClass({
render : function(){ render : function(){
return <nav> return <nav>
<div className='navContent'> <div className='navContent'>
{this.props.children} {this.props.children}
</div> </div>
</nav> </nav>;
} }
}), }),
logo : function(){ logo : function(){
return <a className='navLogo' href="http://naturalcrit.com"> return <a className='navLogo' href='http://naturalcrit.com'>
<NaturalCritIcon /> <NaturalCritIcon />
<span className='name'> <span className='name'>
Natural<span className='crit'>Crit</span> Natural<span className='crit'>Crit</span>
</span> </span>
</a>; </a>;
}, },
section : React.createClass({ section : React.createClass({
render : function(){ render : function(){
return <div className='navSection'> return <div className='navSection'>
{this.props.children} {this.props.children}
</div> </div>;
} }
}), }),
item : React.createClass({ item : React.createClass({
getDefaultProps: function() { getDefaultProps : function() {
return { return {
icon : null, icon : null,
href : null, href : null,
newTab : false, newTab : false,
onClick : function(){}, onClick : function(){},
color : null color : null
}; };
}, },
handleClick : function(){ handleClick : function(){
this.props.onClick(); this.props.onClick();
}, },
render : function(){ render : function(){
var classes = cx('navItem', this.props.color, this.props.className); const classes = cx('navItem', this.props.color, this.props.className);
var icon; let icon;
if(this.props.icon) icon = <i className={'fa ' + this.props.icon} />; if(this.props.icon) icon = <i className={`fa ${this.props.icon}`} />;
const props = _.omit(this.props, ['newTab']); const props = _.omit(this.props, ['newTab']);
if(this.props.href){ if(this.props.href){
return <a {...props} className={classes} target={this.props.newTab ? '_blank' : '_self'} > return <a {...props} className={classes} target={this.props.newTab ? '_blank' : '_self'} >
{this.props.children} {this.props.children}
{icon} {icon}
</a> </a>;
}else{ } else {
return <div {...props} className={classes} onClick={this.handleClick} > return <div {...props} className={classes} onClick={this.handleClick} >
{this.props.children} {this.props.children}
{icon} {icon}
</div> </div>;
} }
} }
}), }),
}; };
module.exports = Nav; module.exports = Nav;

View File

@@ -1,99 +1,99 @@
var React = require('react'); const React = require('react');
var _ = require('lodash'); const _ = require('lodash');
var cx = require('classnames'); const cx = require('classnames');
var SplitPane = React.createClass({ const SplitPane = React.createClass({
getDefaultProps: function() { getDefaultProps : function() {
return { return {
storageKey : 'naturalcrit-pane-split', storageKey : 'naturalcrit-pane-split',
onDragFinish : function(){} //fires when dragging onDragFinish : function(){} //fires when dragging
}; };
}, },
getInitialState: function() { getInitialState : function() {
return { return {
size : null, size : null,
isDragging : false isDragging : false
}; };
}, },
componentDidMount: function() { componentDidMount : function() {
var paneSize = window.localStorage.getItem(this.props.storageKey); const paneSize = window.localStorage.getItem(this.props.storageKey);
if(paneSize){ if(paneSize){
this.setState({ this.setState({
size : paneSize size : paneSize
}) });
} }
}, },
handleUp : function(){ handleUp : function(){
if(this.state.isDragging){ if(this.state.isDragging){
this.props.onDragFinish(this.state.size); this.props.onDragFinish(this.state.size);
window.localStorage.setItem(this.props.storageKey, this.state.size); window.localStorage.setItem(this.props.storageKey, this.state.size);
} }
this.setState({ isDragging : false }); this.setState({ isDragging: false });
}, },
handleDown : function(){ handleDown : function(){
this.setState({ isDragging : true }); this.setState({ isDragging: true });
//this.unFocus() //this.unFocus()
}, },
handleMove : function(e){ handleMove : function(e){
if(!this.state.isDragging) return; if(!this.state.isDragging) return;
this.setState({ this.setState({
size : e.pageX size : e.pageX
}); });
}, },
/* /*
unFocus : function() { unFocus : function() {
if(document.selection){ if(document.selection){
document.selection.empty(); document.selection.empty();
}else{ }else{
window.getSelection().removeAllRanges(); window.getSelection().removeAllRanges();
} }
}, },
*/ */
renderDivider : function(){ renderDivider : function(){
return <div className='divider' onMouseDown={this.handleDown}> return <div className='divider' onMouseDown={this.handleDown}>
<div className='dots'> <div className='dots'>
<i className='fa fa-circle' /> <i className='fa fa-circle' />
<i className='fa fa-circle' /> <i className='fa fa-circle' />
<i className='fa fa-circle' /> <i className='fa fa-circle' />
</div> </div>
</div> </div>;
}, },
render : function(){ render : function(){
return <div className='splitPane' onMouseMove={this.handleMove} onMouseUp={this.handleUp}> return <div className='splitPane' onMouseMove={this.handleMove} onMouseUp={this.handleUp}>
<Pane ref='pane1' width={this.state.size}>{this.props.children[0]}</Pane> <Pane ref='pane1' width={this.state.size}>{this.props.children[0]}</Pane>
{this.renderDivider()} {this.renderDivider()}
<Pane ref='pane2'>{this.props.children[1]}</Pane> <Pane ref='pane2'>{this.props.children[1]}</Pane>
</div> </div>;
} }
}); });
var Pane = React.createClass({ const Pane = React.createClass({
getDefaultProps: function() { getDefaultProps : function() {
return { return {
width : null width : null
}; };
}, },
render : function(){ render : function(){
var styles = {}; let styles = {};
if(this.props.width){ if(this.props.width){
styles = { styles = {
flex : 'none', flex : 'none',
width : this.props.width + 'px' width : `${this.props.width}px`
} };
} }
return <div className={cx('pane', this.props.className)} style={styles}> return <div className={cx('pane', this.props.className)} style={styles}>
{this.props.children} {this.props.children}
</div> </div>;
} }
}); });
module.exports = SplitPane; module.exports = SplitPane;

View File

@@ -1,3 +1,3 @@
module.exports = function(props){ module.exports = function(props){
return <svg version="1.1" x="0px" y="0px" viewBox="0 0 80 100" enableBackground="new 0 0 80 80"><g><g><polygon fill="#000000" points="12.9,71.4 7.6,66.1 19.3,54.4 20.7,55.8 10.4,66.1 12.9,68.6 23.2,58.3 24.6,59.7 "/></g><g><path fill="#000000" d="M29,61.6c-1.7,0-3.4-0.7-4.6-1.9l-5.1-5.1c-2.5-2.5-2.5-6.6,0-9.2l0.7-0.7L34.3,59l-0.7,0.7 C32.4,60.9,30.8,61.6,29,61.6z M20.1,47.6c-1.1,1.7-0.9,4.1,0.6,5.6l5.1,5.1c0.8,0.8,2,1.3,3.2,1.3c0.9,0,1.7-0.2,2.4-0.7 L20.1,47.6z"/></g><g><path fill="#000000" d="M12.3,74.8c-0.8,0-1.5-0.3-2-0.8l-5.2-5.2c-0.5-0.5-0.8-1.2-0.8-2c0-0.8,0.3-1.5,0.8-2 c1.1-1.1,2.9-1.1,4,0l5.2,5.2c1.1,1.1,1.1,2.9,0,4C13.8,74.5,13.1,74.8,12.3,74.8z M7.1,65.9c-0.2,0-0.4,0.1-0.6,0.2 c-0.2,0.2-0.2,0.4-0.2,0.6s0.1,0.4,0.2,0.6l5.2,5.2c0.3,0.3,0.9,0.3,1.2,0c0.3-0.3,0.3-0.8,0-1.2l-5.2-5.2 C7.5,66,7.3,65.9,7.1,65.9z"/></g><g><polygon fill="#000000" points="31.7,58.7 30.3,57.3 70,17.6 70,9 62.4,9 23.3,49.4 21.9,48 61.6,7 72,7 72,18.4 "/></g><g><rect x="46" y="6.7" transform="matrix(0.7168 0.6973 -0.6973 0.7168 35.9716 -23.568)" fill="#000000" width="2" height="51.6"/></g><g><rect x="13" y="61" fill="#000000" width="2" height="7"/></g><g><rect x="17" y="57" fill="#000000" width="2" height="7"/></g></g><g><g><polygon fill="#000000" points="68.4,71.4 56.7,59.7 58.1,58.3 68.4,68.6 70.8,66.1 60.5,55.8 61.9,54.4 73.6,66.1 "/></g><g><path fill="#000000" d="M52.2,61.6c-1.7,0-3.4-0.7-4.6-1.9L46.9,59l14.3-14.3l0.7,0.7c2.5,2.5,2.5,6.6,0,9.2l-5.1,5.1 C55.6,60.9,53.9,61.6,52.2,61.6z M49.8,58.9c0.7,0.4,1.5,0.7,2.4,0.7c1.2,0,2.3-0.5,3.2-1.3l5.1-5.1c1.5-1.5,1.7-3.8,0.6-5.6 L49.8,58.9z"/></g><g><path fill="#000000" d="M68.9,74.8c-0.8,0-1.5-0.3-2-0.8c-1.1-1.1-1.1-2.9,0-4l5.2-5.2c1.1-1.1,2.9-1.1,4,0c0.5,0.5,0.8,1.2,0.8,2 c0,0.8-0.3,1.5-0.8,2L70.9,74C70.4,74.5,69.7,74.8,68.9,74.8z M74.2,65.9c-0.2,0-0.4,0.1-0.6,0.2l-5.2,5.2c-0.3,0.3-0.3,0.8,0,1.2 c0.3,0.3,0.9,0.3,1.2,0l5.2-5.2c0.2-0.2,0.2-0.4,0.2-0.6s-0.1-0.4-0.2-0.6C74.6,66,74.4,65.9,74.2,65.9z"/></g><g><rect x="38.6" y="52.3" transform="matrix(0.7082 0.706 -0.706 0.7082 50.8397 -16.4875)" fill="#000000" width="13.4" height="2"/></g><g><polygon fill="#000000" points="30.6,39.9 9,18.4 9,7 19.7,7 41.1,29.1 39.7,30.5 18.8,9 11,9 11,17.6 32,38.5 "/></g><g><rect x="47.8" y="43.1" transform="matrix(0.6959 0.7181 -0.7181 0.6959 48.1381 -25.5246)" fill="#000000" width="12.8" height="2"/></g><g><rect x="12" y="23.1" transform="matrix(0.6974 0.7167 -0.7167 0.6974 25.1384 -11.3825)" fill="#000000" width="28.1" height="2"/></g><g><rect x="43.8" y="46.4" transform="matrix(0.6974 0.7167 -0.7167 0.6974 48.7492 -20.5985)" fill="#000000" width="10" height="2"/></g><g><rect x="66" y="61" fill="#000000" width="2" height="7"/></g><g><rect x="62" y="57" fill="#000000" width="2" height="7"/></g></g></svg>; return <svg version='1.1' x='0px' y='0px' viewBox='0 0 80 100' enableBackground='new 0 0 80 80'><g><g><polygon fill='#000000' points='12.9,71.4 7.6,66.1 19.3,54.4 20.7,55.8 10.4,66.1 12.9,68.6 23.2,58.3 24.6,59.7 '/></g><g><path fill='#000000' d='M29,61.6c-1.7,0-3.4-0.7-4.6-1.9l-5.1-5.1c-2.5-2.5-2.5-6.6,0-9.2l0.7-0.7L34.3,59l-0.7,0.7 C32.4,60.9,30.8,61.6,29,61.6z M20.1,47.6c-1.1,1.7-0.9,4.1,0.6,5.6l5.1,5.1c0.8,0.8,2,1.3,3.2,1.3c0.9,0,1.7-0.2,2.4-0.7 L20.1,47.6z'/></g><g><path fill='#000000' d='M12.3,74.8c-0.8,0-1.5-0.3-2-0.8l-5.2-5.2c-0.5-0.5-0.8-1.2-0.8-2c0-0.8,0.3-1.5,0.8-2 c1.1-1.1,2.9-1.1,4,0l5.2,5.2c1.1,1.1,1.1,2.9,0,4C13.8,74.5,13.1,74.8,12.3,74.8z M7.1,65.9c-0.2,0-0.4,0.1-0.6,0.2 c-0.2,0.2-0.2,0.4-0.2,0.6s0.1,0.4,0.2,0.6l5.2,5.2c0.3,0.3,0.9,0.3,1.2,0c0.3-0.3,0.3-0.8,0-1.2l-5.2-5.2 C7.5,66,7.3,65.9,7.1,65.9z'/></g><g><polygon fill='#000000' points='31.7,58.7 30.3,57.3 70,17.6 70,9 62.4,9 23.3,49.4 21.9,48 61.6,7 72,7 72,18.4 '/></g><g><rect x='46' y='6.7' transform='matrix(0.7168 0.6973 -0.6973 0.7168 35.9716 -23.568)' fill='#000000' width='2' height='51.6'/></g><g><rect x='13' y='61' fill='#000000' width='2' height='7'/></g><g><rect x='17' y='57' fill='#000000' width='2' height='7'/></g></g><g><g><polygon fill='#000000' points='68.4,71.4 56.7,59.7 58.1,58.3 68.4,68.6 70.8,66.1 60.5,55.8 61.9,54.4 73.6,66.1 '/></g><g><path fill='#000000' d='M52.2,61.6c-1.7,0-3.4-0.7-4.6-1.9L46.9,59l14.3-14.3l0.7,0.7c2.5,2.5,2.5,6.6,0,9.2l-5.1,5.1 C55.6,60.9,53.9,61.6,52.2,61.6z M49.8,58.9c0.7,0.4,1.5,0.7,2.4,0.7c1.2,0,2.3-0.5,3.2-1.3l5.1-5.1c1.5-1.5,1.7-3.8,0.6-5.6 L49.8,58.9z'/></g><g><path fill='#000000' d='M68.9,74.8c-0.8,0-1.5-0.3-2-0.8c-1.1-1.1-1.1-2.9,0-4l5.2-5.2c1.1-1.1,2.9-1.1,4,0c0.5,0.5,0.8,1.2,0.8,2 c0,0.8-0.3,1.5-0.8,2L70.9,74C70.4,74.5,69.7,74.8,68.9,74.8z M74.2,65.9c-0.2,0-0.4,0.1-0.6,0.2l-5.2,5.2c-0.3,0.3-0.3,0.8,0,1.2 c0.3,0.3,0.9,0.3,1.2,0l5.2-5.2c0.2-0.2,0.2-0.4,0.2-0.6s-0.1-0.4-0.2-0.6C74.6,66,74.4,65.9,74.2,65.9z'/></g><g><rect x='38.6' y='52.3' transform='matrix(0.7082 0.706 -0.706 0.7082 50.8397 -16.4875)' fill='#000000' width='13.4' height='2'/></g><g><polygon fill='#000000' points='30.6,39.9 9,18.4 9,7 19.7,7 41.1,29.1 39.7,30.5 18.8,9 11,9 11,17.6 32,38.5 '/></g><g><rect x='47.8' y='43.1' transform='matrix(0.6959 0.7181 -0.7181 0.6959 48.1381 -25.5246)' fill='#000000' width='12.8' height='2'/></g><g><rect x='12' y='23.1' transform='matrix(0.6974 0.7167 -0.7167 0.6974 25.1384 -11.3825)' fill='#000000' width='28.1' height='2'/></g><g><rect x='43.8' y='46.4' transform='matrix(0.6974 0.7167 -0.7167 0.6974 48.7492 -20.5985)' fill='#000000' width='10' height='2'/></g><g><rect x='66' y='61' fill='#000000' width='2' height='7'/></g><g><rect x='62' y='57' fill='#000000' width='2' height='7'/></g></g></svg>;
} };

View File

@@ -1,9 +1,9 @@
var React = require('react'); const React = require('react');
module.exports = function(props){ module.exports = function(props){
return <svg version="1.1" x="0px" y="0px" viewBox="0 0 90 112.5" enableBackground="new 0 0 90 90" > return <svg version='1.1' x='0px' y='0px' viewBox='0 0 90 112.5' enableBackground='new 0 0 90 90' >
<path d="M25.363,25.54c0,1.906,8.793,3.454,19.636,3.454c10.848,0,19.638-1.547,19.638-3.454c0-1.12-3.056-2.117-7.774-2.75 c-1.418,1.891-3.659,3.133-6.208,3.133c-2.85,0-5.315-1.547-6.67-3.833C33.617,22.185,25.363,23.692,25.363,25.54z"/><path d="M84.075,54.142c0-8.68-2.868-17.005-8.144-23.829c1.106-1.399,1.41-2.771,1.41-3.854c0-6.574-10.245-9.358-19.264-10.533 c0.209,0.706,0.359,1.439,0.359,2.215c0,0.09-0.022,0.17-0.028,0.26l0,0c-0.028,0.853-0.195,1.667-0.479,2.429 c9.106,1.282,14.508,3.754,14.508,5.63c0,2.644-10.688,6.486-27.439,6.486c-16.748,0-27.438-3.842-27.438-6.486 c0-2.542,9.904-6.183,25.559-6.459c-0.098-0.396-0.159-0.807-0.2-1.223c0.006,0,0.013,0,0.017,0 c-0.017-0.213-0.063-0.417-0.063-0.636c0-1.084,0.226-2.119,0.628-3.058c-6.788,0.129-30.846,1.299-30.846,11.376 c0,1.083,0.305,2.455,1.411,3.854c-5.276,6.823-8.145,15.149-8.145,23.829c0,11.548,5.187,20.107,14.693,25.115 c-0.902,3.146-1.391,7.056,1.111,8.181c2.626,1.178,5.364-2.139,7.111-5.005c4.73,1.261,10.13,1.923,16.161,1.923 c6.034,0,11.428-0.661,16.158-1.922c1.75,2.865,4.493,6.18,7.112,5.004c2.504-1.123,2.014-5.035,1.113-8.179 C78.889,74.249,84.075,65.689,84.075,54.142z M70.39,31.392c5.43,6.046,8.78,14,8.78,22.75c0,20.919-18.582,25.309-34.171,25.309 c-15.587,0-34.17-4.39-34.17-25.309c0-8.75,3.35-16.7,8.781-22.753c5.561,2.643,15.502,4.009,25.389,4.009 C54.886,35.397,64.829,34.031,70.39,31.392z"/><path d="M50.654,23.374c2.892,0,5.234-2.341,5.234-5.233c0-2.887-2.343-5.23-5.234-5.23c-2.887,0-5.231,2.343-5.231,5.23 C45.423,21.032,47.768,23.374,50.654,23.374z"/> <path d='M25.363,25.54c0,1.906,8.793,3.454,19.636,3.454c10.848,0,19.638-1.547,19.638-3.454c0-1.12-3.056-2.117-7.774-2.75 c-1.418,1.891-3.659,3.133-6.208,3.133c-2.85,0-5.315-1.547-6.67-3.833C33.617,22.185,25.363,23.692,25.363,25.54z'/><path d='M84.075,54.142c0-8.68-2.868-17.005-8.144-23.829c1.106-1.399,1.41-2.771,1.41-3.854c0-6.574-10.245-9.358-19.264-10.533 c0.209,0.706,0.359,1.439,0.359,2.215c0,0.09-0.022,0.17-0.028,0.26l0,0c-0.028,0.853-0.195,1.667-0.479,2.429 c9.106,1.282,14.508,3.754,14.508,5.63c0,2.644-10.688,6.486-27.439,6.486c-16.748,0-27.438-3.842-27.438-6.486 c0-2.542,9.904-6.183,25.559-6.459c-0.098-0.396-0.159-0.807-0.2-1.223c0.006,0,0.013,0,0.017,0 c-0.017-0.213-0.063-0.417-0.063-0.636c0-1.084,0.226-2.119,0.628-3.058c-6.788,0.129-30.846,1.299-30.846,11.376 c0,1.083,0.305,2.455,1.411,3.854c-5.276,6.823-8.145,15.149-8.145,23.829c0,11.548,5.187,20.107,14.693,25.115 c-0.902,3.146-1.391,7.056,1.111,8.181c2.626,1.178,5.364-2.139,7.111-5.005c4.73,1.261,10.13,1.923,16.161,1.923 c6.034,0,11.428-0.661,16.158-1.922c1.75,2.865,4.493,6.18,7.112,5.004c2.504-1.123,2.014-5.035,1.113-8.179 C78.889,74.249,84.075,65.689,84.075,54.142z M70.39,31.392c5.43,6.046,8.78,14,8.78,22.75c0,20.919-18.582,25.309-34.171,25.309 c-15.587,0-34.17-4.39-34.17-25.309c0-8.75,3.35-16.7,8.781-22.753c5.561,2.643,15.502,4.009,25.389,4.009 C54.886,35.397,64.829,34.031,70.39,31.392z'/><path d='M50.654,23.374c2.892,0,5.234-2.341,5.234-5.233c0-2.887-2.343-5.23-5.234-5.23c-2.887,0-5.231,2.343-5.231,5.23 C45.423,21.032,47.768,23.374,50.654,23.374z'/>
<circle cx="62.905" cy="10.089" r="3.595"/> <circle cx='62.905' cy='10.089' r='3.595'/>
<circle cx="52.616" cy="5.048" r="2.73"/> <circle cx='52.616' cy='5.048' r='2.73'/>
</svg>; </svg>;
}; };

View File

@@ -1,5 +1,5 @@
var React = require('react'); const React = require('react');
module.exports = function(props){ module.exports = function(props){
return <svg version="1.1" x="0px" y="0px" viewBox="0 0 100 100" enableBackground="new 0 0 100 100"><path d="M80.644,87.982l16.592-41.483c0.054-0.128,0.088-0.26,0.108-0.394c0.006-0.039,0.007-0.077,0.011-0.116 c0.007-0.087,0.008-0.174,0.002-0.26c-0.003-0.046-0.007-0.091-0.014-0.137c-0.014-0.089-0.036-0.176-0.063-0.262 c-0.012-0.034-0.019-0.069-0.031-0.103c-0.047-0.118-0.106-0.229-0.178-0.335c-0.004-0.006-0.006-0.012-0.01-0.018L67.999,3.358 c-0.01-0.013-0.003-0.026-0.013-0.04L68,3.315V4c0,0-0.033,0-0.037,0c-0.403-1-1.094-1.124-1.752-0.976 c0,0.004-0.004-0.012-0.007-0.012C66.201,3.016,66.194,3,66.194,3H66.19h-0.003h-0.003h-0.004h-0.003c0,0-0.004,0-0.007,0 s-0.003-0.151-0.007-0.151L20.495,15.227c-0.025,0.007-0.046-0.019-0.071-0.011c-0.087,0.028-0.172,0.041-0.253,0.083 c-0.054,0.027-0.102,0.053-0.152,0.085c-0.051,0.033-0.101,0.061-0.147,0.099c-0.044,0.036-0.084,0.073-0.124,0.113 c-0.048,0.048-0.093,0.098-0.136,0.152c-0.03,0.039-0.059,0.076-0.085,0.117c-0.046,0.07-0.084,0.145-0.12,0.223 c-0.011,0.023-0.027,0.042-0.036,0.066L2.911,57.664C2.891,57.715,3,57.768,3,57.82v0.002c0,0.186,0,0.375,0,0.562 c0,0.004,0,0.004,0,0.008c0,0,0,0,0,0.002c0,0,0,0,0,0.004v0.004v0.002c0,0.074-0.002,0.15,0.012,0.223 C3.015,58.631,3,58.631,3,58.633c0,0.004,0,0.004,0,0.008c0,0,0,0,0,0.002c0,0,0,0,0,0.004v0.004c0,0,0,0,0,0.002v0.004 c0,0.191-0.046,0.377,0.06,0.545c0-0.002-0.03,0.004-0.03,0.004c0,0.004-0.03,0.004-0.03,0.004c0,0.002,0,0.002,0,0.002 l-0.045,0.004c0.03,0.047,0.036,0.09,0.068,0.133l29.049,37.359c0.002,0.004,0,0.006,0.002,0.01c0.002,0.002,0,0.004,0.002,0.008 c0.006,0.008,0.014,0.014,0.021,0.021c0.024,0.029,0.052,0.051,0.078,0.078c0.027,0.029,0.053,0.057,0.082,0.082 c0.03,0.027,0.055,0.062,0.086,0.088c0.026,0.02,0.057,0.033,0.084,0.053c0.04,0.027,0.081,0.053,0.123,0.076 c0.005,0.004,0.01,0.008,0.016,0.01c0.087,0.051,0.176,0.09,0.269,0.123c0.042,0.014,0.082,0.031,0.125,0.043 c0.021,0.006,0.041,0.018,0.062,0.021c0.123,0.027,0.249,0.043,0.375,0.043c0.099,0,0.202-0.012,0.304-0.027l45.669-8.303 c0.057-0.01,0.108-0.021,0.163-0.037C79.547,88.992,79.562,89,79.575,89c0.004,0,0.004,0,0.004,0c0.021,0,0.039-0.027,0.06-0.035 c0.041-0.014,0.08-0.034,0.12-0.052c0.021-0.01,0.044-0.019,0.064-0.03c0.017-0.01,0.026-0.015,0.033-0.017 c0.014-0.008,0.023-0.021,0.037-0.028c0.14-0.078,0.269-0.174,0.38-0.285c0.014-0.016,0.024-0.034,0.038-0.048 c0.109-0.119,0.201-0.252,0.271-0.398c0.006-0.01,0.016-0.018,0.021-0.029c0.004-0.008,0.008-0.017,0.011-0.026 c0.002-0.004,0.003-0.006,0.005-0.01C80.627,88.021,80.635,88.002,80.644,87.982z M77.611,84.461L48.805,66.453l32.407-25.202 L77.611,84.461z M46.817,63.709L35.863,23.542l43.818,14.608L46.817,63.709z M84.668,40.542l8.926,5.952l-11.902,29.75 L84.668,40.542z M89.128,39.446L84.53,36.38l-6.129-12.257L89.128,39.446z M79.876,34.645L37.807,20.622L65.854,6.599L79.876,34.645 z M33.268,19.107l-6.485-2.162l23.781-6.487L33.268,19.107z M21.92,18.895l8.67,2.891L10.357,47.798L21.92,18.895z M32.652,24.649 l10.845,39.757L7.351,57.178L32.652,24.649z M43.472,67.857L32.969,92.363L8.462,60.855L43.472,67.857z M46.631,69.09l27.826,17.393 l-38.263,6.959L46.631,69.09z"></path></svg>; return <svg version='1.1' x='0px' y='0px' viewBox='0 0 100 100' enableBackground='new 0 0 100 100'><path d='M80.644,87.982l16.592-41.483c0.054-0.128,0.088-0.26,0.108-0.394c0.006-0.039,0.007-0.077,0.011-0.116 c0.007-0.087,0.008-0.174,0.002-0.26c-0.003-0.046-0.007-0.091-0.014-0.137c-0.014-0.089-0.036-0.176-0.063-0.262 c-0.012-0.034-0.019-0.069-0.031-0.103c-0.047-0.118-0.106-0.229-0.178-0.335c-0.004-0.006-0.006-0.012-0.01-0.018L67.999,3.358 c-0.01-0.013-0.003-0.026-0.013-0.04L68,3.315V4c0,0-0.033,0-0.037,0c-0.403-1-1.094-1.124-1.752-0.976 c0,0.004-0.004-0.012-0.007-0.012C66.201,3.016,66.194,3,66.194,3H66.19h-0.003h-0.003h-0.004h-0.003c0,0-0.004,0-0.007,0 s-0.003-0.151-0.007-0.151L20.495,15.227c-0.025,0.007-0.046-0.019-0.071-0.011c-0.087,0.028-0.172,0.041-0.253,0.083 c-0.054,0.027-0.102,0.053-0.152,0.085c-0.051,0.033-0.101,0.061-0.147,0.099c-0.044,0.036-0.084,0.073-0.124,0.113 c-0.048,0.048-0.093,0.098-0.136,0.152c-0.03,0.039-0.059,0.076-0.085,0.117c-0.046,0.07-0.084,0.145-0.12,0.223 c-0.011,0.023-0.027,0.042-0.036,0.066L2.911,57.664C2.891,57.715,3,57.768,3,57.82v0.002c0,0.186,0,0.375,0,0.562 c0,0.004,0,0.004,0,0.008c0,0,0,0,0,0.002c0,0,0,0,0,0.004v0.004v0.002c0,0.074-0.002,0.15,0.012,0.223 C3.015,58.631,3,58.631,3,58.633c0,0.004,0,0.004,0,0.008c0,0,0,0,0,0.002c0,0,0,0,0,0.004v0.004c0,0,0,0,0,0.002v0.004 c0,0.191-0.046,0.377,0.06,0.545c0-0.002-0.03,0.004-0.03,0.004c0,0.004-0.03,0.004-0.03,0.004c0,0.002,0,0.002,0,0.002 l-0.045,0.004c0.03,0.047,0.036,0.09,0.068,0.133l29.049,37.359c0.002,0.004,0,0.006,0.002,0.01c0.002,0.002,0,0.004,0.002,0.008 c0.006,0.008,0.014,0.014,0.021,0.021c0.024,0.029,0.052,0.051,0.078,0.078c0.027,0.029,0.053,0.057,0.082,0.082 c0.03,0.027,0.055,0.062,0.086,0.088c0.026,0.02,0.057,0.033,0.084,0.053c0.04,0.027,0.081,0.053,0.123,0.076 c0.005,0.004,0.01,0.008,0.016,0.01c0.087,0.051,0.176,0.09,0.269,0.123c0.042,0.014,0.082,0.031,0.125,0.043 c0.021,0.006,0.041,0.018,0.062,0.021c0.123,0.027,0.249,0.043,0.375,0.043c0.099,0,0.202-0.012,0.304-0.027l45.669-8.303 c0.057-0.01,0.108-0.021,0.163-0.037C79.547,88.992,79.562,89,79.575,89c0.004,0,0.004,0,0.004,0c0.021,0,0.039-0.027,0.06-0.035 c0.041-0.014,0.08-0.034,0.12-0.052c0.021-0.01,0.044-0.019,0.064-0.03c0.017-0.01,0.026-0.015,0.033-0.017 c0.014-0.008,0.023-0.021,0.037-0.028c0.14-0.078,0.269-0.174,0.38-0.285c0.014-0.016,0.024-0.034,0.038-0.048 c0.109-0.119,0.201-0.252,0.271-0.398c0.006-0.01,0.016-0.018,0.021-0.029c0.004-0.008,0.008-0.017,0.011-0.026 c0.002-0.004,0.003-0.006,0.005-0.01C80.627,88.021,80.635,88.002,80.644,87.982z M77.611,84.461L48.805,66.453l32.407-25.202 L77.611,84.461z M46.817,63.709L35.863,23.542l43.818,14.608L46.817,63.709z M84.668,40.542l8.926,5.952l-11.902,29.75 L84.668,40.542z M89.128,39.446L84.53,36.38l-6.129-12.257L89.128,39.446z M79.876,34.645L37.807,20.622L65.854,6.599L79.876,34.645 z M33.268,19.107l-6.485-2.162l23.781-6.487L33.268,19.107z M21.92,18.895l8.67,2.891L10.357,47.798L21.92,18.895z M32.652,24.649 l10.845,39.757L7.351,57.178L32.652,24.649z M43.472,67.857L32.969,92.363L8.462,60.855L43.472,67.857z M46.631,69.09l27.826,17.393 l-38.263,6.959L46.631,69.09z'></path></svg>;
}; };