mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2026-01-26 22:43:07 +00:00
Compare commits
96 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b39f9041c2 | ||
|
|
2023ae4f6a | ||
|
|
d5f04ca2b6 | ||
|
|
f99bcabad0 | ||
|
|
32317bfa6f | ||
|
|
1946a50ce0 | ||
|
|
ee1827eab0 | ||
|
|
20371a8b3d | ||
|
|
28a3f31caa | ||
|
|
c647bdf5ee | ||
|
|
eb1827cedb | ||
|
|
a3251dfa19 | ||
|
|
30d3fcf168 | ||
|
|
393df1b181 | ||
|
|
94a3a96960 | ||
|
|
bfb2cea48e | ||
|
|
00f2703d0b | ||
|
|
4c874149fb | ||
|
|
0705e08381 | ||
|
|
e112808706 | ||
|
|
234d216d64 | ||
|
|
ef0265f4fa | ||
|
|
fbc18a017c | ||
|
|
9d4d337bb9 | ||
|
|
0d0ce101f3 | ||
|
|
540c00cb0c | ||
|
|
446ae9cbcf | ||
|
|
dc486cfba9 | ||
|
|
a6a1f41e77 | ||
|
|
fd567352a4 | ||
|
|
a33b1d845d | ||
|
|
b20f4ffb46 | ||
|
|
2f69ef3fe8 | ||
|
|
1da1f90a35 | ||
|
|
bd08858745 | ||
|
|
304cd0ffcd | ||
|
|
b40e5bc4c4 | ||
|
|
0663737e1c | ||
|
|
307dd2d9ba | ||
|
|
95c91b6ba8 | ||
|
|
c8c46725a2 | ||
|
|
7001b71d91 | ||
|
|
cbab4f4959 | ||
|
|
22d9982888 | ||
|
|
76ced9ca49 | ||
|
|
b1db8040a4 | ||
|
|
c8b089f7fb | ||
|
|
97c0443c76 | ||
|
|
c470bed591 | ||
|
|
4593099914 | ||
|
|
6030134de2 | ||
|
|
7e6f42f062 | ||
|
|
75111acefb | ||
|
|
26bcb3395a | ||
|
|
a826aaffd9 | ||
|
|
8018442f25 | ||
|
|
8e58e5aca9 | ||
|
|
2f82d3875e | ||
|
|
efee8ff05c | ||
|
|
a405c7cfb2 | ||
|
|
dfcb04fd09 | ||
|
|
728277f861 | ||
|
|
0878439750 | ||
|
|
e77532acef | ||
|
|
cd2eb5fdce | ||
|
|
37de888f03 | ||
|
|
1aa79b32d9 | ||
|
|
894e345a44 | ||
|
|
07f249b23e | ||
|
|
baaa82ed34 | ||
|
|
0d0f0d8eb0 | ||
|
|
d77fa0a3dc | ||
|
|
a26c4e2092 | ||
|
|
ca40ec5a2d | ||
|
|
987363ed41 | ||
|
|
7b38bccec1 | ||
|
|
174c2973f7 | ||
|
|
66ca09b36d | ||
|
|
5820564894 | ||
|
|
3dc4c13178 | ||
|
|
537a75b2ab | ||
|
|
a0bc4fddf8 | ||
|
|
25e0a1607a | ||
|
|
68ecf749ea | ||
|
|
10f4759471 | ||
|
|
5ba3f98696 | ||
|
|
95c09ba7ad | ||
|
|
1173af5803 | ||
|
|
924b398768 | ||
|
|
41303e6918 | ||
|
|
f75f60aa1e | ||
|
|
f4cf288f27 | ||
|
|
8abf6abf99 | ||
|
|
95aa803c61 | ||
|
|
47396e5c7e | ||
|
|
7581d155a6 |
13
.github/issue_template.md
vendored
13
.github/issue_template.md
vendored
@@ -1,16 +1,5 @@
|
|||||||
|
Share link to issue brew: http://homebrewery.naturalcrit.com/share/XXXXXXX
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Additional Details
|
|
||||||
|
|
||||||
**Share Link** :
|
|
||||||
|
|
||||||
or
|
|
||||||
|
|
||||||
**Brew code to reproduce** : <details><summary>Click to expand</summary><code><pre>
|
|
||||||
|
|
||||||
PASTE BREW CODE HERE
|
|
||||||
|
|
||||||
</pre></code></details>
|
|
||||||
@@ -1,38 +1,41 @@
|
|||||||
var React = require('react');
|
const React = require('react');
|
||||||
var _ = require('lodash');
|
const _ = require('lodash');
|
||||||
var cx = require('classnames');
|
|
||||||
|
|
||||||
var HomebrewAdmin = require('./homebrewAdmin/homebrewAdmin.jsx');
|
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
|
|
||||||
var Admin = React.createClass({
|
const BrewLookup = require('./brewLookup/brewLookup.jsx');
|
||||||
|
const AdminSearch = require('./adminSearch/adminSearch.jsx');
|
||||||
|
const InvalidBrew = require('./invalidBrew/invalidBrew.jsx');
|
||||||
|
|
||||||
|
const Admin = React.createClass({
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
return {
|
return {
|
||||||
url : "",
|
admin_key : '',
|
||||||
admin_key : "",
|
|
||||||
homebrews : [],
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
renderNavbar : function(){
|
||||||
|
return <Nav.base>
|
||||||
|
<Nav.section>
|
||||||
|
<Nav.item icon='fa-magic' className='homebreweryLogo'>
|
||||||
|
Homebrewery Admin
|
||||||
|
</Nav.item>
|
||||||
|
</Nav.section>
|
||||||
|
</Nav.base>
|
||||||
|
},
|
||||||
|
|
||||||
render : function(){
|
render : function(){
|
||||||
var self = this;
|
return <div className='admin'>
|
||||||
return(
|
{this.renderNavbar()}
|
||||||
<div className='admin'>
|
<main className='content'>
|
||||||
|
<BrewLookup adminKey={this.props.admin_key} />
|
||||||
|
<AdminSearch adminKey={this.props.admin_key} />
|
||||||
|
|
||||||
<header>
|
<div className='dangerZone'>Danger Zone</div>
|
||||||
<div className='container'>
|
|
||||||
<i className='fa fa-rocket' />
|
|
||||||
naturalcrit admin
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div className='container'>
|
<InvalidBrew adminKey={this.props.admin_key} />
|
||||||
|
</main>
|
||||||
<HomebrewAdmin homebrews={this.props.homebrews} admin_key={this.props.admin_key} />
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,39 +1,53 @@
|
|||||||
@import 'naturalcrit/styles/reset.less';
|
|
||||||
@import 'naturalcrit/styles/elements.less';
|
|
||||||
@import 'naturalcrit/styles/animations.less';
|
|
||||||
@import 'naturalcrit/styles/colors.less';
|
|
||||||
@import 'naturalcrit/styles/tooltip.less';
|
|
||||||
|
|
||||||
@import 'font-awesome/css/font-awesome.css';
|
@import 'naturalcrit/styles/core.less';
|
||||||
|
html,body, #reactRoot{
|
||||||
html,body, #reactContainer, .naturalCrit{
|
|
||||||
min-height : 100%;
|
min-height : 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@sidebarWidth : 250px;
|
|
||||||
|
|
||||||
body{
|
body{
|
||||||
background-color : #eee;
|
height : 100%;
|
||||||
font-family : 'Open Sans', sans-serif;
|
|
||||||
color : #4b5055;
|
|
||||||
font-weight : 100;
|
|
||||||
text-rendering : optimizeLegibility;
|
|
||||||
margin : 0;
|
margin : 0;
|
||||||
padding : 0;
|
padding : 0;
|
||||||
height : 100%;
|
background-color : #ddd;
|
||||||
|
font-family : 'Open Sans', sans-serif;
|
||||||
|
font-weight : 100;
|
||||||
|
color : #4b5055;
|
||||||
|
text-rendering : optimizeLegibility;
|
||||||
}
|
}
|
||||||
|
.admin {
|
||||||
.admin{
|
nav {
|
||||||
|
|
||||||
header{
|
|
||||||
background-color : @red;
|
background-color : @red;
|
||||||
font-size: 2em;
|
.navItem{
|
||||||
padding : 20px 0px;
|
background-color : @red;
|
||||||
color : white;
|
}
|
||||||
margin-bottom: 30px;
|
.homebreweryLogo{
|
||||||
i{
|
font-family : CodeBold;
|
||||||
margin-right: 30px;
|
font-size : 12px;
|
||||||
|
color : white;
|
||||||
|
div{
|
||||||
|
margin-top : 2px;
|
||||||
|
margin-bottom : -2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h1{
|
||||||
|
margin-bottom : 10px;
|
||||||
|
font-size : 2em;
|
||||||
|
font-weight : 800;
|
||||||
|
border-bottom : 1px solid #ddd;
|
||||||
|
}
|
||||||
|
main.content{
|
||||||
|
width : 1000px;
|
||||||
|
margin : 0 auto;
|
||||||
|
padding : 50px 20px;
|
||||||
|
background-color : white;
|
||||||
|
.dangerZone{
|
||||||
|
margin : 30px 0px;
|
||||||
|
padding : 10px 20px;
|
||||||
|
background : repeating-linear-gradient(45deg, @yellow, @yellow 10px, darken(#333, 10%) 10px, darken(#333, 10%) 20px);
|
||||||
|
font-size : 1em;
|
||||||
|
font-weight : 800;
|
||||||
|
color : white;
|
||||||
|
text-transform : uppercase;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
86
client/admin/adminSearch/adminSearch.jsx
Normal file
86
client/admin/adminSearch/adminSearch.jsx
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
|
||||||
|
const React = require('react');
|
||||||
|
const _ = require('lodash');
|
||||||
|
const cx = require('classnames');
|
||||||
|
const request = require('superagent');
|
||||||
|
|
||||||
|
const BrewTable = require('../brewTable/brewTable.jsx');
|
||||||
|
|
||||||
|
const LIMIT = 10;
|
||||||
|
|
||||||
|
const AdminSearch = React.createClass({
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
adminKey : '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getInitialState: function() {
|
||||||
|
return {
|
||||||
|
totalBrews : 1,
|
||||||
|
brews: [],
|
||||||
|
|
||||||
|
searching : false,
|
||||||
|
error : null,
|
||||||
|
|
||||||
|
page : 1,
|
||||||
|
searchTerms : ''
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
handleSearch : function(e){
|
||||||
|
this.setState({
|
||||||
|
searchTerms : e.target.value
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handlePage : function(e){
|
||||||
|
this.setState({
|
||||||
|
page : e.target.value
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
search : function(){
|
||||||
|
this.setState({ searching : true, error : null });
|
||||||
|
|
||||||
|
request.get(`/api/brew`)
|
||||||
|
.query({
|
||||||
|
terms : this.state.searchTerms,
|
||||||
|
limit : LIMIT,
|
||||||
|
page : this.state.page - 1
|
||||||
|
})
|
||||||
|
.set('x-homebrew-admin', this.props.adminKey)
|
||||||
|
.end((err, res) => {
|
||||||
|
if(err){
|
||||||
|
this.setState({
|
||||||
|
searching : false,
|
||||||
|
error : err && err.toString()
|
||||||
|
});
|
||||||
|
}else{
|
||||||
|
this.setState({
|
||||||
|
brews : res.body.brews,
|
||||||
|
totalBrews : res.body.total
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function(){
|
||||||
|
return <div className='adminSearch'>
|
||||||
|
<h1>Admin Search</h1>
|
||||||
|
<div className='controls'>
|
||||||
|
<input className='search' type='text' value={this.state.searchTerms} onChange={this.handleSearch} />
|
||||||
|
|
||||||
|
<button onClick={this.search}> <i className='fa fa-search' /> search </button>
|
||||||
|
|
||||||
|
|
||||||
|
<div className='page'>
|
||||||
|
page:
|
||||||
|
<input type='text' value={this.state.page} onChange={this.handlePage} />
|
||||||
|
/ {Math.ceil(this.state.totalBrews / LIMIT)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<BrewTable brews={this.state.brews} />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = AdminSearch;
|
||||||
17
client/admin/adminSearch/adminSearch.less
Normal file
17
client/admin/adminSearch/adminSearch.less
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
|
||||||
|
.adminSearch{
|
||||||
|
.controls{
|
||||||
|
margin-bottom : 20px;
|
||||||
|
input.search{
|
||||||
|
height : 33px;
|
||||||
|
padding : 10px;
|
||||||
|
}
|
||||||
|
.page {
|
||||||
|
float : right;
|
||||||
|
font-weight : 800;
|
||||||
|
input{
|
||||||
|
width : 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,8 +3,7 @@ const _ = require('lodash');
|
|||||||
const cx = require('classnames');
|
const cx = require('classnames');
|
||||||
|
|
||||||
const request = require('superagent');
|
const request = require('superagent');
|
||||||
const Moment = require('moment');
|
const BrewTable = require('../brewTable/brewTable.jsx');
|
||||||
|
|
||||||
|
|
||||||
const BrewLookup = React.createClass({
|
const BrewLookup = React.createClass({
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
@@ -16,7 +15,8 @@ const BrewLookup = React.createClass({
|
|||||||
return {
|
return {
|
||||||
query:'',
|
query:'',
|
||||||
resultBrew : null,
|
resultBrew : null,
|
||||||
searching : false
|
searching : false,
|
||||||
|
error : null
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -26,13 +26,14 @@ const BrewLookup = React.createClass({
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
lookup : function(){
|
lookup : function(){
|
||||||
this.setState({ searching : true });
|
this.setState({ searching : true, error : null });
|
||||||
|
|
||||||
request.get(`/admin/lookup/${this.state.query}`)
|
request.get(`/admin/lookup/${this.state.query}`)
|
||||||
.query({ admin_key : this.props.adminKey })
|
.set('x-homebrew-admin', this.props.adminKey)
|
||||||
.end((err, res) => {
|
.end((err, res) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
searching : false,
|
searching : false,
|
||||||
|
error : err && err.toString(),
|
||||||
resultBrew : (err ? null : res.body)
|
resultBrew : (err ? null : res.body)
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
@@ -42,14 +43,31 @@ const BrewLookup = React.createClass({
|
|||||||
if(this.state.searching) return <div className='searching'><i className='fa fa-spin fa-spinner' /></div>;
|
if(this.state.searching) return <div className='searching'><i className='fa fa-spin fa-spinner' /></div>;
|
||||||
if(!this.state.resultBrew) return <div className='noBrew'>No brew found.</div>;
|
if(!this.state.resultBrew) return <div className='noBrew'>No brew found.</div>;
|
||||||
|
|
||||||
|
return <BrewTable brews={[this.state.resultBrew ]} />
|
||||||
|
|
||||||
|
/*
|
||||||
const brew = this.state.resultBrew;
|
const brew = this.state.resultBrew;
|
||||||
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'>{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'>{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 className='deleteButton'>
|
||||||
|
<i className='fa fa-trash' />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
*/
|
||||||
|
},
|
||||||
|
|
||||||
|
renderError : function(){
|
||||||
|
if(!this.state.error) return;
|
||||||
|
|
||||||
|
return <div className='error'>
|
||||||
|
{this.state.error}
|
||||||
</div>
|
</div>
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -60,6 +78,7 @@ const BrewLookup = React.createClass({
|
|||||||
<button onClick={this.lookup}><i className='fa fa-search'/></button>
|
<button onClick={this.lookup}><i className='fa fa-search'/></button>
|
||||||
|
|
||||||
{this.renderFoundBrew()}
|
{this.renderFoundBrew()}
|
||||||
|
{this.renderError()}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
13
client/admin/brewLookup/brewLookup.less
Normal file
13
client/admin/brewLookup/brewLookup.less
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
|
||||||
|
.brewLookup{
|
||||||
|
height : 200px;
|
||||||
|
input{
|
||||||
|
height : 33px;
|
||||||
|
margin-bottom : 20px;
|
||||||
|
padding : 0px 10px;
|
||||||
|
}
|
||||||
|
.error{
|
||||||
|
font-weight : 800;
|
||||||
|
color : @red;
|
||||||
|
}
|
||||||
|
}
|
||||||
54
client/admin/brewTable/brewTable.jsx
Normal file
54
client/admin/brewTable/brewTable.jsx
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
const React = require('react');
|
||||||
|
const _ = require('lodash');
|
||||||
|
const cx = require('classnames');
|
||||||
|
|
||||||
|
const Moment = require('moment');
|
||||||
|
|
||||||
|
//TODO: Add in delete
|
||||||
|
|
||||||
|
const BrewTable = React.createClass({
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
brews : []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
renderRows : function(){
|
||||||
|
return _.map(this.props.brews, (brew) => {
|
||||||
|
let authors = 'None.';
|
||||||
|
if(brew.authors && brew.authors.length) authors = brew.authors.join(', ');
|
||||||
|
|
||||||
|
return <tr className={cx('brewRow', {'isEmpty' : brew.text == "false"})} key={brew.shareId || brew}>
|
||||||
|
<td>{brew.title}</td>
|
||||||
|
<td>{authors}</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>{Moment(brew.updatedAt).fromNow()}</td>
|
||||||
|
<td>{brew.views}</td>
|
||||||
|
<td className='deleteButton'>
|
||||||
|
<i className='fa fa-trash' />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
});
|
||||||
|
},
|
||||||
|
render: function(){
|
||||||
|
return <table className='brewTable'>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Title</th>
|
||||||
|
<th>Authors</th>
|
||||||
|
<th>Edit Link</th>
|
||||||
|
<th>Share Link</th>
|
||||||
|
<th>Last Updated</th>
|
||||||
|
<th>Views</th>
|
||||||
|
<th>Remove</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{this.renderRows()}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = BrewTable;
|
||||||
44
client/admin/brewTable/brewTable.less
Normal file
44
client/admin/brewTable/brewTable.less
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
|
||||||
|
table.brewTable{
|
||||||
|
th{
|
||||||
|
padding : 10px;
|
||||||
|
background-color : fade(@blue, 20%);
|
||||||
|
font-weight : 800;
|
||||||
|
}
|
||||||
|
tr:nth-child(even){
|
||||||
|
background-color : fade(@green, 10%);
|
||||||
|
}
|
||||||
|
tr.isEmpty{
|
||||||
|
background-color : fade(@red, 30%);
|
||||||
|
}
|
||||||
|
td{
|
||||||
|
min-width : 100px;
|
||||||
|
padding : 10px;
|
||||||
|
text-align : center;
|
||||||
|
|
||||||
|
/*
|
||||||
|
&.preview{
|
||||||
|
position : relative;
|
||||||
|
&:hover{
|
||||||
|
.content{
|
||||||
|
display : block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.content{
|
||||||
|
position : absolute;
|
||||||
|
display : none;
|
||||||
|
top : 100%;
|
||||||
|
left : 0px;
|
||||||
|
z-index : 1000;
|
||||||
|
max-height : 500px;
|
||||||
|
width : 300px;
|
||||||
|
padding : 30px;
|
||||||
|
background-color : white;
|
||||||
|
font-family : monospace;
|
||||||
|
text-align : left;
|
||||||
|
pointer-events : none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
.brewLookup{
|
|
||||||
height : 200px;
|
|
||||||
input{
|
|
||||||
height : 33px;
|
|
||||||
padding : 0px 10px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
var React = require('react');
|
|
||||||
var _ = require('lodash');
|
|
||||||
var cx = require('classnames');
|
|
||||||
|
|
||||||
var request = require('superagent');
|
|
||||||
|
|
||||||
var BrewSearch = React.createClass({
|
|
||||||
|
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
admin_key : ''
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
getInitialState: function() {
|
|
||||||
return {
|
|
||||||
searchTerm: '',
|
|
||||||
brew : null,
|
|
||||||
searching : false
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
search : function(){
|
|
||||||
this.setState({
|
|
||||||
searching : true
|
|
||||||
});
|
|
||||||
|
|
||||||
request.get('/homebrew/api/search?id=' + this.state.searchTerm)
|
|
||||||
.query({
|
|
||||||
admin_key : this.props.admin_key,
|
|
||||||
})
|
|
||||||
.end((err, res)=>{
|
|
||||||
console.log(err, res, res.body.brews[0]);
|
|
||||||
this.setState({
|
|
||||||
brew : res.body.brews[0],
|
|
||||||
|
|
||||||
searching : false
|
|
||||||
})
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
handleChange : function(e){
|
|
||||||
this.setState({
|
|
||||||
searchTerm : e.target.value
|
|
||||||
});
|
|
||||||
},
|
|
||||||
handleSearchClick : function(){
|
|
||||||
this.search();
|
|
||||||
},
|
|
||||||
|
|
||||||
renderBrew : function(){
|
|
||||||
if(!this.state.brew) return null;
|
|
||||||
return <div className='brew'>
|
|
||||||
<div>Edit id : {this.state.brew.editId}</div>
|
|
||||||
<div>Share id : {this.state.brew.shareId}</div>
|
|
||||||
</div>
|
|
||||||
},
|
|
||||||
|
|
||||||
render : function(){
|
|
||||||
return <div className='search'>
|
|
||||||
<input type='text' value={this.state.searchTerm} onChange={this.handleChange} />
|
|
||||||
|
|
||||||
<button onClick={this.handleSearchClick}>Search</button>
|
|
||||||
|
|
||||||
{this.renderBrew()}
|
|
||||||
</div>
|
|
||||||
},
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = BrewSearch;
|
|
||||||
@@ -1,172 +0,0 @@
|
|||||||
var React = require('react');
|
|
||||||
var _ = require('lodash');
|
|
||||||
var cx = require('classnames');
|
|
||||||
var request = require('superagent');
|
|
||||||
|
|
||||||
var Moment = require('moment');
|
|
||||||
|
|
||||||
var BrewSearch = require('./brewSearch.jsx');
|
|
||||||
|
|
||||||
var BrewLookup = require('./brewLookup/brewLookup.jsx');
|
|
||||||
|
|
||||||
|
|
||||||
var HomebrewAdmin = React.createClass({
|
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
admin_key : ''
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
getInitialState: function() {
|
|
||||||
return {
|
|
||||||
page: 0,
|
|
||||||
count : 20,
|
|
||||||
brewCache : {},
|
|
||||||
total : 0,
|
|
||||||
|
|
||||||
processingOldBrews : false
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
fetchBrews : function(page){
|
|
||||||
request.get('/api/search')
|
|
||||||
.query({
|
|
||||||
admin_key : this.props.admin_key,
|
|
||||||
count : this.state.count,
|
|
||||||
page : page
|
|
||||||
})
|
|
||||||
.end((err, res)=>{
|
|
||||||
if(err || !res.body || !res.body.brews) return;
|
|
||||||
this.state.brewCache[page] = res.body.brews;
|
|
||||||
this.setState({
|
|
||||||
brewCache : this.state.brewCache,
|
|
||||||
total : res.body.total,
|
|
||||||
count : res.body.count
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
componentDidMount: function() {
|
|
||||||
this.fetchBrews(this.state.page);
|
|
||||||
},
|
|
||||||
|
|
||||||
changePageTo : function(page){
|
|
||||||
if(!this.state.brewCache[page]){
|
|
||||||
this.fetchBrews(page);
|
|
||||||
}
|
|
||||||
this.setState({
|
|
||||||
page : page
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
clearInvalidBrews : function(){
|
|
||||||
request.get('/api/invalid')
|
|
||||||
.query({admin_key : this.props.admin_key})
|
|
||||||
.end((err, res)=>{
|
|
||||||
if(!confirm("This will remove " + res.body.count + " brews. Are you sure?")) return;
|
|
||||||
request.get('/api/invalid')
|
|
||||||
.query({admin_key : this.props.admin_key, do_it : true})
|
|
||||||
.end((err, res)=>{
|
|
||||||
alert("Done!")
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
deleteBrew : function(brewId){
|
|
||||||
if(!confirm("Are you sure you want to delete '" + brewId + "'?")) return;
|
|
||||||
request.get('/api/remove/' + brewId)
|
|
||||||
.query({admin_key : this.props.admin_key})
|
|
||||||
.end(function(err, res){
|
|
||||||
window.location.reload();
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
handlePageChange : function(dir){
|
|
||||||
this.changePageTo(this.state.page + dir);
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
renderPagnination : function(){
|
|
||||||
var outOf;
|
|
||||||
if(this.state.total){
|
|
||||||
outOf = this.state.page + ' / ' + Math.round(this.state.total/this.state.count);
|
|
||||||
}
|
|
||||||
return <div className='pagnination'>
|
|
||||||
<i className='fa fa-chevron-left' onClick={this.handlePageChange.bind(this, -1)}/>
|
|
||||||
{outOf}
|
|
||||||
<i className='fa fa-chevron-right' onClick={this.handlePageChange.bind(this, 1)}/>
|
|
||||||
</div>
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
renderBrews : function(){
|
|
||||||
var brews = this.state.brewCache[this.state.page] || _.times(this.state.count);
|
|
||||||
return _.map(brews, (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={'/share/' + brew.shareId} target='_blank'>{brew.shareId}</a></td>
|
|
||||||
<td>{Moment(brew.createdAt).fromNow()}</td>
|
|
||||||
<td>{Moment(brew.updatedAt).fromNow()}</td>
|
|
||||||
<td>{Moment(brew.lastViewed).fromNow()}</td>
|
|
||||||
<td>{brew.views}</td>
|
|
||||||
<td>
|
|
||||||
<div className='deleteButton' onClick={this.deleteBrew.bind(this, brew.editId)}>
|
|
||||||
<i className='fa fa-trash' />
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
renderBrewTable : function(){
|
|
||||||
return <div className='brewTable'>
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Edit Id</th>
|
|
||||||
<th>Share Id</th>
|
|
||||||
<th>Created At</th>
|
|
||||||
<th>Last Updated</th>
|
|
||||||
<th>Last Viewed</th>
|
|
||||||
<th>Views</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{this.renderBrews()}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
},
|
|
||||||
|
|
||||||
render : function(){
|
|
||||||
var self = this;
|
|
||||||
return <div className='homebrewAdmin'>
|
|
||||||
|
|
||||||
<BrewLookup adminKey={this.props.admin_key} />
|
|
||||||
|
|
||||||
{/*
|
|
||||||
<h2>
|
|
||||||
Homebrews - {this.state.total}
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{this.renderPagnination()}
|
|
||||||
{this.renderBrewTable()}
|
|
||||||
|
|
||||||
<button className='clearOldButton' onClick={this.clearInvalidBrews}>
|
|
||||||
Clear Old
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<BrewSearch admin_key={this.props.admin_key} />
|
|
||||||
*/}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = HomebrewAdmin;
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
|
|
||||||
.homebrewAdmin{
|
|
||||||
margin-bottom: 80px;
|
|
||||||
.brewTable{
|
|
||||||
table{
|
|
||||||
|
|
||||||
th{
|
|
||||||
padding : 10px;
|
|
||||||
font-weight : 800;
|
|
||||||
}
|
|
||||||
tr:nth-child(even){
|
|
||||||
background-color : fade(@green, 10%);
|
|
||||||
}
|
|
||||||
tr.isEmpty{
|
|
||||||
background-color : fade(@red, 30%);
|
|
||||||
}
|
|
||||||
td{
|
|
||||||
min-width : 100px;
|
|
||||||
padding : 10px;
|
|
||||||
text-align : center;
|
|
||||||
|
|
||||||
&.preview{
|
|
||||||
position : relative;
|
|
||||||
&:hover{
|
|
||||||
.content{
|
|
||||||
display : block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.content{
|
|
||||||
position : absolute;
|
|
||||||
display : none;
|
|
||||||
top : 100%;
|
|
||||||
left : 0px;
|
|
||||||
z-index : 1000;
|
|
||||||
max-height : 500px;
|
|
||||||
width : 300px;
|
|
||||||
padding : 30px;
|
|
||||||
background-color : white;
|
|
||||||
font-family : monospace;
|
|
||||||
text-align : left;
|
|
||||||
pointer-events : none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.deleteButton{
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
button.clearOldButton{
|
|
||||||
float : right;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
54
client/admin/invalidBrew/invalidBrew.jsx
Normal file
54
client/admin/invalidBrew/invalidBrew.jsx
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
const React = require('react');
|
||||||
|
const _ = require('lodash');
|
||||||
|
const cx = require('classnames');
|
||||||
|
|
||||||
|
const request = require('superagent');
|
||||||
|
const BrewTable = require('../brewTable/brewTable.jsx');
|
||||||
|
|
||||||
|
|
||||||
|
const InvalidBrew = React.createClass({
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
adminKey : '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getInitialState: function() {
|
||||||
|
return {
|
||||||
|
brews: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getInvalid : function(){
|
||||||
|
request.get(`/admin/invalid`)
|
||||||
|
.set('x-homebrew-admin', this.props.adminKey)
|
||||||
|
.end((err, res) => {
|
||||||
|
this.setState({
|
||||||
|
brews : res.body
|
||||||
|
});
|
||||||
|
})
|
||||||
|
},
|
||||||
|
removeInvalid : function(){
|
||||||
|
if(!this.state.brews.length) return;
|
||||||
|
if(!confirm(`Are you sure you want to remove ${this.state.brews.length} brews`)) return;
|
||||||
|
if(!confirm('Sure you are sure?')) return;
|
||||||
|
|
||||||
|
request.delete(`/admin/invalid`)
|
||||||
|
.set('x-homebrew-admin', this.props.adminKey)
|
||||||
|
.end((err, res) => {
|
||||||
|
console.log(err, res.body);
|
||||||
|
alert('Invalid brews removed!');
|
||||||
|
this.getInvalid();
|
||||||
|
})
|
||||||
|
},
|
||||||
|
render: function(){
|
||||||
|
return <div className='invalidBrew'>
|
||||||
|
<h1>Remove Invalid Brews</h1>
|
||||||
|
<div>This will removes all brews older than 3 days and shorter than a tweet.</div>
|
||||||
|
<button className='get' onClick={this.getInvalid}> Get Invalid Brews</button>
|
||||||
|
<button className='remove' disabled={this.state.brews.length == 0} onClick={this.removeInvalid}> Remove invalid Brews</button>
|
||||||
|
|
||||||
|
<BrewTable brews={this.state.brews} />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = InvalidBrew;
|
||||||
5
client/admin/invalidBrew/invalidBrew.less
Normal file
5
client/admin/invalidBrew/invalidBrew.less
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
.invalidBrew{
|
||||||
|
button{
|
||||||
|
margin: 10px 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,145 +0,0 @@
|
|||||||
const React = require('react');
|
|
||||||
const _ = require('lodash');
|
|
||||||
const cx = require('classnames');
|
|
||||||
|
|
||||||
const CodeEditor = require('naturalcrit/codeEditor/codeEditor.jsx');
|
|
||||||
const SnippetBar = require('./snippetbar/snippetbar.jsx');
|
|
||||||
const MetadataEditor = require('./metadataEditor/metadataEditor.jsx');
|
|
||||||
|
|
||||||
|
|
||||||
const splice = function(str, index, inject){
|
|
||||||
return str.slice(0, index) + inject + str.slice(index);
|
|
||||||
};
|
|
||||||
|
|
||||||
const SNIPPETBAR_HEIGHT = 25;
|
|
||||||
|
|
||||||
const Editor = React.createClass({
|
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
value : '',
|
|
||||||
onChange : ()=>{},
|
|
||||||
|
|
||||||
metadata : {},
|
|
||||||
onMetadataChange : ()=>{},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
getInitialState: function() {
|
|
||||||
return {
|
|
||||||
showMetadataEditor: false
|
|
||||||
};
|
|
||||||
},
|
|
||||||
cursorPosition : {
|
|
||||||
line : 0,
|
|
||||||
ch : 0
|
|
||||||
},
|
|
||||||
|
|
||||||
componentDidMount: function() {
|
|
||||||
this.updateEditorSize();
|
|
||||||
this.highlightPageLines();
|
|
||||||
window.addEventListener("resize", this.updateEditorSize);
|
|
||||||
},
|
|
||||||
componentWillUnmount: function() {
|
|
||||||
window.removeEventListener("resize", this.updateEditorSize);
|
|
||||||
},
|
|
||||||
|
|
||||||
updateEditorSize : function() {
|
|
||||||
let paneHeight = this.refs.main.parentNode.clientHeight;
|
|
||||||
paneHeight -= SNIPPETBAR_HEIGHT + 1;
|
|
||||||
this.refs.codeEditor.codeMirror.setSize(null, paneHeight);
|
|
||||||
},
|
|
||||||
|
|
||||||
handleTextChange : function(text){
|
|
||||||
this.props.onChange(text);
|
|
||||||
},
|
|
||||||
handleCursorActivty : function(curpos){
|
|
||||||
this.cursorPosition = curpos;
|
|
||||||
},
|
|
||||||
handleInject : function(injectText){
|
|
||||||
const lines = this.props.value.split('\n');
|
|
||||||
lines[this.cursorPosition.line] = splice(lines[this.cursorPosition.line], this.cursorPosition.ch, injectText);
|
|
||||||
|
|
||||||
this.handleTextChange(lines.join('\n'));
|
|
||||||
this.refs.codeEditor.setCursorPosition(this.cursorPosition.line, this.cursorPosition.ch + injectText.length);
|
|
||||||
},
|
|
||||||
handgleToggle : function(){
|
|
||||||
this.setState({
|
|
||||||
showMetadataEditor : !this.state.showMetadataEditor
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
getCurrentPage : function(){
|
|
||||||
const lines = this.props.value.split('\n').slice(0, this.cursorPosition.line + 1);
|
|
||||||
return _.reduce(lines, (r, line) => {
|
|
||||||
if(line.indexOf('\\page') !== -1) r++;
|
|
||||||
return r;
|
|
||||||
}, 1);
|
|
||||||
},
|
|
||||||
|
|
||||||
highlightPageLines : function(){
|
|
||||||
if(!this.refs.codeEditor) return;
|
|
||||||
const codeMirror = this.refs.codeEditor.codeMirror;
|
|
||||||
|
|
||||||
const lineNumbers = _.reduce(this.props.value.split('\n'), (r, line, lineNumber)=>{
|
|
||||||
if(line.indexOf('\\page') !== -1){
|
|
||||||
codeMirror.addLineClass(lineNumber, 'background', 'pageLine');
|
|
||||||
r.push(lineNumber);
|
|
||||||
}
|
|
||||||
return r;
|
|
||||||
}, []);
|
|
||||||
return lineNumbers
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
brewJump : function(){
|
|
||||||
const currentPage = this.getCurrentPage();
|
|
||||||
window.location.hash = 'p' + currentPage;
|
|
||||||
},
|
|
||||||
|
|
||||||
//Called when there are changes to the editor's dimensions
|
|
||||||
update : function(){
|
|
||||||
this.refs.codeEditor.updateSize();
|
|
||||||
},
|
|
||||||
|
|
||||||
renderMetadataEditor : function(){
|
|
||||||
if(!this.state.showMetadataEditor) return;
|
|
||||||
return <MetadataEditor
|
|
||||||
metadata={this.props.metadata}
|
|
||||||
onChange={this.props.onMetadataChange}
|
|
||||||
/>
|
|
||||||
},
|
|
||||||
|
|
||||||
render : function(){
|
|
||||||
this.highlightPageLines();
|
|
||||||
return(
|
|
||||||
<div className='editor' ref='main'>
|
|
||||||
<SnippetBar
|
|
||||||
brew={this.props.value}
|
|
||||||
onInject={this.handleInject}
|
|
||||||
onToggle={this.handgleToggle}
|
|
||||||
showmeta={this.state.showMetadataEditor} />
|
|
||||||
{this.renderMetadataEditor()}
|
|
||||||
<CodeEditor
|
|
||||||
ref='codeEditor'
|
|
||||||
wrap={true}
|
|
||||||
language='gfm'
|
|
||||||
value={this.props.value}
|
|
||||||
onChange={this.handleTextChange}
|
|
||||||
onCursorActivity={this.handleCursorActivty} />
|
|
||||||
|
|
||||||
{/*
|
|
||||||
<div className='brewJump' onClick={this.brewJump}>
|
|
||||||
<i className='fa fa-arrow-right' />
|
|
||||||
</div>
|
|
||||||
*/}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = Editor;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
const React = require('react');
|
|
||||||
const _ = require('lodash');
|
|
||||||
const cx = require('classnames');
|
|
||||||
|
|
||||||
|
|
||||||
const Snippets = require('./snippets/snippets.js');
|
|
||||||
|
|
||||||
const execute = function(val, brew){
|
|
||||||
if(_.isFunction(val)) return val(brew);
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const Snippetbar = React.createClass({
|
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
brew : '',
|
|
||||||
onInject : ()=>{},
|
|
||||||
onToggle : ()=>{},
|
|
||||||
showmeta : false
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
handleSnippetClick : function(injectedText){
|
|
||||||
this.props.onInject(injectedText)
|
|
||||||
},
|
|
||||||
|
|
||||||
renderSnippetGroups : function(){
|
|
||||||
return _.map(Snippets, (snippetGroup)=>{
|
|
||||||
return <SnippetGroup
|
|
||||||
brew={this.props.brew}
|
|
||||||
groupName={snippetGroup.groupName}
|
|
||||||
icon={snippetGroup.icon}
|
|
||||||
snippets={snippetGroup.snippets}
|
|
||||||
key={snippetGroup.groupName}
|
|
||||||
onSnippetClick={this.handleSnippetClick}
|
|
||||||
/>
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
render : function(){
|
|
||||||
return <div className='snippetBar'>
|
|
||||||
{this.renderSnippetGroups()}
|
|
||||||
<div className={cx('toggleMeta', {selected: this.props.showmeta})}
|
|
||||||
onClick={this.props.onToggle}>
|
|
||||||
<i className='fa fa-bars' />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = Snippetbar;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const SnippetGroup = React.createClass({
|
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
brew : '',
|
|
||||||
groupName : '',
|
|
||||||
icon : 'fa-rocket',
|
|
||||||
snippets : [],
|
|
||||||
onSnippetClick : function(){},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
handleSnippetClick : function(snippet){
|
|
||||||
this.props.onSnippetClick(execute(snippet.gen, this.props.brew));
|
|
||||||
},
|
|
||||||
renderSnippets : function(){
|
|
||||||
return _.map(this.props.snippets, (snippet)=>{
|
|
||||||
return <div className='snippet' key={snippet.name} onClick={this.handleSnippetClick.bind(this, snippet)}>
|
|
||||||
<i className={'fa fa-fw ' + snippet.icon} />
|
|
||||||
{snippet.name}
|
|
||||||
</div>
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
render : function(){
|
|
||||||
return <div className='snippetGroup'>
|
|
||||||
<div className='text'>
|
|
||||||
<i className={'fa fa-fw ' + this.props.icon} />
|
|
||||||
<span className='groupName'>{this.props.groupName}</span>
|
|
||||||
</div>
|
|
||||||
<div className='dropdown'>
|
|
||||||
{this.renderSnippets()}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
},
|
|
||||||
|
|
||||||
});
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
|
|
||||||
.snippetBar{
|
|
||||||
@height : 25px;
|
|
||||||
position : relative;
|
|
||||||
height : @height;
|
|
||||||
background-color : #ddd;
|
|
||||||
.toggleMeta{
|
|
||||||
position : absolute;
|
|
||||||
top : 0px;
|
|
||||||
right : 0px;
|
|
||||||
height : @height;
|
|
||||||
width : @height;
|
|
||||||
cursor : pointer;
|
|
||||||
line-height : @height;
|
|
||||||
text-align : center;
|
|
||||||
.tooltipLeft("Edit Brew Metadata");
|
|
||||||
&:hover, &.selected{
|
|
||||||
background-color : #999;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.snippetGroup{
|
|
||||||
display : inline-block;
|
|
||||||
height : @height;
|
|
||||||
padding : 0px 5px;
|
|
||||||
cursor : pointer;
|
|
||||||
font-size : 0.6em;
|
|
||||||
font-weight : 800;
|
|
||||||
line-height : @height;
|
|
||||||
text-transform : uppercase;
|
|
||||||
border-right : 1px solid black;
|
|
||||||
i{
|
|
||||||
vertical-align : middle;
|
|
||||||
margin-right : 3px;
|
|
||||||
font-size : 1.2em;
|
|
||||||
}
|
|
||||||
&:hover, &.selected{
|
|
||||||
background-color : #999;
|
|
||||||
}
|
|
||||||
.text{
|
|
||||||
line-height : @height;
|
|
||||||
.groupName{
|
|
||||||
font-size : 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&:hover{
|
|
||||||
.dropdown{
|
|
||||||
visibility : visible;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.dropdown{
|
|
||||||
position : absolute;
|
|
||||||
top : 100%;
|
|
||||||
visibility : hidden;
|
|
||||||
z-index : 1000;
|
|
||||||
margin-left : -5px;
|
|
||||||
padding : 0px;
|
|
||||||
background-color : #ddd;
|
|
||||||
.snippet{
|
|
||||||
.animate(background-color);
|
|
||||||
padding : 5px;
|
|
||||||
cursor : pointer;
|
|
||||||
font-size : 10px;
|
|
||||||
i{
|
|
||||||
margin-right : 8px;
|
|
||||||
font-size : 13px;
|
|
||||||
}
|
|
||||||
&:hover{
|
|
||||||
background-color : #999;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,13 +3,15 @@ const _ = require('lodash');
|
|||||||
const cx = require('classnames');
|
const cx = require('classnames');
|
||||||
|
|
||||||
const CreateRouter = require('pico-router').createRouter;
|
const CreateRouter = require('pico-router').createRouter;
|
||||||
|
const BrewActions = require('homebrewery/brew.actions.js');
|
||||||
|
const AccountActions = require('homebrewery/account.actions.js');
|
||||||
|
|
||||||
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;
|
||||||
@@ -17,45 +19,27 @@ const Homebrew = React.createClass({
|
|||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
return {
|
return {
|
||||||
url : '',
|
url : '',
|
||||||
welcomeText : '',
|
|
||||||
changelog : '',
|
|
||||||
version : '0.0.0',
|
version : '0.0.0',
|
||||||
account : null,
|
loginPath : '',
|
||||||
brew : {
|
|
||||||
title : '',
|
user : undefined,
|
||||||
text : '',
|
brew : undefined,
|
||||||
shareId : null,
|
brews : []
|
||||||
editId : null,
|
|
||||||
createdAt : null,
|
|
||||||
updatedAt : null,
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
global.account = this.props.account;
|
BrewActions.init({
|
||||||
global.version = this.props.version;
|
version : this.props.version,
|
||||||
|
brew : this.props.brew
|
||||||
|
});
|
||||||
|
AccountActions.init({
|
||||||
|
user : this.props.user,
|
||||||
|
loginPath : this.props.loginPath
|
||||||
|
});
|
||||||
|
|
||||||
Router = CreateRouter({
|
Router = CreateRouter({
|
||||||
'/edit/:id' : (args) => {
|
'/edit/:id' : <EditPage />,
|
||||||
if(!this.props.brew.editId){
|
'/share/:id' : <SharePage />,
|
||||||
return <ErrorPage errorId={args.id}/>
|
|
||||||
}
|
|
||||||
|
|
||||||
return <EditPage
|
|
||||||
id={args.id}
|
|
||||||
brew={this.props.brew} />
|
|
||||||
},
|
|
||||||
|
|
||||||
'/share/:id' : (args) => {
|
|
||||||
if(!this.props.brew.shareId){
|
|
||||||
return <ErrorPage errorId={args.id}/>
|
|
||||||
}
|
|
||||||
|
|
||||||
return <SharePage
|
|
||||||
id={args.id}
|
|
||||||
brew={this.props.brew} />
|
|
||||||
},
|
|
||||||
'/user/:username' : (args) => {
|
'/user/:username' : (args) => {
|
||||||
return <UserPage
|
return <UserPage
|
||||||
username={args.username}
|
username={args.username}
|
||||||
@@ -68,15 +52,15 @@ const Homebrew = React.createClass({
|
|||||||
'/print' : (args, query) => {
|
'/print' : (args, query) => {
|
||||||
return <PrintPage query={query}/>;
|
return <PrintPage query={query}/>;
|
||||||
},
|
},
|
||||||
'/new' : (args) => {
|
'/new' : <NewPage />,
|
||||||
return <NewPage />
|
|
||||||
},
|
|
||||||
'/changelog' : (args) => {
|
'/changelog' : <SharePage />,
|
||||||
return <SharePage
|
'/test' : <SharePage />,
|
||||||
brew={{title : 'Changelog', text : this.props.changelog}} />
|
'/test_old' : <SharePage />,
|
||||||
},
|
|
||||||
'*' : <HomePage
|
|
||||||
welcomeText={this.props.welcomeText} />,
|
'*' : <HomePage />,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
render : function(){
|
render : function(){
|
||||||
|
|||||||
@@ -1,17 +1,23 @@
|
|||||||
const React = require('react');
|
const React = require('react');
|
||||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
|
|
||||||
|
const Store = require('homebrewery/account.store.js');
|
||||||
|
const Actions = require('homebrewery/account.actions.js');
|
||||||
|
|
||||||
|
|
||||||
module.exports = function(props){
|
module.exports = function(props){
|
||||||
if(global.account){
|
const user = Store.getUser();
|
||||||
return <Nav.item href={`/user/${global.account.username}`} color='yellow' icon='fa-user'>
|
if(user && user == props.userPage){
|
||||||
{global.account.username}
|
return <Nav.item onClick={Actions.logout} color='yellow' icon='fa-user-times'>
|
||||||
|
logout
|
||||||
</Nav.item>
|
</Nav.item>
|
||||||
}
|
}
|
||||||
let url = '';
|
if(user){
|
||||||
if(typeof window !== 'undefined'){
|
return <Nav.item href={`/user/${user}`} color='yellow' icon='fa-user'>
|
||||||
url = window.location.href
|
{user}
|
||||||
|
</Nav.item>
|
||||||
}
|
}
|
||||||
return <Nav.item href={`http://naturalcrit.com/login?redirect=${url}`} color='teal' icon='fa-sign-in'>
|
return <Nav.item onClick={Actions.login} color='teal' icon='fa-sign-in'>
|
||||||
login
|
login
|
||||||
</Nav.item>
|
</Nav.item>
|
||||||
};
|
};
|
||||||
9
client/homebrew/navbar/brewTitle.navitem.jsx
Normal file
9
client/homebrew/navbar/brewTitle.navitem.jsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
const React = require('react');
|
||||||
|
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
|
const Store = require('homebrewery/brew.store.js');
|
||||||
|
|
||||||
|
module.exports = Store.createSmartComponent((props) => {
|
||||||
|
return <Nav.item className='brewTitle'>{props.title}</Nav.item>
|
||||||
|
}, (props) => {
|
||||||
|
return {title : Store.getMetaData().title};
|
||||||
|
})
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
const flux = require('pico-flux')
|
||||||
|
const React = require('react');
|
||||||
|
const _ = require('lodash');
|
||||||
|
const cx = require('classnames');
|
||||||
|
|
||||||
|
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
|
|
||||||
|
const Store = require('homebrewery/brew.store.js');
|
||||||
|
const Actions = require('homebrewery/brew.actions.js');
|
||||||
|
|
||||||
|
const onStoreChange = () => {
|
||||||
|
return {
|
||||||
|
status : Store.getStatus(),
|
||||||
|
errors : Store.getErrors()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const ContinousSave = React.createClass({
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
status : 'ready',
|
||||||
|
errors : undefined
|
||||||
|
};
|
||||||
|
},
|
||||||
|
componentDidMount: function() {
|
||||||
|
flux.actionEmitter.on('dispatch', this.actionHandler);
|
||||||
|
window.onbeforeunload = ()=>{
|
||||||
|
if(this.props.status !== 'ready') return 'You have unsaved changes!';
|
||||||
|
};
|
||||||
|
},
|
||||||
|
componentWillUnmount: function() {
|
||||||
|
flux.actionEmitter.removeListener('dispatch', this.actionHandler);
|
||||||
|
window.onbeforeunload = function(){};
|
||||||
|
},
|
||||||
|
actionHandler : function(actionType){
|
||||||
|
if(actionType == 'UPDATE_BREW_TEXT' || actionType == 'UPDATE_META'){
|
||||||
|
Actions.pendingSave();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleClick : function(){
|
||||||
|
Actions.save();
|
||||||
|
},
|
||||||
|
renderError : function(){
|
||||||
|
let errMsg = '';
|
||||||
|
try{
|
||||||
|
errMsg += this.state.errors.toString() + '\n\n';
|
||||||
|
errMsg += '```\n' + JSON.stringify(this.state.errors.response.error, null, ' ') + '\n```';
|
||||||
|
}catch(e){}
|
||||||
|
|
||||||
|
return <Nav.item className='continousSave error' icon="fa-warning">
|
||||||
|
Oops!
|
||||||
|
<div className='errorContainer'>
|
||||||
|
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)}>
|
||||||
|
here
|
||||||
|
</a>.
|
||||||
|
</div>
|
||||||
|
</Nav.item>
|
||||||
|
},
|
||||||
|
render : function(){
|
||||||
|
if(this.props.status == 'error') return this.renderError();
|
||||||
|
|
||||||
|
if(this.props.status == 'saving'){
|
||||||
|
return <Nav.item className='continousSave' icon="fa-spinner fa-spin">saving...</Nav.item>
|
||||||
|
}
|
||||||
|
if(this.props.status == 'pending'){
|
||||||
|
return <Nav.item className='continousSave' onClick={this.handleClick} color='blue' icon='fa-save'>Save Now</Nav.item>
|
||||||
|
}
|
||||||
|
if(this.props.status == 'ready'){
|
||||||
|
return <Nav.item className='continousSave saved'>saved.</Nav.item>
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = Store.createSmartComponent(ContinousSave, onStoreChange);
|
||||||
79
client/homebrew/navbar/continousSave.navitem.jsx
Normal file
79
client/homebrew/navbar/continousSave.navitem.jsx
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
const flux = require('pico-flux')
|
||||||
|
const React = require('react');
|
||||||
|
const _ = require('lodash');
|
||||||
|
const cx = require('classnames');
|
||||||
|
|
||||||
|
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
|
|
||||||
|
const Store = require('homebrewery/brew.store.js');
|
||||||
|
const Actions = require('homebrewery/brew.actions.js');
|
||||||
|
|
||||||
|
const onStoreChange = () => {
|
||||||
|
return {
|
||||||
|
status : Store.getStatus(),
|
||||||
|
errors : Store.getErrors()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const ContinousSave = React.createClass({
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
status : 'ready',
|
||||||
|
errors : undefined
|
||||||
|
};
|
||||||
|
},
|
||||||
|
componentDidMount: function() {
|
||||||
|
flux.actionEmitter.on('dispatch', this.actionHandler);
|
||||||
|
window.onbeforeunload = ()=>{
|
||||||
|
if(this.props.status !== 'ready') return 'You have unsaved changes!';
|
||||||
|
};
|
||||||
|
},
|
||||||
|
componentWillUnmount: function() {
|
||||||
|
flux.actionEmitter.removeListener('dispatch', this.actionHandler);
|
||||||
|
window.onbeforeunload = function(){};
|
||||||
|
},
|
||||||
|
actionHandler : function(actionType){
|
||||||
|
if(actionType == 'UPDATE_BREW_CODE' || actionType == 'UPDATE_META' || actionType == 'UPDATE_BREW_STYLE'){
|
||||||
|
Actions.pendingSave();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleClick : function(){
|
||||||
|
Actions.save();
|
||||||
|
},
|
||||||
|
renderError : function(){
|
||||||
|
let errMsg = '';
|
||||||
|
try{
|
||||||
|
errMsg += this.state.errors.toString() + '\n\n';
|
||||||
|
errMsg += '```\n' + JSON.stringify(this.state.errors.response.error, null, ' ') + '\n```';
|
||||||
|
}catch(e){}
|
||||||
|
|
||||||
|
return <Nav.item className='continousSave error' icon="fa-warning">
|
||||||
|
Oops!
|
||||||
|
<div className='errorContainer'>
|
||||||
|
Looks like there was a problem saving. <br />
|
||||||
|
Back up your brew in a text file, just in case.
|
||||||
|
<br /><br />
|
||||||
|
|
||||||
|
Report the issue <a target='_blank' href={'https://github.com/stolksdorf/naturalcrit/issues/new?body='+ encodeURIComponent(errMsg)}>
|
||||||
|
here
|
||||||
|
</a>.
|
||||||
|
</div>
|
||||||
|
</Nav.item>
|
||||||
|
},
|
||||||
|
render : function(){
|
||||||
|
if(this.props.status == 'error') return this.renderError();
|
||||||
|
|
||||||
|
if(this.props.status == 'saving'){
|
||||||
|
return <Nav.item className='continousSave' icon="fa-spinner fa-spin">saving...</Nav.item>
|
||||||
|
}
|
||||||
|
if(this.props.status == 'pending'){
|
||||||
|
return <Nav.item className='continousSave' onClick={this.handleClick} color='blue' icon='fa-save'>Save Now</Nav.item>
|
||||||
|
}
|
||||||
|
if(this.props.status == 'ready'){
|
||||||
|
return <Nav.item className='continousSave saved'>saved.</Nav.item>
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = Store.createSmartComponent(ContinousSave, onStoreChange);
|
||||||
@@ -2,7 +2,12 @@ var React = require('react');
|
|||||||
var Nav = require('naturalcrit/nav/nav.jsx');
|
var Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
|
|
||||||
module.exports = function(props){
|
module.exports = function(props){
|
||||||
return <Nav.item newTab={true} href='https://github.com/stolksdorf/homebrewery/issues' color='red' icon='fa-bug'>
|
return <Nav.item
|
||||||
|
{...props}
|
||||||
|
newTab={true}
|
||||||
|
href='https://github.com/stolksdorf/homebrewery/issues'
|
||||||
|
color='red'
|
||||||
|
icon='fa-bug'>
|
||||||
report issue
|
report issue
|
||||||
</Nav.item>
|
</Nav.item>
|
||||||
};
|
};
|
||||||
@@ -2,34 +2,9 @@ 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 Store = require('homebrewery/brew.store.js');
|
||||||
|
|
||||||
const Navbar = React.createClass({
|
const Navbar = React.createClass({
|
||||||
getInitialState: function() {
|
|
||||||
return {
|
|
||||||
//showNonChromeWarning : false,
|
|
||||||
ver : '0.0.0'
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
componentDidMount: function() {
|
|
||||||
//const isChrome = /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor);
|
|
||||||
this.setState({
|
|
||||||
//showNonChromeWarning : !isChrome,
|
|
||||||
ver : window.version
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
|
||||||
renderChromeWarning : function(){
|
|
||||||
if(!this.state.showNonChromeWarning) return;
|
|
||||||
return <Nav.item className='warning' icon='fa-exclamation-triangle'>
|
|
||||||
Optimized for Chrome
|
|
||||||
<div className='dropdown'>
|
|
||||||
If you are experiencing rendering issues, use Chrome instead
|
|
||||||
</div>
|
|
||||||
</Nav.item>
|
|
||||||
},
|
|
||||||
*/
|
|
||||||
render : function(){
|
render : function(){
|
||||||
return <Nav.base>
|
return <Nav.base>
|
||||||
<Nav.section>
|
<Nav.section>
|
||||||
@@ -37,9 +12,7 @@ const Navbar = React.createClass({
|
|||||||
<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${Store.getVersion()}`}</Nav.item>
|
||||||
|
|
||||||
{/*this.renderChromeWarning()*/}
|
|
||||||
</Nav.section>
|
</Nav.section>
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</Nav.base>
|
</Nav.base>
|
||||||
|
|||||||
@@ -13,32 +13,6 @@
|
|||||||
color : @blue;
|
color : @blue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.editTitle.navItem{
|
|
||||||
padding : 2px 12px;
|
|
||||||
input{
|
|
||||||
width : 250px;
|
|
||||||
margin : 0;
|
|
||||||
padding : 2px;
|
|
||||||
background-color : #444;
|
|
||||||
font-family : 'Open Sans', sans-serif;
|
|
||||||
font-size : 12px;
|
|
||||||
font-weight : 800;
|
|
||||||
color : white;
|
|
||||||
text-align : center;
|
|
||||||
border : 1px solid @blue;
|
|
||||||
outline : none;
|
|
||||||
}
|
|
||||||
.charCount{
|
|
||||||
display : inline-block;
|
|
||||||
vertical-align : bottom;
|
|
||||||
margin-left : 8px;
|
|
||||||
color : #666;
|
|
||||||
text-align : right;
|
|
||||||
&.max{
|
|
||||||
color : @red;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.brewTitle.navItem{
|
.brewTitle.navItem{
|
||||||
font-size : 12px;
|
font-size : 12px;
|
||||||
font-weight : 800;
|
font-weight : 800;
|
||||||
@@ -125,4 +99,34 @@
|
|||||||
text-align : center;
|
text-align : center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.staticSave.navItem{
|
||||||
|
background-color : @orange;
|
||||||
|
&:hover{
|
||||||
|
background-color : @green;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.continousSave.navItem{
|
||||||
|
width : 105px;
|
||||||
|
text-align : center;
|
||||||
|
&.saved{
|
||||||
|
cursor : initial;
|
||||||
|
color : #666;
|
||||||
|
}
|
||||||
|
&.error{
|
||||||
|
position : relative;
|
||||||
|
background-color : @red;
|
||||||
|
.errorContainer{
|
||||||
|
position : absolute;
|
||||||
|
top : 29px;
|
||||||
|
left : -20px;
|
||||||
|
z-index : 1000;
|
||||||
|
width : 170px;
|
||||||
|
padding : 8px;
|
||||||
|
background-color : #333;
|
||||||
|
a{
|
||||||
|
color : @teal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
10
client/homebrew/navbar/navitems.js
Normal file
10
client/homebrew/navbar/navitems.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
module.exports = {
|
||||||
|
Account : require('./account.navitem.jsx'),
|
||||||
|
BrewTitle : require('./brewTitle.navitem.jsx'),
|
||||||
|
ContinousSave : require('./continousSave.navitem.jsx'),
|
||||||
|
Issue : require('./issue.navitem.jsx'),
|
||||||
|
Patreon : require('./patreon.navitem.jsx'),
|
||||||
|
Print : require('./print.navitem.jsx'),
|
||||||
|
Recent : require('./recent.navitem.jsx'),
|
||||||
|
StaticSave : require('./staticSave.navitem.jsx'),
|
||||||
|
};
|
||||||
@@ -3,6 +3,7 @@ var Nav = require('naturalcrit/nav/nav.jsx');
|
|||||||
|
|
||||||
module.exports = function(props){
|
module.exports = function(props){
|
||||||
return <Nav.item
|
return <Nav.item
|
||||||
|
{...props}
|
||||||
className='patreon'
|
className='patreon'
|
||||||
newTab={true}
|
newTab={true}
|
||||||
href='https://www.patreon.com/stolksdorf'
|
href='https://www.patreon.com/stolksdorf'
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ var 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';
|
||||||
|
|
||||||
|
//DEPRICATED
|
||||||
|
|
||||||
|
|
||||||
var BaseItem = React.createClass({
|
var BaseItem = React.createClass({
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
return {
|
return {
|
||||||
@@ -28,6 +31,8 @@ var BaseItem = React.createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
componentDidMount: function() {
|
componentDidMount: function() {
|
||||||
|
console.log('Recent nav item is depricated');
|
||||||
|
|
||||||
var brews = JSON.parse(localStorage.getItem(this.props.storageKey) || '[]');
|
var brews = JSON.parse(localStorage.getItem(this.props.storageKey) || '[]');
|
||||||
|
|
||||||
brews = _.filter(brews, (brew)=>{
|
brews = _.filter(brews, (brew)=>{
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
var React = require('react');
|
|
||||||
var _ = require('lodash');
|
|
||||||
var cx = require('classnames');
|
|
||||||
|
|
||||||
//var striptags = require('striptags');
|
|
||||||
|
|
||||||
var Nav = require('naturalcrit/nav/nav.jsx');
|
|
||||||
|
|
||||||
const MAX_URL_SIZE = 2083;
|
|
||||||
const MAIN_URL = "https://www.reddit.com/r/UnearthedArcana/submit?selftext=true"
|
|
||||||
|
|
||||||
|
|
||||||
var RedditShare = React.createClass({
|
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
brew : {
|
|
||||||
title : '',
|
|
||||||
sharedId : '',
|
|
||||||
text : ''
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
getText : function(){
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
handleClick : function(){
|
|
||||||
var url = [
|
|
||||||
MAIN_URL,
|
|
||||||
'title=' + encodeURIComponent(this.props.brew.title ? this.props.brew.title : 'Check out my brew!'),
|
|
||||||
|
|
||||||
'text=' + encodeURIComponent(this.props.brew.text)
|
|
||||||
|
|
||||||
|
|
||||||
].join('&');
|
|
||||||
|
|
||||||
window.open(url, '_blank');
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
render : function(){
|
|
||||||
return <Nav.item icon='fa-reddit-alien' color='red' onClick={this.handleClick}>
|
|
||||||
share on reddit
|
|
||||||
</Nav.item>
|
|
||||||
},
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = RedditShare;
|
|
||||||
37
client/homebrew/navbar/staticSave.navitem.jsx
Normal file
37
client/homebrew/navbar/staticSave.navitem.jsx
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
const React = require('react');
|
||||||
|
const _ = require('lodash');
|
||||||
|
const cx = require('classnames');
|
||||||
|
|
||||||
|
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
|
const Store = require('homebrewery/brew.store.js');
|
||||||
|
const Actions = require('homebrewery/brew.actions.js');
|
||||||
|
|
||||||
|
const StaticSave = React.createClass({
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
status : 'ready'
|
||||||
|
};
|
||||||
|
},
|
||||||
|
handleClick : function(){
|
||||||
|
Actions.create();
|
||||||
|
},
|
||||||
|
render : function(){
|
||||||
|
if(this.props.status === 'saving'){
|
||||||
|
return <Nav.item icon='fa-spinner fa-spin' className='staticSave'>
|
||||||
|
save...
|
||||||
|
</Nav.item>
|
||||||
|
}
|
||||||
|
if(this.props.status === 'ready'){
|
||||||
|
return <Nav.item icon='fa-save' className='staticSave' onClick={this.handleClick}>
|
||||||
|
save
|
||||||
|
</Nav.item>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = Store.createSmartComponent(StaticSave, ()=>{
|
||||||
|
return {
|
||||||
|
status : Store.getStatus()
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -1,230 +1,63 @@
|
|||||||
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 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 Items = require('../../navbar/navitems.js');
|
||||||
|
|
||||||
const ReportIssue = require('../../navbar/issue.navitem.jsx');
|
const BrewInterface = require('homebrewery/brewInterface/brewInterface.jsx');
|
||||||
const PrintLink = require('../../navbar/print.navitem.jsx');
|
const Utils = require('homebrewery/utils.js');
|
||||||
const Account = require('../../navbar/account.navitem.jsx');
|
|
||||||
//const RecentlyEdited = require('../../navbar/recent.navitem.jsx').edited;
|
|
||||||
|
|
||||||
const SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
|
const Store = require('homebrewery/brew.store.js');
|
||||||
const Editor = require('../../editor/editor.jsx');
|
const Actions = require('homebrewery/brew.actions.js');
|
||||||
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
|
||||||
|
|
||||||
const Markdown = require('naturalcrit/markdown.js');
|
|
||||||
|
|
||||||
const SAVE_TIMEOUT = 3000;
|
|
||||||
|
|
||||||
|
|
||||||
const EditPage = React.createClass({
|
const EditPage = React.createClass({
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
brew : {
|
|
||||||
text : '',
|
|
||||||
shareId : null,
|
|
||||||
editId : null,
|
|
||||||
createdAt : null,
|
|
||||||
updatedAt : null,
|
|
||||||
|
|
||||||
title : '',
|
|
||||||
description : '',
|
|
||||||
tags : '',
|
|
||||||
published : false,
|
|
||||||
authors : [],
|
|
||||||
systems : []
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
getInitialState: function() {
|
|
||||||
return {
|
|
||||||
brew : this.props.brew,
|
|
||||||
|
|
||||||
isSaving : false,
|
|
||||||
isPending : false,
|
|
||||||
errors : null,
|
|
||||||
htmlErrors : Markdown.validate(this.props.brew.text),
|
|
||||||
lastUpdated : this.props.brew.updatedAt
|
|
||||||
};
|
|
||||||
},
|
|
||||||
savedBrew : null,
|
|
||||||
|
|
||||||
componentDidMount: function(){
|
componentDidMount: function(){
|
||||||
this.trySave();
|
|
||||||
window.onbeforeunload = ()=>{
|
|
||||||
if(this.state.isSaving || this.state.isPending){
|
|
||||||
return 'You have unsaved changes!';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
htmlErrors : Markdown.validate(this.state.brew.text)
|
|
||||||
})
|
|
||||||
|
|
||||||
document.addEventListener('keydown', this.handleControlKeys);
|
document.addEventListener('keydown', this.handleControlKeys);
|
||||||
},
|
},
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount: function() {
|
||||||
window.onbeforeunload = function(){};
|
|
||||||
document.removeEventListener('keydown', this.handleControlKeys);
|
document.removeEventListener('keydown', this.handleControlKeys);
|
||||||
},
|
},
|
||||||
|
handleControlKeys : Utils.controlKeys({
|
||||||
|
s : Actions.save,
|
||||||
handleControlKeys : function(e){
|
p : Actions.print
|
||||||
if(!(e.ctrlKey || e.metaKey)) return;
|
}),
|
||||||
const S_KEY = 83;
|
|
||||||
const P_KEY = 80;
|
|
||||||
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 || e.keyCode == S_KEY){
|
|
||||||
e.stopPropagation();
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
handleSplitMove : function(){
|
|
||||||
this.refs.editor.update();
|
|
||||||
},
|
|
||||||
|
|
||||||
handleMetadataChange : function(metadata){
|
|
||||||
this.setState({
|
|
||||||
brew : _.merge({}, this.state.brew, metadata),
|
|
||||||
isPending : true,
|
|
||||||
}, ()=>{
|
|
||||||
this.trySave();
|
|
||||||
});
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
handleTextChange : function(text){
|
|
||||||
|
|
||||||
//If there are errors, run the validator on everychange to give quick feedback
|
|
||||||
var htmlErrors = this.state.htmlErrors;
|
|
||||||
if(htmlErrors.length) htmlErrors = Markdown.validate(text);
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
brew : _.merge({}, this.state.brew, {text : text}),
|
|
||||||
isPending : true,
|
|
||||||
htmlErrors : htmlErrors
|
|
||||||
});
|
|
||||||
|
|
||||||
this.trySave();
|
|
||||||
},
|
|
||||||
|
|
||||||
hasChanges : function(){
|
|
||||||
if(this.savedBrew){
|
|
||||||
return !_.isEqual(this.state.brew, this.savedBrew)
|
|
||||||
}else{
|
|
||||||
return !_.isEqual(this.state.brew, this.props.brew)
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
|
|
||||||
trySave : function(){
|
|
||||||
if(!this.debounceSave) this.debounceSave = _.debounce(this.save, SAVE_TIMEOUT);
|
|
||||||
if(this.hasChanges()){
|
|
||||||
this.debounceSave();
|
|
||||||
}else{
|
|
||||||
this.debounceSave.cancel();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
save : function(){
|
|
||||||
if(this.debounceSave && this.debounceSave.cancel) this.debounceSave.cancel();
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
isSaving : true,
|
|
||||||
errors : null,
|
|
||||||
htmlErrors : Markdown.validate(this.state.brew.text)
|
|
||||||
});
|
|
||||||
|
|
||||||
request
|
|
||||||
.put('/api/update/' + this.props.brew.editId)
|
|
||||||
.send(this.state.brew)
|
|
||||||
.end((err, res) => {
|
|
||||||
if(err){
|
|
||||||
this.setState({
|
|
||||||
errors : err,
|
|
||||||
})
|
|
||||||
}else{
|
|
||||||
this.savedBrew = res.body;
|
|
||||||
this.setState({
|
|
||||||
isPending : false,
|
|
||||||
isSaving : false,
|
|
||||||
lastUpdated : res.body.updatedAt
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
renderSaveButton : function(){
|
|
||||||
if(this.state.errors){
|
|
||||||
var errMsg = '';
|
|
||||||
try{
|
|
||||||
errMsg += this.state.errors.toString() + '\n\n';
|
|
||||||
errMsg += '```\n' + JSON.stringify(this.state.errors.response.error, null, ' ') + '\n```';
|
|
||||||
}catch(e){}
|
|
||||||
|
|
||||||
return <Nav.item className='save error' icon="fa-warning">
|
|
||||||
Oops!
|
|
||||||
<div className='errorContainer'>
|
|
||||||
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)}>
|
|
||||||
here
|
|
||||||
</a>.
|
|
||||||
</div>
|
|
||||||
</Nav.item>
|
|
||||||
}
|
|
||||||
|
|
||||||
if(this.state.isSaving){
|
|
||||||
return <Nav.item className='save' icon="fa-spinner fa-spin">saving...</Nav.item>
|
|
||||||
}
|
|
||||||
if(this.state.isPending && this.hasChanges()){
|
|
||||||
return <Nav.item className='save' onClick={this.save} color='blue' icon='fa-save'>Save Now</Nav.item>
|
|
||||||
}
|
|
||||||
if(!this.state.isPending && !this.state.isSaving){
|
|
||||||
return <Nav.item className='save saved'>saved.</Nav.item>
|
|
||||||
}
|
|
||||||
},
|
|
||||||
renderNavbar : function(){
|
|
||||||
return <Navbar>
|
|
||||||
<Nav.section>
|
|
||||||
<Nav.item className='brewTitle'>{this.state.brew.title}</Nav.item>
|
|
||||||
</Nav.section>
|
|
||||||
<Nav.section>
|
|
||||||
{this.renderSaveButton()}
|
|
||||||
{/*<RecentlyEdited brew={this.props.brew} />*/}
|
|
||||||
<ReportIssue />
|
|
||||||
<Nav.item newTab={true} href={'/share/' + this.props.brew.shareId} color='teal' icon='fa-share-alt'>
|
|
||||||
Share
|
|
||||||
</Nav.item>
|
|
||||||
<PrintLink shareId={this.props.brew.shareId} />
|
|
||||||
<Account />
|
|
||||||
</Nav.section>
|
|
||||||
</Navbar>
|
|
||||||
},
|
|
||||||
|
|
||||||
render : function(){
|
render : function(){
|
||||||
return <div className='editPage page'>
|
return <div className='editPage page'>
|
||||||
{this.renderNavbar()}
|
<SmartNav />
|
||||||
|
|
||||||
<div className='content'>
|
<div className='content'>
|
||||||
<SplitPane onDragFinish={this.handleSplitMove} ref='pane'>
|
<BrewInterface />
|
||||||
<Editor
|
|
||||||
ref='editor'
|
|
||||||
value={this.state.brew.text}
|
|
||||||
onChange={this.handleTextChange}
|
|
||||||
metadata={this.state.brew}
|
|
||||||
onMetadataChange={this.handleMetadataChange}
|
|
||||||
/>
|
|
||||||
<BrewRenderer text={this.state.brew.text} errors={this.state.htmlErrors} />
|
|
||||||
</SplitPane>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const SmartNav = Store.createSmartComponent(React.createClass({
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
brew : {}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
render : function(){
|
||||||
|
return <Navbar>
|
||||||
|
<Nav.section>
|
||||||
|
<Items.BrewTitle />
|
||||||
|
</Nav.section>
|
||||||
|
<Nav.section>
|
||||||
|
<Items.ContinousSave />
|
||||||
|
<Items.Issue />
|
||||||
|
<Nav.item newTab={true} href={'/share/' + Store.getBrew().shareId} color='teal' icon='fa-share-alt'>
|
||||||
|
Share
|
||||||
|
</Nav.item>
|
||||||
|
<Items.Print />
|
||||||
|
<Items.Account />
|
||||||
|
</Nav.section>
|
||||||
|
</Navbar>
|
||||||
|
}
|
||||||
|
}), ()=>{
|
||||||
|
return {brew : Store.getBrew()}
|
||||||
|
});
|
||||||
|
|
||||||
module.exports = EditPage;
|
module.exports = EditPage;
|
||||||
|
|||||||
@@ -1,27 +1,4 @@
|
|||||||
|
|
||||||
.editPage{
|
.editPage{
|
||||||
.navItem.save{
|
|
||||||
width : 105px;
|
|
||||||
text-align : center;
|
|
||||||
&.saved{
|
|
||||||
cursor : initial;
|
|
||||||
color : #666;
|
|
||||||
}
|
|
||||||
&.error{
|
|
||||||
position : relative;
|
|
||||||
background-color : @red;
|
|
||||||
.errorContainer{
|
|
||||||
position : absolute;
|
|
||||||
top : 29px;
|
|
||||||
left : -20px;
|
|
||||||
z-index : 1000;
|
|
||||||
width : 120px;
|
|
||||||
padding : 8px;
|
|
||||||
background-color : #333;
|
|
||||||
a{
|
|
||||||
color : @teal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
//TODO: Depricate
|
|
||||||
|
|
||||||
module.exports = function(shareId){
|
|
||||||
return function(event){
|
|
||||||
event = event || window.event;
|
|
||||||
if((event.ctrlKey || event.metaKey) && event.keyCode == 80){
|
|
||||||
var win = window.open(`/homebrew/print/${shareId}?dialog=true`, '_blank');
|
|
||||||
win.focus();
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
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 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');
|
||||||
@@ -11,50 +10,24 @@ 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 BrewInterface = require('homebrewery/brewInterface/brewInterface.jsx');
|
||||||
const Editor = require('../../editor/editor.jsx');
|
|
||||||
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
|
||||||
|
|
||||||
|
|
||||||
|
const Actions = require('homebrewery/brew.actions.js');
|
||||||
|
//
|
||||||
|
|
||||||
const HomePage = React.createClass({
|
const HomePage = React.createClass({
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
welcomeText : '',
|
|
||||||
ver : '0.0.0'
|
|
||||||
};
|
|
||||||
},
|
|
||||||
getInitialState: function() {
|
|
||||||
return {
|
|
||||||
text: this.props.welcomeText
|
|
||||||
};
|
|
||||||
},
|
|
||||||
handleSave : function(){
|
handleSave : function(){
|
||||||
request.post('/api')
|
Actions.saveNew();
|
||||||
.send({
|
|
||||||
text : this.state.text
|
|
||||||
})
|
|
||||||
.end((err, res)=>{
|
|
||||||
if(err) return;
|
|
||||||
var brew = res.body;
|
|
||||||
window.location = '/edit/' + brew.editId;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
handleSplitMove : function(){
|
|
||||||
this.refs.editor.update();
|
|
||||||
},
|
|
||||||
handleTextChange : function(text){
|
|
||||||
this.setState({
|
|
||||||
text : text
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
renderNavbar : function(){
|
renderNavbar : function(){
|
||||||
return <Navbar ver={this.props.ver}>
|
return <Navbar>
|
||||||
<Nav.section>
|
<Nav.section>
|
||||||
<PatreonNavItem />
|
<PatreonNavItem collaspe={true} />
|
||||||
<IssueNavItem />
|
<IssueNavItem collaspe={true} />
|
||||||
<Nav.item newTab={true} href='/changelog' color='purple' icon='fa-file-text-o'>
|
<Nav.item newTab={true} href='/changelog' color='purple' icon='fa-star' collaspe={true}>
|
||||||
Changelog
|
What's new
|
||||||
</Nav.item>
|
</Nav.item>
|
||||||
<RecentNavItem.both />
|
<RecentNavItem.both />
|
||||||
<AccountNavItem />
|
<AccountNavItem />
|
||||||
@@ -70,15 +43,13 @@ const HomePage = React.createClass({
|
|||||||
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'>
|
<BrewInterface />
|
||||||
<Editor value={this.state.text} onChange={this.handleTextChange} ref='editor'/>
|
|
||||||
<BrewRenderer text={this.state.text} />
|
|
||||||
</SplitPane>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={cx('floatingSaveButton', {show : this.props.welcomeText != this.state.text})} onClick={this.handleSave}>
|
<div className={cx('floatingSaveButton', {
|
||||||
|
//show : Store.getBrewText() !== this.props.welcomeText
|
||||||
|
})} onClick={this.handleSave}>
|
||||||
Save current <i className='fa fa-save' />
|
Save current <i className='fa fa-save' />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,158 +1,59 @@
|
|||||||
const React = require('react');
|
const React = require('react');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const cx = require('classnames');
|
|
||||||
const request = require("superagent");
|
|
||||||
|
|
||||||
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 Items = require('../../navbar/navitems.js');
|
||||||
const IssueNavItem = require('../../navbar/issue.navitem.jsx');
|
|
||||||
|
|
||||||
const SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
|
const Store = require('homebrewery/brew.store.js');
|
||||||
const Editor = require('../../editor/editor.jsx');
|
const Actions = require('homebrewery/brew.actions.js');
|
||||||
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
|
||||||
|
|
||||||
|
const BrewInterface = require('homebrewery/brewInterface/brewInterface.jsx');
|
||||||
|
const Utils = require('homebrewery/utils.js');
|
||||||
|
|
||||||
const KEY = 'homebrewery-new';
|
const KEY = 'homebrewery-new';
|
||||||
|
|
||||||
const NewPage = React.createClass({
|
const NewPage = React.createClass({
|
||||||
getInitialState: function() {
|
|
||||||
return {
|
|
||||||
metadata : {
|
|
||||||
title : '',
|
|
||||||
description : '',
|
|
||||||
tags : '',
|
|
||||||
published : false,
|
|
||||||
authors : [],
|
|
||||||
systems : []
|
|
||||||
},
|
|
||||||
|
|
||||||
text: '',
|
|
||||||
isSaving : false,
|
|
||||||
errors : []
|
|
||||||
};
|
|
||||||
},
|
|
||||||
componentDidMount: function() {
|
componentDidMount: function() {
|
||||||
const storage = localStorage.getItem(KEY);
|
try{
|
||||||
if(storage){
|
const storedBrew = JSON.parse(localStorage.getItem(KEY));
|
||||||
this.setState({
|
if(storedBrew && storedBrew.text) Actions.setBrew(storedBrew);
|
||||||
text : storage
|
}catch(e){}
|
||||||
})
|
Store.updateEmitter.on('change', this.saveToLocal);
|
||||||
}
|
|
||||||
document.addEventListener('keydown', this.handleControlKeys);
|
document.addEventListener('keydown', this.handleControlKeys);
|
||||||
},
|
},
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount: function() {
|
||||||
|
Store.updateEmitter.removeListener('change', this.saveToLocal);
|
||||||
document.removeEventListener('keydown', this.handleControlKeys);
|
document.removeEventListener('keydown', this.handleControlKeys);
|
||||||
},
|
},
|
||||||
|
|
||||||
handleControlKeys : function(e){
|
saveToLocal : function(){
|
||||||
if(!(e.ctrlKey || e.metaKey)) return;
|
localStorage.setItem(KEY, JSON.stringify(Store.getBrew()));
|
||||||
const S_KEY = 83;
|
|
||||||
const P_KEY = 80;
|
|
||||||
if(e.keyCode == S_KEY) this.save();
|
|
||||||
if(e.keyCode == P_KEY) this.print();
|
|
||||||
if(e.keyCode == P_KEY || e.keyCode == S_KEY){
|
|
||||||
e.stopPropagation();
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
handleSplitMove : function(){
|
|
||||||
this.refs.editor.update();
|
|
||||||
},
|
|
||||||
|
|
||||||
handleMetadataChange : function(metadata){
|
|
||||||
this.setState({
|
|
||||||
metadata : _.merge({}, this.state.metadata, metadata)
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
handleTextChange : function(text){
|
|
||||||
this.setState({
|
|
||||||
text : text,
|
|
||||||
errors : Markdown.validate(text)
|
|
||||||
});
|
|
||||||
localStorage.setItem(KEY, text);
|
|
||||||
},
|
|
||||||
|
|
||||||
save : function(){
|
|
||||||
this.setState({
|
|
||||||
isSaving : true
|
|
||||||
});
|
|
||||||
|
|
||||||
request.post('/api')
|
|
||||||
.send(_.merge({}, this.state.metadata, {
|
|
||||||
text : this.state.text
|
|
||||||
}))
|
|
||||||
.end((err, res)=>{
|
|
||||||
if(err){
|
|
||||||
this.setState({
|
|
||||||
isSaving : false
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
window.onbeforeunload = function(){};
|
|
||||||
const brew = res.body;
|
|
||||||
localStorage.removeItem(KEY);
|
|
||||||
window.location = '/edit/' + brew.editId;
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
renderSaveButton : function(){
|
|
||||||
if(this.state.isSaving){
|
|
||||||
return <Nav.item icon='fa-spinner fa-spin' className='saveButton'>
|
|
||||||
save...
|
|
||||||
</Nav.item>
|
|
||||||
}else{
|
|
||||||
return <Nav.item icon='fa-save' className='saveButton' onClick={this.save}>
|
|
||||||
save
|
|
||||||
</Nav.item>
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
print : function(){
|
|
||||||
localStorage.setItem('print', this.state.text);
|
|
||||||
window.open('/print?dialog=true&local=print','_blank');
|
|
||||||
},
|
|
||||||
|
|
||||||
renderLocalPrintButton : function(){
|
|
||||||
return <Nav.item color='purple' icon='fa-file-pdf-o' onClick={this.print}>
|
|
||||||
get PDF
|
|
||||||
</Nav.item>
|
|
||||||
},
|
|
||||||
|
|
||||||
renderNavbar : function(){
|
|
||||||
return <Navbar>
|
|
||||||
|
|
||||||
<Nav.section>
|
|
||||||
<Nav.item className='brewTitle'>{this.state.metadata.title}</Nav.item>
|
|
||||||
</Nav.section>
|
|
||||||
|
|
||||||
<Nav.section>
|
|
||||||
{this.renderSaveButton()}
|
|
||||||
{this.renderLocalPrintButton()}
|
|
||||||
<IssueNavItem />
|
|
||||||
<AccountNavItem />
|
|
||||||
</Nav.section>
|
|
||||||
</Navbar>
|
|
||||||
},
|
},
|
||||||
|
handleControlKeys : Utils.controlKeys({
|
||||||
|
s : Actions.saveNew,
|
||||||
|
p : Actions.localPrint
|
||||||
|
}),
|
||||||
|
|
||||||
render : function(){
|
render : function(){
|
||||||
return <div className='newPage page'>
|
return <div className='newPage page'>
|
||||||
{this.renderNavbar()}
|
<Navbar>
|
||||||
|
<Nav.section>
|
||||||
|
<Items.BrewTitle />
|
||||||
|
</Nav.section>
|
||||||
|
|
||||||
|
<Nav.section>
|
||||||
|
<Items.StaticSave />
|
||||||
|
<Nav.item color='purple' icon='fa-file-pdf-o' onClick={Actions.localPrint}>
|
||||||
|
get PDF
|
||||||
|
</Nav.item>
|
||||||
|
<Items.Issue collaspe={true} />
|
||||||
|
<Items.Account />
|
||||||
|
</Nav.section>
|
||||||
|
</Navbar>
|
||||||
|
|
||||||
<div className='content'>
|
<div className='content'>
|
||||||
<SplitPane onDragFinish={this.handleSplitMove} ref='pane'>
|
<BrewInterface />
|
||||||
<Editor
|
|
||||||
ref='editor'
|
|
||||||
value={this.state.text}
|
|
||||||
onChange={this.handleTextChange}
|
|
||||||
metadata={this.state.metadata}
|
|
||||||
onMetadataChange={this.handleMetadataChange}
|
|
||||||
/>
|
|
||||||
<BrewRenderer text={this.state.text} errors={this.state.errors} />
|
|
||||||
</SplitPane>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,4 @@
|
|||||||
.newPage{
|
.newPage{
|
||||||
|
|
||||||
.saveButton{
|
|
||||||
background-color: @orange;
|
|
||||||
&:hover{
|
|
||||||
background-color: @green;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
const React = require('react');
|
const React = require('react');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const cx = require('classnames');
|
const cx = require('classnames');
|
||||||
const Markdown = require('naturalcrit/markdown.js');
|
const Markdown = require('homebrewery/markdown.js');
|
||||||
|
|
||||||
|
const Headtags = require('vitreum/headtags');
|
||||||
|
|
||||||
const PrintPage = React.createClass({
|
const PrintPage = React.createClass({
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
@@ -9,36 +11,54 @@ const PrintPage = React.createClass({
|
|||||||
query : {},
|
query : {},
|
||||||
brew : {
|
brew : {
|
||||||
text : '',
|
text : '',
|
||||||
|
style : ''
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
brewText: this.props.brew.text
|
brew: this.props.brew
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidMount: function() {
|
componentDidMount: function() {
|
||||||
if(this.props.query.local){
|
if(this.props.query.local){
|
||||||
this.setState({ brewText : localStorage.getItem(this.props.query.local)});
|
try{
|
||||||
|
this.setState({
|
||||||
|
brew : JSON.parse(
|
||||||
|
localStorage.getItem(this.props.query.local)
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}catch(e){}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.props.query.dialog) window.print();
|
if(this.props.query.dialog) window.print();
|
||||||
},
|
},
|
||||||
|
//TODO: Print page shouldn't replicate functionality in brew renderer
|
||||||
|
renderStyle : function(){
|
||||||
|
if(!this.state.brew.style) return;
|
||||||
|
return <style>{this.state.brew.style.replace(/;/g, ' !important;')}</style>
|
||||||
|
},
|
||||||
renderPages : function(){
|
renderPages : function(){
|
||||||
return _.map(this.state.brewText.split('\\page'), (page, index) => {
|
return _.map(this.state.brew.text.split('\\page'), (page, index) => {
|
||||||
return <div
|
return <div
|
||||||
className='phb'
|
className='phb v2'
|
||||||
id={`p${index + 1}`}
|
id={`p${index + 1}`}
|
||||||
dangerouslySetInnerHTML={{__html:Markdown.render(page)}}
|
dangerouslySetInnerHTML={{__html:Markdown.render(page)}}
|
||||||
key={index} />;
|
key={index} />;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
renderPrintInstructions : function(){
|
||||||
|
return <div className='printInstructions'>
|
||||||
|
Hey, I'm really cool instructions!!!!!
|
||||||
|
|
||||||
|
</div>
|
||||||
|
},
|
||||||
|
|
||||||
render : function(){
|
render : function(){
|
||||||
return <div>
|
return <div className='printPage'>
|
||||||
|
<Headtags.title>{this.state.brew.title}</Headtags.title>
|
||||||
|
{this.renderPrintInstructions()}
|
||||||
|
{this.renderStyle()}
|
||||||
{this.renderPages()}
|
{this.renderPages()}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,17 @@
|
|||||||
.printPage{
|
|
||||||
|
|
||||||
|
.printPage{
|
||||||
|
position : relative;
|
||||||
|
@media print{
|
||||||
|
.printInstructions{
|
||||||
|
display : none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.printInstructions{
|
||||||
|
position : absolute;
|
||||||
|
top : 0px;
|
||||||
|
right : 0px;
|
||||||
|
z-index : 100000;
|
||||||
|
padding : 30px;
|
||||||
|
background-color : @blue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -9,60 +9,63 @@ 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('homebrewery/brewRenderer/brewRenderer.jsx');
|
||||||
|
const Utils = require('homebrewery/utils.js');
|
||||||
|
|
||||||
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
const Actions = require('homebrewery/brew.actions.js');
|
||||||
|
const Store = require('homebrewery/brew.store.js');
|
||||||
|
|
||||||
|
const Headtags = require('vitreum/headtags');
|
||||||
|
|
||||||
const SharePage = React.createClass({
|
const SharePage = React.createClass({
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
brew : {
|
|
||||||
title : '',
|
|
||||||
text : '',
|
|
||||||
shareId : null,
|
|
||||||
createdAt : null,
|
|
||||||
updatedAt : null,
|
|
||||||
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 : Utils.controlKeys({
|
||||||
if(!(e.ctrlKey || e.metaKey)) return;
|
p : Actions.print
|
||||||
const P_KEY = 80;
|
}),
|
||||||
if(e.keyCode == P_KEY){
|
|
||||||
window.open(`/print/${this.props.brew.shareId}?dialog=true`, '_blank').focus();
|
renderMetatags : function(brew){
|
||||||
e.stopPropagation();
|
let metatags = [
|
||||||
e.preventDefault();
|
<Headtags.meta key='site_name' property='og:site_name' content='Homebrewery'/>,
|
||||||
|
<Headtags.meta key='type' property='og:type' content='article' />
|
||||||
|
];
|
||||||
|
if(brew.title){
|
||||||
|
metatags.push(<Headtags.meta key='title' property='og:title' content={brew.title} />);
|
||||||
}
|
}
|
||||||
|
if(brew.description){
|
||||||
|
metatags.push(<Headtags.meta key='description' name='description' content={brew.description} />);
|
||||||
|
}
|
||||||
|
if(brew.thumbnail){
|
||||||
|
metatags.push(<Headtags.meta key='image' property='og:image' content={brew.thumbnail} />);
|
||||||
|
}
|
||||||
|
return metatags;
|
||||||
},
|
},
|
||||||
|
|
||||||
render : function(){
|
render : function(){
|
||||||
|
const brew = Store.getBrew();
|
||||||
return <div className='sharePage page'>
|
return <div className='sharePage page'>
|
||||||
|
{this.renderMetatags(brew)}
|
||||||
|
|
||||||
<Navbar>
|
<Navbar>
|
||||||
<Nav.section>
|
<Nav.section>
|
||||||
<Nav.item className='brewTitle'>{this.props.brew.title}</Nav.item>
|
<Nav.item className='brewTitle'>{brew.title}</Nav.item>
|
||||||
</Nav.section>
|
</Nav.section>
|
||||||
|
|
||||||
<Nav.section>
|
<Nav.section>
|
||||||
<ReportIssue />
|
<ReportIssue />
|
||||||
{/*<RecentlyViewed brew={this.props.brew} />*/}
|
<PrintLink shareId={brew.shareId} />
|
||||||
<PrintLink shareId={this.props.brew.shareId} />
|
<Nav.item href={'/source/' + 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 brew={brew} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ 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 BrewItem = React.createClass({
|
const BrewItem = React.createClass({
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
@@ -16,24 +15,6 @@ const BrewItem = React.createClass({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteBrew : function(){
|
|
||||||
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;
|
|
||||||
|
|
||||||
request.get('/api/remove/' + this.props.brew.editId)
|
|
||||||
.send()
|
|
||||||
.end(function(err, res){
|
|
||||||
location.reload();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
renderDeleteBrewLink: function(){
|
|
||||||
if(!this.props.brew.editId) return;
|
|
||||||
|
|
||||||
return <a onClick={this.deleteBrew}>
|
|
||||||
<i className='fa fa-trash' />
|
|
||||||
</a>
|
|
||||||
},
|
|
||||||
renderEditLink: function(){
|
renderEditLink: function(){
|
||||||
if(!this.props.brew.editId) return;
|
if(!this.props.brew.editId) return;
|
||||||
|
|
||||||
@@ -66,7 +47,6 @@ const BrewItem = React.createClass({
|
|||||||
<i className='fa fa-share-alt' />
|
<i className='fa fa-share-alt' />
|
||||||
</a>
|
</a>
|
||||||
{this.renderEditLink()}
|
{this.renderEditLink()}
|
||||||
{this.renderDeleteBrewLink()}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,12 +52,13 @@ const UserPage = React.createClass({
|
|||||||
|
|
||||||
render : function(){
|
render : function(){
|
||||||
const brews = this.getSortedBrews();
|
const brews = this.getSortedBrews();
|
||||||
|
console.log('user brews', brews);
|
||||||
|
|
||||||
return <div className='userPage page'>
|
return <div className='userPage page'>
|
||||||
<Navbar>
|
<Navbar>
|
||||||
<Nav.section>
|
<Nav.section>
|
||||||
<RecentNavItem.both />
|
<RecentNavItem.both />
|
||||||
<Account />
|
<Account userPage={this.props.username} />
|
||||||
</Nav.section>
|
</Nav.section>
|
||||||
</Navbar>
|
</Navbar>
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
.phb{
|
|
||||||
//Double hr for full width elements
|
|
||||||
hr+hr+blockquote{
|
|
||||||
column-span : all;
|
|
||||||
-webkit-column-span : all;
|
|
||||||
-moz-column-span : all;
|
|
||||||
}
|
|
||||||
|
|
||||||
//*****************************
|
|
||||||
// * CLASS TABLE
|
|
||||||
// *****************************/
|
|
||||||
hr+table{
|
|
||||||
margin-top : -5px;
|
|
||||||
margin-bottom : 50px;
|
|
||||||
padding-top : 10px;
|
|
||||||
border-collapse : separate;
|
|
||||||
background-color : white;
|
|
||||||
border : initial;
|
|
||||||
border-style : solid;
|
|
||||||
border-image-outset : 37px 17px;
|
|
||||||
border-image-repeat : round;
|
|
||||||
border-image-slice : 150 200 150 200;
|
|
||||||
border-image-source : @frameBorderImage;
|
|
||||||
border-image-width : 47px;
|
|
||||||
}
|
|
||||||
h5+hr+table{
|
|
||||||
column-span : all;
|
|
||||||
-webkit-column-span : all;
|
|
||||||
-moz-column-span : all;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 864 B |
@@ -6,6 +6,7 @@ module.exports = function(vitreum){
|
|||||||
<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>
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
{
|
{
|
||||||
"host" : "homebrewery.local.naturalcrit.com:8000",
|
"log_level" : "info",
|
||||||
"naturalcrit_url" : "local.naturalcrit.com:8010",
|
"login_path" : "/dev/login",
|
||||||
"secret" : "secret"
|
"jwt_secret" : "secretsecret",
|
||||||
|
"admin" : {
|
||||||
|
"user" : "admin",
|
||||||
|
"pass" : "password",
|
||||||
|
"key" : "adminadminadmin"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
4
config/production.json
Normal file
4
config/production.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"login_path" : "http://naturalcrit.com/login",
|
||||||
|
"log_level" : "warn"
|
||||||
|
}
|
||||||
4
config/staging.json
Normal file
4
config/staging.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"login_path" : "http://staging.naturalcrit.com/login",
|
||||||
|
"log_level" : "trace"
|
||||||
|
}
|
||||||
32
package.json
32
package.json
@@ -1,39 +1,55 @@
|
|||||||
{
|
{
|
||||||
"name": "homebrewery",
|
"name": "homebrewery",
|
||||||
"description": "Create authentic looking D&D homebrews using only markdown",
|
"description": "Create authentic looking D&D homebrews using only markdown",
|
||||||
"version": "2.7.2",
|
"version": "3.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "node scripts/dev.js",
|
"dev": "node scripts/dev.js",
|
||||||
"quick": "node scripts/quick.js",
|
"quick": "node scripts/quick.js",
|
||||||
"build": "node scripts/build.js",
|
"build": "node scripts/build.js",
|
||||||
"phb": "node scripts/phb.js",
|
"populate": "node scripts/populate.js",
|
||||||
"prod": "set NODE_ENV=production&& npm run build",
|
"prod": "set NODE_ENV=production&& npm run build",
|
||||||
"postinstall": "npm run build",
|
"postinstall": "npm run build",
|
||||||
"start": "node server.js"
|
"start": "node server.js",
|
||||||
|
"snippet": "nodemon scripts/snippet.test.js",
|
||||||
|
"todo": "./node_modules/.bin/fixme -i node_modules/** -i build/**",
|
||||||
|
"test": "mocha tests",
|
||||||
|
"test:dev": "nodemon -x mocha tests || exit 0",
|
||||||
|
"test:markdown": "nodemon -x mocha tests/markdown.test.js || exit 0"
|
||||||
},
|
},
|
||||||
"author": "stolksdorf",
|
"author": "stolksdorf",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"babel-preset-env": "^1.1.8",
|
|
||||||
"basic-auth": "^1.0.3",
|
"basic-auth": "^1.0.3",
|
||||||
"body-parser": "^1.14.2",
|
"body-parser": "^1.14.2",
|
||||||
"classnames": "^2.2.0",
|
"classnames": "^2.2.0",
|
||||||
"codemirror": "^5.22.0",
|
"codemirror": "^5.22.0",
|
||||||
"cookie-parser": "^1.4.3",
|
"cookie-parser": "^1.4.3",
|
||||||
|
"egads": "^1.0.1",
|
||||||
"express": "^4.13.3",
|
"express": "^4.13.3",
|
||||||
"jwt-simple": "^0.5.1",
|
"jwt-simple": "^0.5.1",
|
||||||
"lodash": "^4.11.2",
|
"lodash": "^4.17.3",
|
||||||
|
"loglevel": "^1.4.1",
|
||||||
"marked": "^0.3.5",
|
"marked": "^0.3.5",
|
||||||
"moment": "^2.11.0",
|
"moment": "^2.11.0",
|
||||||
"mongoose": "^4.3.3",
|
"mongoose": "^4.3.3",
|
||||||
"nconf": "^0.8.4",
|
"nconf": "^0.8.4",
|
||||||
"pico-flux": "^1.1.0",
|
"pico-flux": "^2.1.2",
|
||||||
"pico-router": "^1.1.0",
|
"pico-router": "^1.1.0",
|
||||||
"react": "^15.0.2",
|
"react": "^15.4.1",
|
||||||
"react-dom": "^15.0.2",
|
"react-dom": "^15.4.1",
|
||||||
"shortid": "^2.2.4",
|
"shortid": "^2.2.4",
|
||||||
"striptags": "^2.1.1",
|
"striptags": "^2.1.1",
|
||||||
"superagent": "^1.6.1",
|
"superagent": "^1.6.1",
|
||||||
"vitreum": "^4.0.12"
|
"vitreum": "^4.0.12"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"app-module-path": "^2.1.0",
|
||||||
|
"chai": "^3.5.0",
|
||||||
|
"chai-as-promised": "^6.0.0",
|
||||||
|
"chai-subset": "^1.4.0",
|
||||||
|
"fixme": "^0.4.3",
|
||||||
|
"mocha": "^3.2.0",
|
||||||
|
"supertest": "^2.0.1",
|
||||||
|
"supertest-as-promised": "^4.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -2,19 +2,20 @@ const label = 'build';
|
|||||||
console.time(label);
|
console.time(label);
|
||||||
|
|
||||||
const clean = require('vitreum/steps/clean.js');
|
const clean = require('vitreum/steps/clean.js');
|
||||||
const jsx = require('vitreum/steps/jsx.js').partial;
|
const jsx = require('vitreum/steps/jsx.js');
|
||||||
const lib = require('vitreum/steps/libs.js').partial;
|
const lib = require('vitreum/steps/libs.js');
|
||||||
const less = require('vitreum/steps/less.js').partial;
|
const less = require('vitreum/steps/less.js');
|
||||||
const asset = require('vitreum/steps/assets.js').partial;
|
const asset = require('vitreum/steps/assets.js');
|
||||||
|
|
||||||
const Proj = require('./project.json');
|
const Proj = require('./project.json');
|
||||||
|
|
||||||
clean()
|
Promise.resolve()
|
||||||
.then(lib(Proj.libs))
|
.then(()=>clean())
|
||||||
.then(jsx('homebrew', './client/homebrew/homebrew.jsx', Proj.libs, ['./shared']))
|
.then(()=>lib(Proj.libs))
|
||||||
.then(less('homebrew', ['./shared']))
|
.then(()=>jsx('homebrew', './client/homebrew/homebrew.jsx', Proj.libs, ['./shared']))
|
||||||
.then(jsx('admin', './client/admin/admin.jsx', Proj.libs, ['./shared']))
|
.then((deps)=>less('homebrew', ['./shared'], deps))
|
||||||
.then(less('admin', ['./shared']))
|
.then(()=>jsx('admin', './client/admin/admin.jsx', Proj.libs, ['./shared']))
|
||||||
.then(asset(Proj.assets, ['./shared', './client']))
|
.then((deps)=>less('admin', ['./shared'], deps))
|
||||||
.then(console.timeEnd.bind(console, label))
|
.then(()=>asset(Proj.assets, ['./shared', './client']))
|
||||||
|
.then(()=>console.timeEnd.bind(console, label))
|
||||||
.catch(console.error);
|
.catch(console.error);
|
||||||
@@ -1,23 +1,21 @@
|
|||||||
const label = 'dev';
|
const label = 'dev';
|
||||||
console.time(label);
|
console.time(label);
|
||||||
|
|
||||||
const jsx = require('vitreum/steps/jsx.watch.js').partial;
|
const jsx = require('vitreum/steps/jsx.watch.js');
|
||||||
const less = require('vitreum/steps/less.watch.js').partial;
|
const less = require('vitreum/steps/less.watch.js');
|
||||||
const assets = require('vitreum/steps/assets.watch.js').partial;
|
const assets = require('vitreum/steps/assets.watch.js');
|
||||||
const server = require('vitreum/steps/server.watch.js').partial;
|
const server = require('vitreum/steps/server.watch.js');
|
||||||
const livereload = require('vitreum/steps/livereload.js').partial;
|
const livereload = require('vitreum/steps/livereload.js');
|
||||||
|
|
||||||
const Proj = require('./project.json');
|
const Proj = require('./project.json');
|
||||||
|
|
||||||
Promise.resolve()
|
Promise.resolve()
|
||||||
.then(jsx('homebrew', './client/homebrew/homebrew.jsx', Proj.libs, './shared'))
|
.then(()=>jsx('homebrew', './client/homebrew/homebrew.jsx', Proj.libs, './shared'))
|
||||||
.then(less('homebrew', './shared'))
|
.then((deps)=>less('homebrew', './shared', deps))
|
||||||
|
.then(()=>jsx('admin', './client/admin/admin.jsx', Proj.libs, './shared'))
|
||||||
.then(jsx('admin', './client/admin/admin.jsx', Proj.libs, './shared'))
|
.then((deps)=>less('admin', './shared', deps))
|
||||||
.then(less('admin', './shared'))
|
.then(()=>assets(Proj.assets, ['./shared', './client']))
|
||||||
|
.then(()=>livereload())
|
||||||
.then(assets(Proj.assets, ['./shared', './client']))
|
.then(()=>server('./server.js', ['server']))
|
||||||
.then(livereload())
|
.then(()=>console.timeEnd.bind(console, label))
|
||||||
.then(server('./server.js', ['server']))
|
|
||||||
.then(console.timeEnd.bind(console, label))
|
|
||||||
.catch(console.error)
|
.catch(console.error)
|
||||||
8
scripts/notes.js
Normal file
8
scripts/notes.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
require('fixme')({
|
||||||
|
path: process.cwd(),
|
||||||
|
ignored_directories: ['node_modules/**', '.git/**', 'build/**'],
|
||||||
|
file_patterns: ['**/*.js', '**/*.jsx', '**/*.less'],
|
||||||
|
file_encoding: 'utf8',
|
||||||
|
line_length_limit: 200
|
||||||
|
});
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
const less = require('less');
|
|
||||||
const fs = require('fs');
|
|
||||||
|
|
||||||
less.render(fs.readFileSync('./client/homebrew/phbStyle/phb.style.less', 'utf8'), {compress : true})
|
|
||||||
.then((output) => {
|
|
||||||
fs.writeFileSync('./phb.standalone.css', output.css);
|
|
||||||
console.log('phb.standalone.css created!');
|
|
||||||
}, (err) => {
|
|
||||||
console.error(err);
|
|
||||||
});
|
|
||||||
22
scripts/populate.js
Normal file
22
scripts/populate.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
//Populates the DB with a bunch of brews for UI testing
|
||||||
|
const _ = require('lodash');
|
||||||
|
|
||||||
|
const DB = require('../server/db.js');
|
||||||
|
const BrewData = require('../server/brew.data.js');
|
||||||
|
const BrewGen = require('../test/brew.gen.js');
|
||||||
|
|
||||||
|
return Promise.resolve()
|
||||||
|
.then(DB.connect)
|
||||||
|
.then(BrewData.removeAll)
|
||||||
|
.then(() => {
|
||||||
|
console.log('Adding random brews...');
|
||||||
|
return BrewGen.populateDB(BrewGen.random(50));
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
console.log('Adding specific brews...');
|
||||||
|
return BrewGen.populateDB(BrewGen.static());
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
return DB.close();
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"assets": ["*.png","*.otf", "*.ico"],
|
"assets": ["*.png","*.otf", "*.ico", "*.jpg"],
|
||||||
"shared":[
|
"shared":["./shared"],
|
||||||
],
|
|
||||||
"libs" : [
|
"libs" : [
|
||||||
"react",
|
"react",
|
||||||
"react-dom",
|
"react-dom",
|
||||||
@@ -10,6 +9,7 @@
|
|||||||
"codemirror",
|
"codemirror",
|
||||||
"codemirror/mode/gfm/gfm.js",
|
"codemirror/mode/gfm/gfm.js",
|
||||||
"codemirror/mode/javascript/javascript.js",
|
"codemirror/mode/javascript/javascript.js",
|
||||||
|
"codemirror/mode/css/css.js",
|
||||||
"moment",
|
"moment",
|
||||||
"superagent",
|
"superagent",
|
||||||
"marked",
|
"marked",
|
||||||
|
|||||||
8
scripts/snippet.test.js
Normal file
8
scripts/snippet.test.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
const snippets = require('../shared/homebrewery/snippets');
|
||||||
|
|
||||||
|
console.log(snippets);
|
||||||
|
|
||||||
|
//console.log(snippets.brew.spell());
|
||||||
|
//console.log(snippets.brew.table());
|
||||||
|
|
||||||
|
console.log(snippets.brew.noncasterTable());
|
||||||
150
server.js
150
server.js
@@ -1,140 +1,36 @@
|
|||||||
const _ = require('lodash');
|
|
||||||
const jwt = require('jwt-simple');
|
|
||||||
const express = require("express");
|
|
||||||
const app = express();
|
|
||||||
|
|
||||||
app.use(express.static(__dirname + '/build'));''
|
|
||||||
app.use(require('body-parser').json({limit: '25mb'}));
|
|
||||||
app.use(require('cookie-parser')());
|
|
||||||
|
|
||||||
const config = require('nconf')
|
const config = require('nconf')
|
||||||
.argv()
|
.argv()
|
||||||
.env({ lowerCase: true })
|
.env({ lowerCase: true })
|
||||||
.file('environment', { file: `config/${process.env.NODE_ENV}.json` })
|
.file('environment', { file: `config/${process.env.NODE_ENV}.json` })
|
||||||
.file('defaults', { file: 'config/default.json' });
|
.file('defaults', { file: 'config/default.json' });
|
||||||
|
|
||||||
//DB
|
const log = require('loglevel');
|
||||||
require('mongoose')
|
log.setLevel(config.get('log_level'));
|
||||||
.connect(process.env.MONGODB_URI || process.env.MONGOLAB_URI || 'mongodb://localhost/naturalcrit')
|
|
||||||
.connection.on('error', () => {
|
|
||||||
console.log('Error : Could not connect to a Mongo Database.');
|
|
||||||
console.log(' If you are running locally, make sure mongodb.exe is running.');
|
|
||||||
});
|
|
||||||
|
|
||||||
|
//Server
|
||||||
|
const app = require('./server/app.js');
|
||||||
|
|
||||||
//Account MIddleware
|
/*
|
||||||
app.use((req, res, next) => {
|
app.use((req, res, next) => {
|
||||||
if(req.cookies && req.cookies.nc_session){
|
log.debug('---------------------------');
|
||||||
try{
|
log.debug(req.method, req.path);
|
||||||
req.account = jwt.decode(req.cookies.nc_session, config.get('secret'));
|
|
||||||
}catch(e){}
|
if (req.params) {
|
||||||
|
log.debug('req params', req.params);
|
||||||
}
|
}
|
||||||
return next();
|
if (req.query) {
|
||||||
|
log.debug('req query', req.query);
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
|
require('./server/db.js').connect()
|
||||||
app.use(require('./server/homebrew.api.js'));
|
.then(()=>{
|
||||||
app.use(require('./server/admin.api.js'));
|
const PORT = process.env.PORT || 8000;
|
||||||
|
const httpServer = app.listen(PORT, () => {
|
||||||
|
log.info(`server on port:${PORT}`);
|
||||||
const HomebrewModel = require('./server/homebrew.model.js').model;
|
|
||||||
const welcomeText = require('fs').readFileSync('./client/homebrew/pages/homePage/welcome_msg.md', 'utf8');
|
|
||||||
const changelogText = require('fs').readFileSync('./changelog.md', 'utf8');
|
|
||||||
|
|
||||||
|
|
||||||
//Source page
|
|
||||||
String.prototype.replaceAll = function(s,r){return this.split(s).join(r)}
|
|
||||||
app.get('/source/:id', (req, res)=>{
|
|
||||||
HomebrewModel.get({shareId : req.params.id})
|
|
||||||
.then((brew)=>{
|
|
||||||
const text = brew.text.replaceAll('<', '<').replaceAll('>', '>');
|
|
||||||
return res.send(`<code><pre>${text}</pre></code>`);
|
|
||||||
})
|
|
||||||
.catch((err)=>{
|
|
||||||
console.log(err);
|
|
||||||
return res.status(404).send('Could not find Homebrew with that id');
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
app.get('/user/:username', (req, res, next) => {
|
|
||||||
const fullAccess = req.account && (req.account.username == req.params.username);
|
|
||||||
HomebrewModel.getByUser(req.params.username, fullAccess)
|
|
||||||
.then((brews) => {
|
|
||||||
req.brews = brews;
|
|
||||||
return next();
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.log(err);
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
app.get('/edit/:id', (req, res, next)=>{
|
|
||||||
HomebrewModel.get({editId : req.params.id})
|
|
||||||
.then((brew)=>{
|
|
||||||
req.brew = brew.sanatize();
|
|
||||||
return next();
|
|
||||||
})
|
|
||||||
.catch((err)=>{
|
|
||||||
console.log(err);
|
|
||||||
return res.status(400).send(`Can't get that`);
|
|
||||||
});
|
});
|
||||||
});
|
})
|
||||||
|
.catch((err)=>console.error(err))
|
||||||
//Share Page
|
|
||||||
app.get('/share/:id', (req, res, next)=>{
|
|
||||||
HomebrewModel.get({shareId : req.params.id})
|
|
||||||
.then((brew)=>{
|
|
||||||
return brew.increaseView();
|
|
||||||
})
|
|
||||||
.then((brew)=>{
|
|
||||||
req.brew = brew.sanatize(true);
|
|
||||||
return next();
|
|
||||||
})
|
|
||||||
.catch((err)=>{
|
|
||||||
console.log(err);
|
|
||||||
return res.status(400).send(`Can't get that`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
//Print Page
|
|
||||||
app.get('/print/:id', (req, res, next)=>{
|
|
||||||
HomebrewModel.get({shareId : req.params.id})
|
|
||||||
.then((brew)=>{
|
|
||||||
req.brew = brew.sanatize(true);
|
|
||||||
return next();
|
|
||||||
})
|
|
||||||
.catch((err)=>{
|
|
||||||
console.log(err);
|
|
||||||
return res.status(400).send(`Can't get that`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
//Render Page
|
|
||||||
const render = require('vitreum/steps/render');
|
|
||||||
const templateFn = require('./client/template.js');
|
|
||||||
app.use((req, res) => {
|
|
||||||
render('homebrew', templateFn, {
|
|
||||||
version : require('./package.json').version,
|
|
||||||
url: req.originalUrl,
|
|
||||||
welcomeText : welcomeText,
|
|
||||||
changelog : changelogText,
|
|
||||||
brew : req.brew,
|
|
||||||
brews : req.brews,
|
|
||||||
account : req.account
|
|
||||||
})
|
|
||||||
.then((page) => {
|
|
||||||
return res.send(page)
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.log(err);
|
|
||||||
return res.sendStatus(500);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
const PORT = process.env.PORT || 8000;
|
|
||||||
app.listen(PORT);
|
|
||||||
console.log(`server on port:${PORT}`);
|
|
||||||
|
|||||||
@@ -1,85 +0,0 @@
|
|||||||
const _ = require('lodash');
|
|
||||||
const auth = require('basic-auth');
|
|
||||||
const HomebrewModel = require('./homebrew.model.js').model;
|
|
||||||
const router = require('express').Router();
|
|
||||||
|
|
||||||
|
|
||||||
const mw = {
|
|
||||||
adminOnly : (req, res, next)=>{
|
|
||||||
if(req.query && req.query.admin_key == process.env.ADMIN_KEY) return next();
|
|
||||||
return res.status(401).send('Access denied');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
process.env.ADMIN_USER = process.env.ADMIN_USER || 'admin';
|
|
||||||
process.env.ADMIN_PASS = process.env.ADMIN_PASS || 'password';
|
|
||||||
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
|
|
||||||
router.get('/api/invalid', mw.adminOnly, (req, res)=>{
|
|
||||||
const invalidBrewQuery = HomebrewModel.find({
|
|
||||||
'$where' : "this.text.length < 140",
|
|
||||||
createdAt: {
|
|
||||||
$lt: Moment().subtract(3, 'days').toDate()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if(req.query.do_it){
|
|
||||||
invalidBrewQuery.remove().exec((err, objs)=>{
|
|
||||||
refreshCount();
|
|
||||||
return res.send(200);
|
|
||||||
})
|
|
||||||
}else{
|
|
||||||
invalidBrewQuery.exec((err, objs)=>{
|
|
||||||
if(err) console.log(err);
|
|
||||||
return res.json({
|
|
||||||
count : objs.length
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get('/admin/lookup/:id', mw.adminOnly, (req, res, next) => {
|
|
||||||
//search for mathcing edit id
|
|
||||||
//search for matching share id
|
|
||||||
// search for partial match
|
|
||||||
|
|
||||||
HomebrewModel.findOne({ $or:[
|
|
||||||
{editId : { "$regex": req.params.id, "$options": "i" }},
|
|
||||||
{shareId : { "$regex": req.params.id, "$options": "i" }},
|
|
||||||
]}).exec((err, brew) => {
|
|
||||||
return res.json(brew);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//Admin route
|
|
||||||
|
|
||||||
const render = require('vitreum/steps/render');
|
|
||||||
const templateFn = require('../client/template.js');
|
|
||||||
router.get('/admin', function(req, res){
|
|
||||||
const credentials = auth(req)
|
|
||||||
if (!credentials || credentials.name !== process.env.ADMIN_USER || credentials.pass !== process.env.ADMIN_PASS) {
|
|
||||||
res.setHeader('WWW-Authenticate', 'Basic realm="example"')
|
|
||||||
return res.status(401).send('Access denied')
|
|
||||||
}
|
|
||||||
render('admin', templateFn, {
|
|
||||||
url: req.originalUrl,
|
|
||||||
admin_key : process.env.ADMIN_KEY,
|
|
||||||
})
|
|
||||||
.then((page) => {
|
|
||||||
return res.send(page)
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.log(err);
|
|
||||||
return res.sendStatus(500);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = router;
|
|
||||||
60
server/admin.routes.js
Normal file
60
server/admin.routes.js
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
const _ = require('lodash');
|
||||||
|
const router = require('express').Router();
|
||||||
|
const vitreumRender = require('vitreum/steps/render');
|
||||||
|
const templateFn = require('../client/template.js');
|
||||||
|
const config = require('nconf');
|
||||||
|
const Moment = require('moment');
|
||||||
|
|
||||||
|
const mw = require('./middleware.js');
|
||||||
|
const BrewData = require('./brew.data.js');
|
||||||
|
|
||||||
|
|
||||||
|
const getInvalidBrewQuery = ()=>{
|
||||||
|
return BrewData.model.find({
|
||||||
|
'$where' : "this.text.length < 140",
|
||||||
|
createdAt: {
|
||||||
|
$lt: Moment().subtract(3, 'days').toDate()
|
||||||
|
}
|
||||||
|
}).select({ text : false });
|
||||||
|
}
|
||||||
|
|
||||||
|
router.get('/admin', mw.adminLogin, (req, res, next) => {
|
||||||
|
return vitreumRender('admin', templateFn, {
|
||||||
|
url : req.originalUrl,
|
||||||
|
admin_key : config.get('admin:key')
|
||||||
|
})
|
||||||
|
.then((page) => {
|
||||||
|
return res.send(page)
|
||||||
|
})
|
||||||
|
.catch(next);
|
||||||
|
});
|
||||||
|
|
||||||
|
//Removes all empty brews that are older than 3 days and that are shorter than a tweet
|
||||||
|
router.get('/admin/invalid', mw.adminOnly, (req, res, next)=>{
|
||||||
|
getInvalidBrewQuery().exec()
|
||||||
|
.then((brews) => {
|
||||||
|
return res.json(brews);
|
||||||
|
})
|
||||||
|
.catch(next);
|
||||||
|
});
|
||||||
|
router.delete('/admin/invalid', mw.adminOnly, (req, res, next)=>{
|
||||||
|
getInvalidBrewQuery().remove()
|
||||||
|
.then(()=>{
|
||||||
|
return res.status(200).send();
|
||||||
|
})
|
||||||
|
.catch(next);
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/admin/lookup/:search', mw.adminOnly, (req, res, next) => {
|
||||||
|
BrewData.get({ $or:[
|
||||||
|
//Searches for partial matches on either edit or share
|
||||||
|
{editId : { "$regex": req.params.search, "$options": "i" }},
|
||||||
|
{shareId : { "$regex": req.params.search, "$options": "i" }},
|
||||||
|
]})
|
||||||
|
.then((brews) => {
|
||||||
|
return res.json(brews);
|
||||||
|
})
|
||||||
|
.catch(next);
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
28
server/app.js
Normal file
28
server/app.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
const config = require('nconf');
|
||||||
|
const express = require("express");
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
app.use(express.static(__dirname + '/../build'));
|
||||||
|
app.use(require('body-parser').json({limit: '25mb'}));
|
||||||
|
app.use(require('cookie-parser')());
|
||||||
|
|
||||||
|
|
||||||
|
//Middleware
|
||||||
|
const mw = require('./middleware.js');
|
||||||
|
app.use(mw.account);
|
||||||
|
app.use(mw.admin);
|
||||||
|
|
||||||
|
|
||||||
|
//Routes
|
||||||
|
app.use(require('./brew.api.js'));
|
||||||
|
app.use(require('./interface.routes.js'));
|
||||||
|
app.use(require('./admin.routes.js'));
|
||||||
|
|
||||||
|
if(config.get('NODE_ENV') !== 'staging' && config.get('NODE_ENV') !== 'production'){
|
||||||
|
app.use(require('./dev.routes.js'));
|
||||||
|
}
|
||||||
|
|
||||||
|
//Error Handler
|
||||||
|
app.use(require('./error.js').expressHandler);
|
||||||
|
|
||||||
|
module.exports = app;
|
||||||
68
server/brew.api.js
Normal file
68
server/brew.api.js
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
const _ = require('lodash');
|
||||||
|
const router = require('express').Router();
|
||||||
|
|
||||||
|
const BrewData = require('./brew.data.js');
|
||||||
|
const mw = require('./middleware.js');
|
||||||
|
|
||||||
|
//Search
|
||||||
|
router.get('/api/brew', (req, res, next) => {
|
||||||
|
const opts = _.pick(req.query, ['limit', 'sort', 'page']);
|
||||||
|
|
||||||
|
BrewData.termSearch(req.query.terms, opts, req.admin)
|
||||||
|
.then((result) => {
|
||||||
|
return res.status(200).json(result);
|
||||||
|
})
|
||||||
|
.catch(next);
|
||||||
|
});
|
||||||
|
|
||||||
|
//User
|
||||||
|
router.get('/api/user/:username', (req, res, next) => {
|
||||||
|
const fullAccess = req.admin ||
|
||||||
|
!!(req.account && req.params.username == req.account.username);
|
||||||
|
|
||||||
|
BrewData.userSearch(req.params.username, fullAccess)
|
||||||
|
.then((result) => {
|
||||||
|
return res.status(200).json(result);
|
||||||
|
})
|
||||||
|
.catch(next);
|
||||||
|
});
|
||||||
|
|
||||||
|
//Get
|
||||||
|
router.get('/api/brew/:shareId', mw.viewBrew, (req, res, next) => {
|
||||||
|
return res.json(req.brew.toJSON());
|
||||||
|
});
|
||||||
|
|
||||||
|
//Create
|
||||||
|
router.post('/api/brew', (req, res, next)=>{
|
||||||
|
const brew = req.body;
|
||||||
|
if(req.account) brew.authors = [req.account.username];
|
||||||
|
BrewData.create(brew)
|
||||||
|
.then((brew) => {
|
||||||
|
return res.json(brew.toJSON());
|
||||||
|
})
|
||||||
|
.catch(next)
|
||||||
|
});
|
||||||
|
|
||||||
|
//Update
|
||||||
|
router.put('/api/brew/:editId', mw.loadBrew, (req, res, next)=>{
|
||||||
|
const brew = req.body || {};
|
||||||
|
if(req.account){
|
||||||
|
brew.authors = _.uniq(_.concat(brew.authors, req.account.username));
|
||||||
|
}
|
||||||
|
BrewData.update(req.params.editId, brew)
|
||||||
|
.then((brew) => {
|
||||||
|
return res.json(brew.toJSON());
|
||||||
|
})
|
||||||
|
.catch(next);
|
||||||
|
});
|
||||||
|
|
||||||
|
//Delete
|
||||||
|
router.delete('/api/brew/:editId', mw.loadBrew, (req, res, next) => {
|
||||||
|
BrewData.remove(req.params.editId)
|
||||||
|
.then(()=>{
|
||||||
|
return res.sendStatus(200);
|
||||||
|
})
|
||||||
|
.catch(next);
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
102
server/brew.data.js
Normal file
102
server/brew.data.js
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
const _ = require('lodash');
|
||||||
|
const shortid = require('shortid');
|
||||||
|
const mongoose = require('./db.js').instance;
|
||||||
|
|
||||||
|
const Error = require('./error.js');
|
||||||
|
const utils = require('./utils.js');
|
||||||
|
|
||||||
|
const BrewSchema = mongoose.Schema({
|
||||||
|
shareId : {type : String, default: shortid.generate, index: { unique: true }},
|
||||||
|
editId : {type : String, default: shortid.generate, index: { unique: true }},
|
||||||
|
|
||||||
|
text : {type : String, default : ""},
|
||||||
|
style : {type : String, default : ""},
|
||||||
|
|
||||||
|
title : {type : String, default : ""},
|
||||||
|
description : {type : String, default : ""},
|
||||||
|
tags : {type : String, default : ""},
|
||||||
|
thumbnail : {type : String, default : ""},
|
||||||
|
systems : [String],
|
||||||
|
authors : [String],
|
||||||
|
published : {type : Boolean, default : false},
|
||||||
|
|
||||||
|
createdAt : { type: Date, default: Date.now },
|
||||||
|
updatedAt : { type: Date, default: Date.now},
|
||||||
|
lastViewed : { type: Date, default: Date.now},
|
||||||
|
views : {type:Number, default:0},
|
||||||
|
version : {type: Number, default:2}
|
||||||
|
}, {
|
||||||
|
versionKey: false,
|
||||||
|
toJSON : {
|
||||||
|
transform: (doc, ret, options) => {
|
||||||
|
delete ret._id;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//Index these fields for fast text searching
|
||||||
|
BrewSchema.index({ title: "text", description: "text" });
|
||||||
|
|
||||||
|
BrewSchema.methods.increaseView = function(){
|
||||||
|
this.views = this.views + 1;
|
||||||
|
return this.save();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const Brew = mongoose.model('Brew', BrewSchema);
|
||||||
|
const BrewData = {
|
||||||
|
schema : BrewSchema,
|
||||||
|
model : Brew,
|
||||||
|
|
||||||
|
get : (query) => {
|
||||||
|
return Brew.findOne(query).exec()
|
||||||
|
.then((brew) => {
|
||||||
|
if(!brew) throw Error.noBrew();
|
||||||
|
return brew;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
create : (brew) => {
|
||||||
|
delete brew.shareId;
|
||||||
|
delete brew.editId;
|
||||||
|
if(!brew.title) brew.title = utils.getGoodBrewTitle(brew.text);
|
||||||
|
return (new Brew(brew)).save();
|
||||||
|
},
|
||||||
|
update : (editId, newBrew) => {
|
||||||
|
return BrewData.get({ editId })
|
||||||
|
.then((brew) => {
|
||||||
|
delete newBrew.shareId;
|
||||||
|
delete newBrew.editId;
|
||||||
|
brew = _.merge(brew, newBrew, { updatedAt : Date.now() });
|
||||||
|
|
||||||
|
brew.markModified('authors');
|
||||||
|
brew.markModified('systems');
|
||||||
|
return brew.save();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
remove : (editId) => {
|
||||||
|
return Brew.find({ editId }).remove().exec();
|
||||||
|
},
|
||||||
|
removeAll : ()=>{
|
||||||
|
return Brew.find({}).remove().exec();
|
||||||
|
},
|
||||||
|
|
||||||
|
//////// Special
|
||||||
|
|
||||||
|
getByShare : (shareId) => {
|
||||||
|
return BrewData.get({ shareId : shareId})
|
||||||
|
.then((brew) => {
|
||||||
|
brew.increaseView();
|
||||||
|
const brewJSON = brew.toJSON();
|
||||||
|
delete brewJSON.editId;
|
||||||
|
return brewJSON;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getByEdit : (editId) => {
|
||||||
|
return BrewData.get({ editId });
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const BrewSearch = require('./brew.search.js')(Brew);
|
||||||
|
|
||||||
|
module.exports = _.merge(BrewData, BrewSearch);
|
||||||
66
server/brew.search.js
Normal file
66
server/brew.search.js
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
const _ = require('lodash');
|
||||||
|
|
||||||
|
module.exports = (Brew) => {
|
||||||
|
const cmds = {
|
||||||
|
termSearch : (terms='', opts, fullAccess) => {
|
||||||
|
let query = {};
|
||||||
|
if(terms){
|
||||||
|
query = {$text: {
|
||||||
|
//Wrap terms in quotes to perform an AND operation
|
||||||
|
$search: _.map(terms.split(' '), (term)=>{
|
||||||
|
return `\"${term}\"`;
|
||||||
|
}).join(' '),
|
||||||
|
$caseSensitive : false
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
return cmds.search(query, opts, fullAccess);
|
||||||
|
},
|
||||||
|
|
||||||
|
userSearch : (username, fullAccess) => {
|
||||||
|
const query = {
|
||||||
|
authors : username
|
||||||
|
};
|
||||||
|
|
||||||
|
return cmds.search(query, {}, fullAccess);
|
||||||
|
},
|
||||||
|
|
||||||
|
search : (queryObj={}, options={}, fullAccess = true) => {
|
||||||
|
const opts = _.merge({
|
||||||
|
limit : 25,
|
||||||
|
page : 0,
|
||||||
|
sort : {}
|
||||||
|
}, options);
|
||||||
|
opts.limit = _.toNumber(opts.limit);
|
||||||
|
opts.page = _.toNumber(opts.page);
|
||||||
|
|
||||||
|
let filter = {
|
||||||
|
text : 0
|
||||||
|
};
|
||||||
|
|
||||||
|
if(!fullAccess){
|
||||||
|
filter.editId = 0;
|
||||||
|
queryObj.published = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchQuery = Brew
|
||||||
|
.find(queryObj)
|
||||||
|
.sort(opts.sort)
|
||||||
|
.select(filter)
|
||||||
|
.limit(opts.limit)
|
||||||
|
.skip(opts.page * opts.limit)
|
||||||
|
.lean()
|
||||||
|
.exec();
|
||||||
|
|
||||||
|
const countQuery = Brew.count(queryObj).exec();
|
||||||
|
|
||||||
|
return Promise.all([searchQuery, countQuery])
|
||||||
|
.then((result) => {
|
||||||
|
return {
|
||||||
|
brews : result[0],
|
||||||
|
total : result[1]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return cmds;
|
||||||
|
};
|
||||||
36
server/db.js
Normal file
36
server/db.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
const log = require('loglevel');
|
||||||
|
const mongoose = require('mongoose');
|
||||||
|
mongoose.Promise = Promise;
|
||||||
|
|
||||||
|
const dbPath = process.env.MONGODB_URI || process.env.MONGOLAB_URI || 'mongodb://localhost/homebrewery';
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
connect : ()=>{
|
||||||
|
return new Promise((resolve, reject)=>{
|
||||||
|
if(mongoose.connection.readyState !== 0){
|
||||||
|
log.warn('DB already connected');
|
||||||
|
return resolve();
|
||||||
|
}
|
||||||
|
mongoose.connect(dbPath,
|
||||||
|
(err) => {
|
||||||
|
if(err){
|
||||||
|
log.error('Error : Could not connect to a Mongo Database.');
|
||||||
|
log.error(' If you are running locally, make sure mongodb.exe is running.');
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
log.info('DB connected.');
|
||||||
|
return resolve();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
close : ()=>{
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
mongoose.connection.close(()=>{
|
||||||
|
log.info('DB connection closed.');
|
||||||
|
return resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
instance : mongoose
|
||||||
|
}
|
||||||
29
server/dev.routes.js
Normal file
29
server/dev.routes.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
const router = require('express').Router();
|
||||||
|
const jwt = require('jwt-simple');
|
||||||
|
const auth = require('basic-auth');
|
||||||
|
const config = require('nconf');
|
||||||
|
|
||||||
|
if(process.env.NODE_ENV == 'production') throw 'Loading dev routes in prod. ABORT ABORT';
|
||||||
|
|
||||||
|
|
||||||
|
router.get('/dev/login', (req, res, next) => {
|
||||||
|
const user = req.query.user;
|
||||||
|
if(!user){
|
||||||
|
return res.send(`
|
||||||
|
<html>
|
||||||
|
<body>dev login</body>
|
||||||
|
<script>
|
||||||
|
var user = prompt('enter username');
|
||||||
|
if(user) window.location = '/dev/login?user=' + encodeURIComponent(user);
|
||||||
|
</script></html>
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
res.cookie('nc_session', jwt.encode({username : req.query.user}, config.get('jwt_secret')));
|
||||||
|
return res.redirect('/');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
25
server/error.js
Normal file
25
server/error.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
|
||||||
|
const Error = require('egads').extend('Server Error', 500, 'Generic Server Error');
|
||||||
|
|
||||||
|
Error.noBrew = Error.extend('Can not find a brew with that id', 404, 'No Brew Found');
|
||||||
|
Error.noAuth = Error.extend('You can not access this route', 401, 'Unauthorized');
|
||||||
|
Error.noAdmin = Error.extend('You need admin credentials to access this route', 401, 'Unauthorized');
|
||||||
|
|
||||||
|
|
||||||
|
Error.expressHandler = (err, req, res, next) => {
|
||||||
|
if(err instanceof Error){
|
||||||
|
return res.status(err.status).send({
|
||||||
|
type : err.name,
|
||||||
|
message : err.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//If server error, print the whole stack for debugging
|
||||||
|
return res.status(500).send({
|
||||||
|
message : err.message,
|
||||||
|
stack : err.stack
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = Error;
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
const _ = require('lodash');
|
|
||||||
const Moment = require('moment');
|
|
||||||
const HomebrewModel = require('./homebrew.model.js').model;
|
|
||||||
const router = require('express').Router();
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//TODO: Possiblity remove
|
|
||||||
let homebrewTotal = 0;
|
|
||||||
const refreshCount = ()=>{
|
|
||||||
HomebrewModel.count({}, (err, total)=>{
|
|
||||||
homebrewTotal = total;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
refreshCount();
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const getTopBrews = (cb)=>{
|
|
||||||
HomebrewModel.find().sort({views: -1}).limit(5).exec(function(err, brews) {
|
|
||||||
cb(brews);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const getGoodBrewTitle = (text) => {
|
|
||||||
const titlePos = text.indexOf('# ');
|
|
||||||
if(titlePos !== -1){
|
|
||||||
const ending = text.indexOf('\n', titlePos);
|
|
||||||
return text.substring(titlePos + 2, ending);
|
|
||||||
}else{
|
|
||||||
return _.find(text.split('\n'), (line)=>{
|
|
||||||
return line;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
router.post('/api', (req, res)=>{
|
|
||||||
|
|
||||||
let authors = [];
|
|
||||||
if(req.account) authors = [req.account.username];
|
|
||||||
|
|
||||||
const newHomebrew = new HomebrewModel(_.merge({},
|
|
||||||
req.body,
|
|
||||||
{authors : authors}
|
|
||||||
));
|
|
||||||
if(!newHomebrew.title){
|
|
||||||
newHomebrew.title = getGoodBrewTitle(newHomebrew.text);
|
|
||||||
}
|
|
||||||
newHomebrew.save((err, obj)=>{
|
|
||||||
if(err){
|
|
||||||
console.error(err, err.toString(), err.stack);
|
|
||||||
return res.status(500).send(`Error while creating new brew, ${err.toString()}`);
|
|
||||||
}
|
|
||||||
return res.json(obj);
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
router.put('/api/update/:id', (req, res)=>{
|
|
||||||
HomebrewModel.get({editId : req.params.id})
|
|
||||||
.then((brew)=>{
|
|
||||||
brew = _.merge(brew, req.body);
|
|
||||||
brew.updatedAt = new Date();
|
|
||||||
if(req.account) brew.authors = _.uniq(_.concat(brew.authors, req.account.username));
|
|
||||||
|
|
||||||
brew.markModified('authors');
|
|
||||||
brew.markModified('systems');
|
|
||||||
|
|
||||||
brew.save((err, obj)=>{
|
|
||||||
if(err) throw err;
|
|
||||||
return res.status(200).send(obj);
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.catch((err)=>{
|
|
||||||
console.log(err);
|
|
||||||
return res.status(500).send("Error while saving");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
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)=>{
|
|
||||||
if(err) return res.status(500).send("Error while removing");
|
|
||||||
return res.status(200).send();
|
|
||||||
})
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = router;
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = function(app){
|
|
||||||
|
|
||||||
app;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
app.get('/api/search', mw.adminOnly, function(req, res){
|
|
||||||
|
|
||||||
var page = req.query.page || 0;
|
|
||||||
var count = req.query.count || 20;
|
|
||||||
|
|
||||||
var query = {};
|
|
||||||
if(req.query && req.query.id){
|
|
||||||
query = {
|
|
||||||
"$or" : [{
|
|
||||||
editId : req.query.id
|
|
||||||
},{
|
|
||||||
shareId : req.query.id
|
|
||||||
}]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
HomebrewModel.find(query, {
|
|
||||||
text : 0 //omit the text
|
|
||||||
}, {
|
|
||||||
skip: page*count,
|
|
||||||
limit: count*1
|
|
||||||
}, function(err, objs){
|
|
||||||
if(err) console.log(err);
|
|
||||||
return res.json({
|
|
||||||
page : page,
|
|
||||||
count : count,
|
|
||||||
total : homebrewTotal,
|
|
||||||
brews : objs
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return app;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
var mongoose = require('mongoose');
|
|
||||||
var shortid = require('shortid');
|
|
||||||
var _ = require('lodash');
|
|
||||||
|
|
||||||
var HomebrewSchema = mongoose.Schema({
|
|
||||||
shareId : {type : String, default: shortid.generate, index: { unique: true }},
|
|
||||||
editId : {type : String, default: shortid.generate, index: { unique: true }},
|
|
||||||
title : {type : String, default : ""},
|
|
||||||
text : {type : String, default : ""},
|
|
||||||
|
|
||||||
description : {type : String, default : ""},
|
|
||||||
tags : {type : String, default : ""},
|
|
||||||
systems : [String],
|
|
||||||
authors : [String],
|
|
||||||
published : {type : Boolean, default : false},
|
|
||||||
|
|
||||||
createdAt : { type: Date, default: Date.now },
|
|
||||||
updatedAt : { type: Date, default: Date.now},
|
|
||||||
lastViewed : { type: Date, default: Date.now},
|
|
||||||
views : {type:Number, default:0},
|
|
||||||
version : {type: Number, default:1}
|
|
||||||
}, { versionKey: false });
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
HomebrewSchema.methods.sanatize = function(full=false){
|
|
||||||
const brew = this.toJSON();
|
|
||||||
delete brew._id;
|
|
||||||
delete brew.__v;
|
|
||||||
if(full){
|
|
||||||
delete brew.editId;
|
|
||||||
}
|
|
||||||
return brew;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
HomebrewSchema.methods.increaseView = function(){
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
this.lastViewed = new Date();
|
|
||||||
this.views = this.views + 1;
|
|
||||||
this.save((err) => {
|
|
||||||
if(err) return reject(err);
|
|
||||||
return resolve(this);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
HomebrewSchema.statics.get = function(query){
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
Homebrew.find(query, (err, brews)=>{
|
|
||||||
if(err || !brews.length) return reject('Can not find brew');
|
|
||||||
return resolve(brews[0]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
HomebrewSchema.statics.getByUser = function(username, allowAccess=false){
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
let query = {authors : username, published : true};
|
|
||||||
if(allowAccess){
|
|
||||||
delete query.published;
|
|
||||||
}
|
|
||||||
Homebrew.find(query, (err, brews)=>{
|
|
||||||
if(err) return reject('Can not find brew');
|
|
||||||
return resolve(_.map(brews, (brew)=>{
|
|
||||||
return brew.sanatize(!allowAccess);
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var Homebrew = mongoose.model('Homebrew', HomebrewSchema);
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
schema : HomebrewSchema,
|
|
||||||
model : Homebrew,
|
|
||||||
}
|
|
||||||
124
server/interface.routes.js
Normal file
124
server/interface.routes.js
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
const _ = require('lodash');
|
||||||
|
const fs = require('fs');
|
||||||
|
const config = require('nconf');
|
||||||
|
const utils = require('./utils.js');
|
||||||
|
const BrewData = require('./brew.data.js');
|
||||||
|
const router = require('express').Router();
|
||||||
|
const mw = require('./middleware.js');
|
||||||
|
|
||||||
|
|
||||||
|
const statics = {
|
||||||
|
welcomeBrew : fs.readFileSync('./statics/welcome.brew.md', 'utf8'),
|
||||||
|
changelog : fs.readFileSync('./statics/changelog.md', 'utf8'),
|
||||||
|
faq : fs.readFileSync('./statics/faq.md', 'utf8'),
|
||||||
|
|
||||||
|
testBrew : fs.readFileSync('./statics/test.brew.md', 'utf8'),
|
||||||
|
oldTest : fs.readFileSync('./statics/oldTest.brew.md', 'utf8'),
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const vitreumRender = require('vitreum/steps/render');
|
||||||
|
const templateFn = require('../client/template.js');
|
||||||
|
//TODO: Catch errors here?
|
||||||
|
const renderPage = (req, res, next) => {
|
||||||
|
return vitreumRender('homebrew', templateFn, {
|
||||||
|
url : req.originalUrl,
|
||||||
|
version : require('../package.json').version,
|
||||||
|
loginPath : config.get('login_path'),
|
||||||
|
|
||||||
|
user : req.account && req.account.username,
|
||||||
|
brews : req.brews,
|
||||||
|
brew : req.brew
|
||||||
|
})
|
||||||
|
.then((page) => {
|
||||||
|
return res.send(page)
|
||||||
|
})
|
||||||
|
.catch(next);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//Share Page
|
||||||
|
router.get('/share/:shareId', mw.viewBrew, renderPage);
|
||||||
|
|
||||||
|
//Edit Page
|
||||||
|
router.get('/edit/:editId', mw.loadBrew, renderPage);
|
||||||
|
|
||||||
|
//Print Page
|
||||||
|
router.get('/print/:shareId', mw.viewBrew, renderPage);
|
||||||
|
router.get('/print', renderPage);
|
||||||
|
|
||||||
|
//Source page
|
||||||
|
router.get('/source/:sharedId', mw.viewBrew, (req, res, next)=>{
|
||||||
|
const text = utils.replaceByMap(req.brew.text, { '<' : '<', '>' : '>' });
|
||||||
|
return res.send(`<code><pre>${text}</pre></code>`);
|
||||||
|
});
|
||||||
|
|
||||||
|
//User Page
|
||||||
|
router.get('/user/:username', (req, res, next) => {
|
||||||
|
BrewData.search({ user : req.params.username })
|
||||||
|
.then((brews) => {
|
||||||
|
req.brews = brews;
|
||||||
|
return next();
|
||||||
|
})
|
||||||
|
.catch(next);
|
||||||
|
}, renderPage);
|
||||||
|
|
||||||
|
//Search Page
|
||||||
|
router.get('/search', (req, res, next) => {
|
||||||
|
//TODO: Double check that the defaults are okay
|
||||||
|
BrewData.search()
|
||||||
|
.then((brews) => {
|
||||||
|
req.brews = brews;
|
||||||
|
return next();
|
||||||
|
})
|
||||||
|
.catch(next);
|
||||||
|
}, renderPage);
|
||||||
|
|
||||||
|
//Changelog Page
|
||||||
|
router.get('/changelog', (req, res, next) => {
|
||||||
|
req.brew = {
|
||||||
|
text : statics.changelog,
|
||||||
|
title : 'Changelog'
|
||||||
|
};
|
||||||
|
return next();
|
||||||
|
}, renderPage);
|
||||||
|
|
||||||
|
//faq Page
|
||||||
|
router.get('/faq', (req, res, next) => {
|
||||||
|
req.brew = {
|
||||||
|
text : statics.faq,
|
||||||
|
title : 'FAQ',
|
||||||
|
|
||||||
|
editId : true
|
||||||
|
};
|
||||||
|
return next();
|
||||||
|
}, renderPage);
|
||||||
|
|
||||||
|
//New Page
|
||||||
|
router.get('/new', renderPage);
|
||||||
|
|
||||||
|
//Home Page
|
||||||
|
router.get('/', (req, res, next) => {
|
||||||
|
req.brew = { text : statics.welcomeBrew };
|
||||||
|
return next();
|
||||||
|
}, renderPage);
|
||||||
|
|
||||||
|
|
||||||
|
//Test pages
|
||||||
|
router.get('/test', (req, res, next) => {
|
||||||
|
req.brew = {
|
||||||
|
text : statics.testBrew
|
||||||
|
};
|
||||||
|
return next();
|
||||||
|
}, renderPage);
|
||||||
|
router.get('/test_old', (req, res, next) => {
|
||||||
|
req.brew = {
|
||||||
|
text : statics.oldTest,
|
||||||
|
version : 1
|
||||||
|
};
|
||||||
|
return next();
|
||||||
|
}, renderPage);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
69
server/middleware.js
Normal file
69
server/middleware.js
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
const _ = require('lodash');
|
||||||
|
const jwt = require('jwt-simple');
|
||||||
|
const auth = require('basic-auth');
|
||||||
|
const config = require('nconf');
|
||||||
|
|
||||||
|
const Error = require('./error.js');
|
||||||
|
const BrewData = require('./brew.data.js');
|
||||||
|
|
||||||
|
const Middleware = {
|
||||||
|
account : (req, res, next) => {
|
||||||
|
if(req.cookies && req.cookies.nc_session){
|
||||||
|
try{
|
||||||
|
req.account = jwt.decode(req.cookies.nc_session, config.get('jwt_secret'));
|
||||||
|
}catch(e){}
|
||||||
|
}
|
||||||
|
return next();
|
||||||
|
},
|
||||||
|
admin : (req, res, next) => {
|
||||||
|
req.admin = false;
|
||||||
|
if(req.headers['x-homebrew-admin'] === config.get('admin:key')){
|
||||||
|
req.admin = true;
|
||||||
|
}
|
||||||
|
return next();
|
||||||
|
},
|
||||||
|
|
||||||
|
//Filters
|
||||||
|
devOnly : (req, res, next) => {
|
||||||
|
const env = process.env.NODE_ENV;
|
||||||
|
if(env !== 'staging' && env !== 'production') return next();
|
||||||
|
return res.sendStatus(404);
|
||||||
|
},
|
||||||
|
adminOnly : (req, res, next) => {
|
||||||
|
if(req.admin) return next();
|
||||||
|
return next(Error.noAuth());
|
||||||
|
},
|
||||||
|
adminLogin : (req, res, next) => {
|
||||||
|
const creds = auth(req);
|
||||||
|
if(!creds
|
||||||
|
|| creds.name !== config.get('admin:user')
|
||||||
|
|| creds.pass !== config.get('admin:pass')){
|
||||||
|
res.setHeader('WWW-Authenticate', 'Basic realm="example"');
|
||||||
|
return next(Error.noAdmin());
|
||||||
|
}
|
||||||
|
return next();
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
//TODO: REMOVE
|
||||||
|
//Loaders
|
||||||
|
loadBrew : (req, res, next) => {
|
||||||
|
BrewData.getByEdit(req.params.editId)
|
||||||
|
.then((brew) => {
|
||||||
|
req.brew = brew;
|
||||||
|
return next()
|
||||||
|
})
|
||||||
|
.catch(next);
|
||||||
|
},
|
||||||
|
viewBrew : (req, res, next) => {
|
||||||
|
BrewData.getByShare(req.params.shareId)
|
||||||
|
.then((brew) => {
|
||||||
|
req.brew = brew;
|
||||||
|
return next()
|
||||||
|
})
|
||||||
|
.catch(next);
|
||||||
|
},
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = Middleware;
|
||||||
23
server/utils.js
Normal file
23
server/utils.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
const _ = require('lodash');
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getGoodBrewTitle : (text = '') => {
|
||||||
|
const titlePos = text.indexOf('# ');
|
||||||
|
if(titlePos !== -1){
|
||||||
|
let ending = text.indexOf('\n', titlePos);
|
||||||
|
ending = (ending == -1 ? undefined : ending);
|
||||||
|
return text.substring(titlePos + 2, ending).trim();
|
||||||
|
}else{
|
||||||
|
return (_.find(text.split('\n'), (line)=>{
|
||||||
|
return line;
|
||||||
|
}) || '').trim();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
replaceByMap : (text, mapping) => {
|
||||||
|
return _.reduce(mapping, (r, search, replace) => {
|
||||||
|
return r.split(search).join(replace)
|
||||||
|
}, text)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
147
shared/depricated/brewRendererOld/brewRendererOld.jsx
Normal file
147
shared/depricated/brewRendererOld/brewRendererOld.jsx
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
const React = require('react');
|
||||||
|
const _ = require('lodash');
|
||||||
|
const cx = require('classnames');
|
||||||
|
|
||||||
|
const Markdown = require('depricated/markdown.old.js');
|
||||||
|
const ErrorBar = require('./errorBar/errorBar.jsx');
|
||||||
|
|
||||||
|
const RenderWarnings = require('homebrewery/renderWarnings/renderWarnings.jsx')
|
||||||
|
const Store = require('homebrewery/brew.store.js');
|
||||||
|
|
||||||
|
|
||||||
|
const PAGE_HEIGHT = 1056;
|
||||||
|
const PPR_THRESHOLD = 50;
|
||||||
|
|
||||||
|
const OLD_BrewRenderer = React.createClass({
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
value : '',
|
||||||
|
style : '',
|
||||||
|
errors : []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getInitialState: function() {
|
||||||
|
const pages = this.props.value.split('\\page');
|
||||||
|
|
||||||
|
return {
|
||||||
|
viewablePageNumber: 0,
|
||||||
|
height : 0,
|
||||||
|
isMounted : false,
|
||||||
|
pages : pages,
|
||||||
|
usePPR : pages.length >= PPR_THRESHOLD
|
||||||
|
};
|
||||||
|
},
|
||||||
|
height : 0,
|
||||||
|
pageHeight : PAGE_HEIGHT,
|
||||||
|
lastRender : <div></div>,
|
||||||
|
|
||||||
|
componentDidMount: function() {
|
||||||
|
this.updateSize();
|
||||||
|
window.addEventListener("resize", this.updateSize);
|
||||||
|
},
|
||||||
|
componentWillUnmount: function() {
|
||||||
|
window.removeEventListener("resize", this.updateSize);
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillReceiveProps: function(nextProps) {
|
||||||
|
if(this.refs.pages && this.refs.pages.firstChild) this.pageHeight = this.refs.pages.firstChild.clientHeight;
|
||||||
|
|
||||||
|
const pages = nextProps.value.split('\\page');
|
||||||
|
this.setState({
|
||||||
|
pages : pages,
|
||||||
|
usePPR : pages.length >= PPR_THRESHOLD
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
updateSize : function() {
|
||||||
|
setTimeout(()=>{
|
||||||
|
if(this.refs.pages && this.refs.pages.firstChild) this.pageHeight = this.refs.pages.firstChild.clientHeight;
|
||||||
|
}, 1);
|
||||||
|
|
||||||
|
const parentNode = document.querySelector('.page .content');
|
||||||
|
this.setState({
|
||||||
|
height : parentNode.clientHeight,
|
||||||
|
isMounted : true
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
handleScroll : function(e){
|
||||||
|
this.setState({
|
||||||
|
viewablePageNumber : Math.floor(e.target.scrollTop / this.pageHeight)
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
shouldRender : function(pageText, index){
|
||||||
|
if(!this.state.isMounted) return false;
|
||||||
|
|
||||||
|
var viewIndex = this.state.viewablePageNumber;
|
||||||
|
if(index == viewIndex - 1) return true;
|
||||||
|
if(index == viewIndex) return true;
|
||||||
|
if(index == viewIndex + 1) return true;
|
||||||
|
|
||||||
|
//Check for style tages
|
||||||
|
if(pageText.indexOf('<style>') !== -1) return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
renderPageInfo : function(){
|
||||||
|
return <div className='pageInfo'>
|
||||||
|
{this.state.viewablePageNumber + 1} / {this.state.pages.length}
|
||||||
|
</div>
|
||||||
|
},
|
||||||
|
|
||||||
|
renderPPRmsg : function(){
|
||||||
|
if(!this.state.usePPR) return;
|
||||||
|
|
||||||
|
return <div className='ppr_msg'>
|
||||||
|
Partial Page Renderer enabled, because your brew is so large. May effect rendering.
|
||||||
|
</div>
|
||||||
|
},
|
||||||
|
|
||||||
|
renderDummyPage : function(index){
|
||||||
|
return <div className='phb v1' id={`p${index + 1}`} key={index}>
|
||||||
|
<i className='fa fa-spinner fa-spin' />
|
||||||
|
</div>
|
||||||
|
},
|
||||||
|
|
||||||
|
renderPage : function(pageText, index){
|
||||||
|
return <div className='phb v1' id={`p${index + 1}`} dangerouslySetInnerHTML={{__html:Markdown.render(pageText)}} key={index} />
|
||||||
|
},
|
||||||
|
|
||||||
|
renderPages : function(){
|
||||||
|
if(this.state.usePPR){
|
||||||
|
return _.map(this.state.pages, (page, index)=>{
|
||||||
|
if(this.shouldRender(page, index)){
|
||||||
|
return this.renderPage(page, index);
|
||||||
|
}else{
|
||||||
|
return this.renderDummyPage(index);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if(this.props.errors && this.props.errors.length) return this.lastRender;
|
||||||
|
this.lastRender = _.map(this.state.pages, (page, index)=>{
|
||||||
|
return this.renderPage(page, index);
|
||||||
|
});
|
||||||
|
return this.lastRender;
|
||||||
|
},
|
||||||
|
|
||||||
|
render : function(){
|
||||||
|
return <div className='brewRendererOld'
|
||||||
|
onScroll={this.handleScroll}
|
||||||
|
ref='main'
|
||||||
|
style={{height : this.state.height}}>
|
||||||
|
|
||||||
|
<ErrorBar errors={this.props.errors} />
|
||||||
|
<RenderWarnings />
|
||||||
|
|
||||||
|
<div className='pages' ref='pages'>
|
||||||
|
{this.renderPages()}
|
||||||
|
</div>
|
||||||
|
{this.renderPageInfo()}
|
||||||
|
{this.renderPPRmsg()}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = OLD_BrewRenderer;
|
||||||
40
shared/depricated/brewRendererOld/brewRendererOld.less
Normal file
40
shared/depricated/brewRendererOld/brewRendererOld.less
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
|
||||||
|
@import 'shared/depricated/phb_style_v1/phb.v1.less';
|
||||||
|
|
||||||
|
.pane{
|
||||||
|
position : relative;
|
||||||
|
}
|
||||||
|
.brewRendererOld{
|
||||||
|
overflow-y : scroll;
|
||||||
|
.pageInfo{
|
||||||
|
position : absolute;
|
||||||
|
right : 17px;
|
||||||
|
bottom : 0;
|
||||||
|
z-index : 1000;
|
||||||
|
padding : 8px 10px;
|
||||||
|
background-color : #333;
|
||||||
|
font-size : 10px;
|
||||||
|
font-weight : 800;
|
||||||
|
color : white;
|
||||||
|
}
|
||||||
|
.ppr_msg{
|
||||||
|
position : absolute;
|
||||||
|
left : 0px;
|
||||||
|
bottom : 0;
|
||||||
|
z-index : 1000;
|
||||||
|
padding : 8px 10px;
|
||||||
|
background-color : #333;
|
||||||
|
font-size : 10px;
|
||||||
|
font-weight : 800;
|
||||||
|
color : white;
|
||||||
|
}
|
||||||
|
.pages{
|
||||||
|
margin : 30px 0px;
|
||||||
|
&>.phb{
|
||||||
|
margin-right : auto;
|
||||||
|
margin-bottom : 30px;
|
||||||
|
margin-left : auto;
|
||||||
|
box-shadow : 1px 4px 14px #000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,47 +1,54 @@
|
|||||||
|
@media print {
|
||||||
|
.phb.v1{
|
||||||
|
.descriptive, blockquote{
|
||||||
|
box-shadow : none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@import (less) 'shared/naturalcrit/styles/reset.less';
|
.phb.v1{
|
||||||
@import (less) './client/homebrew/phbStyle/phb.fonts.css';
|
@import (less) './phb.fonts.v1.css';
|
||||||
@import (less) './client/homebrew/phbStyle/phb.assets.less';
|
@import (less) './phb.assets.v1.less';
|
||||||
@import (less) './client/homebrew/phbStyle/phb.depricated.less';
|
|
||||||
//Colors
|
|
||||||
@background : #EEE5CE;
|
//Colors
|
||||||
@noteGreen : #e0e5c1;
|
@background : #EEE5CE;
|
||||||
@headerUnderline : #c9ad6a;
|
@noteGreen : #e0e5c1;
|
||||||
@horizontalRule : #9c2b1b;
|
@headerUnderline : #c9ad6a;
|
||||||
@headerText : #58180D;
|
@horizontalRule : #9c2b1b;
|
||||||
@monsterStatBackground : #FDF1DC;
|
@headerText : #58180D;
|
||||||
@page { margin: 0; }
|
@monsterStatBackground : #FDF1DC;
|
||||||
body {
|
|
||||||
counter-reset : phb-page-numbers;
|
@page { margin: 0; }
|
||||||
}
|
|
||||||
*{
|
|
||||||
-webkit-print-color-adjust : exact;
|
.useSansSerif(){
|
||||||
}
|
|
||||||
.useSansSerif(){
|
|
||||||
font-family : ScalySans;
|
|
||||||
em{
|
|
||||||
font-family : ScalySans;
|
font-family : ScalySans;
|
||||||
font-style : italic;
|
em{
|
||||||
|
font-family : ScalySans;
|
||||||
|
font-style : italic;
|
||||||
|
}
|
||||||
|
strong{
|
||||||
|
font-family : ScalySans;
|
||||||
|
font-weight : 800;
|
||||||
|
letter-spacing : -0.02em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
strong{
|
.useColumns(@multiplier : 1){
|
||||||
font-family : ScalySans;
|
column-count : 2;
|
||||||
font-weight : 800;
|
column-fill : auto;
|
||||||
letter-spacing : -0.02em;
|
column-gap : 1cm;
|
||||||
|
column-width : 8cm * @multiplier;
|
||||||
|
-webkit-column-count : 2;
|
||||||
|
-moz-column-count : 2;
|
||||||
|
-webkit-column-width : 8cm * @multiplier;
|
||||||
|
-moz-column-width : 8cm * @multiplier;
|
||||||
|
-webkit-column-gap : 1cm;
|
||||||
|
-moz-column-gap : 1cm;
|
||||||
|
}
|
||||||
|
& *{
|
||||||
|
-webkit-print-color-adjust : exact;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
.useColumns(@multiplier : 1){
|
|
||||||
column-count : 2;
|
|
||||||
column-fill : auto;
|
|
||||||
column-gap : 1cm;
|
|
||||||
column-width : 8cm * @multiplier;
|
|
||||||
-webkit-column-count : 2;
|
|
||||||
-moz-column-count : 2;
|
|
||||||
-webkit-column-width : 8cm * @multiplier;
|
|
||||||
-moz-column-width : 8cm * @multiplier;
|
|
||||||
-webkit-column-gap : 1cm;
|
|
||||||
-moz-column-gap : 1cm;
|
|
||||||
}
|
|
||||||
.phb{
|
|
||||||
.useColumns();
|
.useColumns();
|
||||||
counter-increment : phb-page-numbers;
|
counter-increment : phb-page-numbers;
|
||||||
position : relative;
|
position : relative;
|
||||||
@@ -59,6 +66,7 @@ body {
|
|||||||
text-rendering : optimizeLegibility;
|
text-rendering : optimizeLegibility;
|
||||||
page-break-before : always;
|
page-break-before : always;
|
||||||
page-break-after : always;
|
page-break-after : always;
|
||||||
|
|
||||||
//*****************************
|
//*****************************
|
||||||
// * BASE
|
// * BASE
|
||||||
// *****************************/
|
// *****************************/
|
||||||
@@ -357,124 +365,157 @@ body {
|
|||||||
-webkit-column-break-inside : avoid;
|
-webkit-column-break-inside : avoid;
|
||||||
column-break-inside : avoid;
|
column-break-inside : avoid;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
//*****************************
|
//*****************************
|
||||||
// * SPELL LIST
|
// * SPELL LIST
|
||||||
// *****************************/
|
// *****************************/
|
||||||
.phb .spellList{
|
.spellList{
|
||||||
.useSansSerif();
|
.useSansSerif();
|
||||||
column-count : 4;
|
column-count : 4;
|
||||||
column-span : all;
|
column-span : all;
|
||||||
-webkit-column-span : all;
|
-webkit-column-span : all;
|
||||||
-moz-column-span : all;
|
-moz-column-span : all;
|
||||||
ul+h5{
|
ul+h5{
|
||||||
margin-top : 15px;
|
margin-top : 15px;
|
||||||
}
|
}
|
||||||
p, ul{
|
p, ul{
|
||||||
font-size : 0.352cm;
|
font-size : 0.352cm;
|
||||||
line-height : 1.3em;
|
line-height : 1.3em;
|
||||||
}
|
}
|
||||||
ul{
|
ul{
|
||||||
margin-bottom : 0.5em;
|
margin-bottom : 0.5em;
|
||||||
padding-left : 1em;
|
padding-left : 1em;
|
||||||
text-indent : -1em;
|
text-indent : -1em;
|
||||||
list-style-type : none;
|
list-style-type : none;
|
||||||
-webkit-column-break-inside : auto;
|
-webkit-column-break-inside : auto;
|
||||||
column-break-inside : auto;
|
column-break-inside : auto;
|
||||||
}
|
|
||||||
}
|
|
||||||
//*****************************
|
|
||||||
// * PRINT
|
|
||||||
// *****************************/
|
|
||||||
.phb.print{
|
|
||||||
blockquote{
|
|
||||||
box-shadow : none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@media print {
|
|
||||||
.phb .descriptive, .phb blockquote{
|
|
||||||
box-shadow : none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//*****************************
|
|
||||||
// * WIDE
|
|
||||||
// *****************************/
|
|
||||||
.phb .wide{
|
|
||||||
column-span : all;
|
|
||||||
-webkit-column-span : all;
|
|
||||||
-moz-column-span : all;
|
|
||||||
}
|
|
||||||
//*****************************
|
|
||||||
// * CLASS TABLE
|
|
||||||
// *****************************/
|
|
||||||
.phb .classTable{
|
|
||||||
margin-top : 25px;
|
|
||||||
margin-bottom : 40px;
|
|
||||||
border-collapse : separate;
|
|
||||||
background-color : white;
|
|
||||||
border : initial;
|
|
||||||
border-style : solid;
|
|
||||||
border-image-outset : 25px 17px;
|
|
||||||
border-image-repeat : round;
|
|
||||||
border-image-slice : 150 200 150 200;
|
|
||||||
border-image-source : @frameBorderImage;
|
|
||||||
border-image-width : 47px;
|
|
||||||
h5{
|
|
||||||
margin-bottom : 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//*****************************
|
|
||||||
// * CLASS TABLE
|
|
||||||
// *****************************/
|
|
||||||
.phb .descriptive{
|
|
||||||
display : block-inline;
|
|
||||||
margin-bottom : 1em;
|
|
||||||
background-color : #faf7ea;
|
|
||||||
font-family : ScalySans;
|
|
||||||
border-style : solid;
|
|
||||||
border-width : 7px;
|
|
||||||
border-image : @descriptiveBoxImage 12 round;
|
|
||||||
border-image-outset : 4px;
|
|
||||||
box-shadow : 0px 0px 6px #faf7ea;
|
|
||||||
p{
|
|
||||||
display : block;
|
|
||||||
padding-bottom : 0px;
|
|
||||||
line-height : 1.5em;
|
|
||||||
}
|
|
||||||
p + p {
|
|
||||||
padding-top : .8em;
|
|
||||||
}
|
|
||||||
em {
|
|
||||||
font-family : ScalySans;
|
|
||||||
font-style : italic;
|
|
||||||
}
|
|
||||||
strong {
|
|
||||||
font-family : ScalySans;
|
|
||||||
font-weight : 800;
|
|
||||||
letter-spacing : -0.02em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.phb pre+.descriptive{
|
|
||||||
margin-top : 8px;
|
|
||||||
}
|
|
||||||
//*****************************
|
|
||||||
// * TABLE OF CONTENTS
|
|
||||||
// *****************************/
|
|
||||||
.phb .toc{
|
|
||||||
-webkit-column-break-inside : avoid;
|
|
||||||
column-break-inside : avoid;
|
|
||||||
a{
|
|
||||||
color : black;
|
|
||||||
text-decoration : none;
|
|
||||||
&:hover{
|
|
||||||
text-decoration : underline;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ul{
|
//*****************************
|
||||||
padding-left : 0;
|
// * PRINT
|
||||||
list-style-type : none;
|
// *****************************/
|
||||||
|
&.print{
|
||||||
|
blockquote{
|
||||||
|
box-shadow : none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
&>ul>li{
|
|
||||||
margin-bottom : 10px;
|
//*****************************
|
||||||
|
// * WIDE
|
||||||
|
// *****************************/
|
||||||
|
.wide{
|
||||||
|
column-span : all;
|
||||||
|
-webkit-column-span : all;
|
||||||
|
-moz-column-span : all;
|
||||||
|
}
|
||||||
|
//*****************************
|
||||||
|
// * CLASS TABLE
|
||||||
|
// *****************************/
|
||||||
|
.classTable{
|
||||||
|
margin-top : 25px;
|
||||||
|
margin-bottom : 40px;
|
||||||
|
border-collapse : separate;
|
||||||
|
background-color : white;
|
||||||
|
border : initial;
|
||||||
|
border-style : solid;
|
||||||
|
border-image-outset : 25px 17px;
|
||||||
|
border-image-repeat : round;
|
||||||
|
border-image-slice : 150 200 150 200;
|
||||||
|
border-image-source : @frameBorderImage;
|
||||||
|
border-image-width : 47px;
|
||||||
|
h5{
|
||||||
|
margin-bottom : 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//*****************************
|
||||||
|
// * CLASS TABLE
|
||||||
|
// *****************************/
|
||||||
|
.descriptive{
|
||||||
|
display : block-inline;
|
||||||
|
margin-bottom : 1em;
|
||||||
|
background-color : #faf7ea;
|
||||||
|
font-family : ScalySans;
|
||||||
|
border-style : solid;
|
||||||
|
border-width : 7px;
|
||||||
|
border-image : @descriptiveBoxImage 12 round;
|
||||||
|
border-image-outset : 4px;
|
||||||
|
box-shadow : 0px 0px 6px #faf7ea;
|
||||||
|
p{
|
||||||
|
display : block;
|
||||||
|
padding-bottom : 0px;
|
||||||
|
line-height : 1.5em;
|
||||||
|
}
|
||||||
|
p + p {
|
||||||
|
padding-top : .8em;
|
||||||
|
}
|
||||||
|
em {
|
||||||
|
font-family : ScalySans;
|
||||||
|
font-style : italic;
|
||||||
|
}
|
||||||
|
strong {
|
||||||
|
font-family : ScalySans;
|
||||||
|
font-weight : 800;
|
||||||
|
letter-spacing : -0.02em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pre+.descriptive{
|
||||||
|
margin-top : 8px;
|
||||||
|
}
|
||||||
|
//*****************************
|
||||||
|
// * TABLE OF CONTENTS
|
||||||
|
// *****************************/
|
||||||
|
.toc{
|
||||||
|
-webkit-column-break-inside : avoid;
|
||||||
|
column-break-inside : avoid;
|
||||||
|
a{
|
||||||
|
color : black;
|
||||||
|
text-decoration : none;
|
||||||
|
&:hover{
|
||||||
|
text-decoration : underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ul{
|
||||||
|
padding-left : 0;
|
||||||
|
list-style-type : none;
|
||||||
|
}
|
||||||
|
&>ul>li{
|
||||||
|
margin-bottom : 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//*****************************
|
||||||
|
// * Old Stuff
|
||||||
|
// *****************************/
|
||||||
|
|
||||||
|
//Double hr for full width elements
|
||||||
|
hr+hr+blockquote{
|
||||||
|
column-span : all;
|
||||||
|
-webkit-column-span : all;
|
||||||
|
-moz-column-span : all;
|
||||||
|
}
|
||||||
|
|
||||||
|
//*****************************
|
||||||
|
// * CLASS TABLE
|
||||||
|
// *****************************/
|
||||||
|
hr+table{
|
||||||
|
margin-top : -5px;
|
||||||
|
margin-bottom : 50px;
|
||||||
|
padding-top : 10px;
|
||||||
|
border-collapse : separate;
|
||||||
|
background-color : white;
|
||||||
|
border : initial;
|
||||||
|
border-style : solid;
|
||||||
|
border-image-outset : 37px 17px;
|
||||||
|
border-image-repeat : round;
|
||||||
|
border-image-slice : 150 200 150 200;
|
||||||
|
border-image-source : @frameBorderImage;
|
||||||
|
border-image-width : 47px;
|
||||||
|
}
|
||||||
|
h5+hr+table{
|
||||||
|
column-span : all;
|
||||||
|
-webkit-column-span : all;
|
||||||
|
-moz-column-span : all;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -48,7 +48,7 @@ const getTOC = (pages) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
module.exports = function(brew){
|
module.exports = function(brew){
|
||||||
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})**`)
|
||||||
18
shared/homebrewery/account.actions.js
Normal file
18
shared/homebrewery/account.actions.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
const Store = require('./account.store.js');
|
||||||
|
|
||||||
|
const Actions = {
|
||||||
|
init : (initState) => {
|
||||||
|
Store.init(initState);
|
||||||
|
},
|
||||||
|
login : ()=>{
|
||||||
|
window.location = Store.getLoginPath();
|
||||||
|
},
|
||||||
|
logout : ()=>{
|
||||||
|
document.cookie = 'nc_session=;expires=Thu, 01 Jan 1970 00:00:01 GMT;path=/;domain=.naturalcrit.com';
|
||||||
|
//Remove local dev cookies too
|
||||||
|
document.cookie = 'nc_session=;expires=Thu, 01 Jan 1970 00:00:01 GMT;path=/;';
|
||||||
|
window.location ='/';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = Actions;
|
||||||
27
shared/homebrewery/account.store.js
Normal file
27
shared/homebrewery/account.store.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
const _ = require('lodash');
|
||||||
|
const flux = require('pico-flux');
|
||||||
|
|
||||||
|
|
||||||
|
let State = {
|
||||||
|
loginPath : '',
|
||||||
|
user : undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Store = {}; //Maybe Flux it later?
|
||||||
|
|
||||||
|
|
||||||
|
Store.init = (state)=>{
|
||||||
|
State = _.merge({}, State, state);
|
||||||
|
};
|
||||||
|
Store.getLoginPath = ()=>{
|
||||||
|
let path = State.loginPath;
|
||||||
|
if(typeof window !== 'undefined'){
|
||||||
|
path = `${path}?redirect=${encodeURIComponent(window.location.href)}`;
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
};
|
||||||
|
Store.getUser = ()=>{
|
||||||
|
return State.user;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = Store;
|
||||||
82
shared/homebrewery/brew.actions.js
Normal file
82
shared/homebrewery/brew.actions.js
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
const _ = require('lodash');
|
||||||
|
const dispatch = require('pico-flux').dispatch;
|
||||||
|
|
||||||
|
const request = require('superagent');
|
||||||
|
const Store = require('./brew.store.js');
|
||||||
|
|
||||||
|
let pendingTimer;
|
||||||
|
const PENDING_TIMEOUT = 3000;
|
||||||
|
|
||||||
|
const APIActions = {
|
||||||
|
save : () => {
|
||||||
|
clearTimeout(pendingTimer);
|
||||||
|
const brew = Store.getBrew();
|
||||||
|
dispatch('SET_STATUS', 'saving');
|
||||||
|
request
|
||||||
|
.put('/api/brew/' + brew.editId)
|
||||||
|
.send(brew)
|
||||||
|
.end((err, res) => {
|
||||||
|
if(err) return dispatch('SET_STATUS', 'error', err);
|
||||||
|
dispatch('SET_BREW', res.body);
|
||||||
|
dispatch('SET_STATUS', 'ready');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
create : () => {
|
||||||
|
dispatch('SET_STATUS', 'saving');
|
||||||
|
request.post('/api/brew')
|
||||||
|
.send(Store.getBrew())
|
||||||
|
.end((err, res)=>{
|
||||||
|
if(err) return dispatch('SET_STATUS', 'error', err);
|
||||||
|
localStorage.setItem('homebrewery-new', null);
|
||||||
|
const brew = res.body;
|
||||||
|
window.location = '/edit/' + brew.editId;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
delete : (editId) => {
|
||||||
|
dispatch('SET_STATUS', 'deleting');
|
||||||
|
request.delete('/api/brew/' + editId)
|
||||||
|
.send()
|
||||||
|
.end((err, res)=>{
|
||||||
|
if(err) return dispatch('SET_STATUS', 'error', err);
|
||||||
|
window.location = '/';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Actions = {
|
||||||
|
init : (initState) => {
|
||||||
|
const filteredState = _.reduce(initState, (r, val, key) => {
|
||||||
|
if(typeof val !== 'undefined') r[key] = val;
|
||||||
|
return r;
|
||||||
|
}, {});
|
||||||
|
Store.init(filteredState);
|
||||||
|
},
|
||||||
|
setBrew : (brew) => {
|
||||||
|
dispatch('SET_BREW', brew);
|
||||||
|
},
|
||||||
|
updateBrewCode : (brewCode) => {
|
||||||
|
dispatch('UPDATE_BREW_CODE', brewCode)
|
||||||
|
},
|
||||||
|
updateBrewStyle : (style) => {
|
||||||
|
dispatch('UPDATE_BREW_STYLE', style)
|
||||||
|
},
|
||||||
|
updateMetadata : (meta) => {
|
||||||
|
dispatch('UPDATE_META', meta);
|
||||||
|
},
|
||||||
|
pendingSave : () => {
|
||||||
|
clearTimeout(pendingTimer);
|
||||||
|
pendingTimer = setTimeout(APIActions.save, PENDING_TIMEOUT);
|
||||||
|
dispatch('SET_STATUS', 'pending');
|
||||||
|
},
|
||||||
|
|
||||||
|
localPrint : ()=>{
|
||||||
|
const key = 'print';
|
||||||
|
localStorage.setItem(key, JSON.stringify(Store.getBrew()));
|
||||||
|
window.open(`/print?dialog=true&local=${key}`,'_blank');
|
||||||
|
},
|
||||||
|
print : ()=>{
|
||||||
|
window.open(`/print/${Store.getBrew().shareId}?dialog=true`, '_blank').focus();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = _.merge(Actions, APIActions);
|
||||||
79
shared/homebrewery/brew.store.js
Normal file
79
shared/homebrewery/brew.store.js
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
const _ = require('lodash');
|
||||||
|
const flux = require('pico-flux');
|
||||||
|
|
||||||
|
const Markdown = require('homebrewery/markdown.js');
|
||||||
|
|
||||||
|
let State = {
|
||||||
|
version : '0.0.0',
|
||||||
|
|
||||||
|
brew : {
|
||||||
|
text : '',
|
||||||
|
style : '',
|
||||||
|
shareId : undefined,
|
||||||
|
editId : undefined,
|
||||||
|
createdAt : undefined,
|
||||||
|
updatedAt : undefined,
|
||||||
|
|
||||||
|
title : '',
|
||||||
|
description : '',
|
||||||
|
tags : '',
|
||||||
|
published : false,
|
||||||
|
authors : [],
|
||||||
|
systems : []
|
||||||
|
},
|
||||||
|
|
||||||
|
errors : [],
|
||||||
|
status : 'ready', //ready, pending, saving, error
|
||||||
|
};
|
||||||
|
|
||||||
|
const Store = flux.createStore({
|
||||||
|
SET_BREW : (brew) => {
|
||||||
|
State.brew = brew;
|
||||||
|
},
|
||||||
|
UPDATE_BREW_CODE : (brewCode) => {
|
||||||
|
State.brew.text = brewCode;
|
||||||
|
|
||||||
|
//TODO: Remove?
|
||||||
|
State.errors = Markdown.validate(brewCode);
|
||||||
|
},
|
||||||
|
UPDATE_BREW_STYLE : (style) => {
|
||||||
|
//TODO: add in an error checker?
|
||||||
|
State.brew.style = style;
|
||||||
|
},
|
||||||
|
UPDATE_META : (meta) => {
|
||||||
|
State.brew = _.merge({}, State.brew, meta);
|
||||||
|
},
|
||||||
|
SET_STATUS : (status, error) => {
|
||||||
|
if(status == State.status) return false;
|
||||||
|
if(error) State.errors = error;
|
||||||
|
State.status = status;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Store.init = (state)=>{
|
||||||
|
State = _.merge({}, State, state);
|
||||||
|
};
|
||||||
|
Store.getBrew = ()=>{
|
||||||
|
return State.brew;
|
||||||
|
};
|
||||||
|
Store.getBrewCode = ()=>{
|
||||||
|
return State.brew.text;
|
||||||
|
};
|
||||||
|
Store.getBrewStyle = ()=>{
|
||||||
|
return State.brew.style;
|
||||||
|
};
|
||||||
|
Store.getMetaData = ()=>{
|
||||||
|
return _.omit(State.brew, ['text', 'style']);
|
||||||
|
};
|
||||||
|
Store.getErrors = ()=>{
|
||||||
|
return State.errors;
|
||||||
|
};
|
||||||
|
Store.getVersion = ()=>{
|
||||||
|
return State.version;
|
||||||
|
};
|
||||||
|
Store.getStatus = ()=>{
|
||||||
|
return State.status;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = Store;
|
||||||
178
shared/homebrewery/brewEditor/brewEditor.jsx
Normal file
178
shared/homebrewery/brewEditor/brewEditor.jsx
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
const React = require('react');
|
||||||
|
const _ = require('lodash');
|
||||||
|
const cx = require('classnames');
|
||||||
|
|
||||||
|
const CodeEditor = require('naturalcrit/codeEditor/codeEditor.jsx');
|
||||||
|
const MetadataEditor = require('./metadataEditor/metadataEditor.jsx');
|
||||||
|
const Menubar = require('./menubar/menubar.jsx');
|
||||||
|
|
||||||
|
const splice = function(str, index, inject){
|
||||||
|
return str.slice(0, index) + inject + str.slice(index);
|
||||||
|
};
|
||||||
|
|
||||||
|
const MENUBAR_HEIGHT = 25;
|
||||||
|
|
||||||
|
const BrewEditor = React.createClass({
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
brew : {
|
||||||
|
text : '',
|
||||||
|
style : '',
|
||||||
|
},
|
||||||
|
|
||||||
|
onCodeChange : ()=>{},
|
||||||
|
onStyleChange : ()=>{},
|
||||||
|
onMetaChange : ()=>{},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getInitialState: function() {
|
||||||
|
return {
|
||||||
|
view : 'code', //'code', 'style', 'meta'
|
||||||
|
};
|
||||||
|
},
|
||||||
|
isCode : function(){ return this.state.view == 'code' },
|
||||||
|
isStyle : function(){ return this.state.view == 'style' },
|
||||||
|
isMeta : function(){ return this.state.view == 'meta' },
|
||||||
|
|
||||||
|
|
||||||
|
componentDidMount: function() {
|
||||||
|
this.updateEditorSize();
|
||||||
|
this.highlightPageLines();
|
||||||
|
window.addEventListener("resize", this.updateEditorSize);
|
||||||
|
},
|
||||||
|
componentWillUnmount: function() {
|
||||||
|
window.removeEventListener("resize", this.updateEditorSize);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateEditorSize : function() {
|
||||||
|
if(this.refs.codeEditor){
|
||||||
|
let paneHeight = this.refs.main.parentNode.clientHeight;
|
||||||
|
paneHeight -= MENUBAR_HEIGHT + 1;
|
||||||
|
this.refs.codeEditor.codeMirror.setSize(null, paneHeight);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
handleInject : function(injectText){
|
||||||
|
const text = (this.isCode() ? this.props.brew.text : this.props.brew.style);
|
||||||
|
|
||||||
|
const lines = text.split('\n');
|
||||||
|
const cursorPos = this.refs.codeEditor.getCursorPosition();
|
||||||
|
lines[cursorPos.line] = splice(lines[cursorPos.line], cursorPos.ch, injectText);
|
||||||
|
|
||||||
|
this.refs.codeEditor.setCursorPosition(cursorPos.line, cursorPos.ch + injectText.length);
|
||||||
|
|
||||||
|
if(this.state.view == 'code') this.props.onCodeChange(lines.join('\n'));
|
||||||
|
if(this.state.view == 'style') this.props.onStyleChange(lines.join('\n'));
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
handleViewChange : function(newView){
|
||||||
|
this.setState({
|
||||||
|
view : newView
|
||||||
|
}, this.updateEditorSize);
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
brewJump : function(){
|
||||||
|
const currentPage = this.getCurrentPage();
|
||||||
|
window.location.hash = 'p' + currentPage;
|
||||||
|
},
|
||||||
|
|
||||||
|
//Called when there are changes to the editor's dimensions
|
||||||
|
/*
|
||||||
|
update : function(){
|
||||||
|
if(this.refs.codeEditor) this.refs.codeEditor.updateSize();
|
||||||
|
},
|
||||||
|
*/
|
||||||
|
|
||||||
|
//TODO: convert this into a generic function for columns and blocks
|
||||||
|
//MOve this to a util.sj file
|
||||||
|
highlightPageLines : function(){
|
||||||
|
if(!this.refs.codeEditor) return;
|
||||||
|
if(!this.isCode()) return;
|
||||||
|
|
||||||
|
|
||||||
|
const codeMirror = this.refs.codeEditor.codeMirror;
|
||||||
|
|
||||||
|
const lineNumbers = _.reduce(this.props.brew.text.split('\n'), (r, line, lineNumber)=>{
|
||||||
|
if(line.indexOf('\\page') !== -1){
|
||||||
|
codeMirror.addLineClass(lineNumber, 'background', 'pageLine');
|
||||||
|
r.push(lineNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(line.indexOf('\\column') === 0){
|
||||||
|
codeMirror.addLineClass(lineNumber, 'text', 'columnSplit');
|
||||||
|
r.push(lineNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(_.startsWith(line, '{{') || _.startsWith(line, '}}')){
|
||||||
|
codeMirror.addLineClass(lineNumber, 'text', 'block');
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}, []);
|
||||||
|
return lineNumbers
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
renderMetadataEditor : function(){
|
||||||
|
if(!this.state.showMetadataEditor) return;
|
||||||
|
return <MetadataEditor
|
||||||
|
metadata={this.props.metadata}
|
||||||
|
onChange={this.props.onMetadataChange}
|
||||||
|
/>
|
||||||
|
},
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
renderEditor : function(){
|
||||||
|
if(this.isMeta()){
|
||||||
|
return <MetadataEditor
|
||||||
|
metadata={this.props.brew}
|
||||||
|
onChange={this.props.onMetaChange} />
|
||||||
|
}
|
||||||
|
if(this.isStyle()){
|
||||||
|
return <CodeEditor key='style'
|
||||||
|
ref='codeEditor'
|
||||||
|
language='css'
|
||||||
|
value={this.props.brew.style}
|
||||||
|
onChange={this.props.onStyleChange} />
|
||||||
|
}
|
||||||
|
if(this.isCode()){
|
||||||
|
return <CodeEditor key='code'
|
||||||
|
ref='codeEditor'
|
||||||
|
language='gfm'
|
||||||
|
value={this.props.brew.text}
|
||||||
|
onChange={this.props.onCodeChange} />
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
render : function(){
|
||||||
|
this.highlightPageLines();
|
||||||
|
return <div className='brewEditor' ref='main'>
|
||||||
|
<Menubar
|
||||||
|
view={this.state.view}
|
||||||
|
onViewChange={this.handleViewChange}
|
||||||
|
onSnippetInject={this.handleInject}
|
||||||
|
|
||||||
|
/>
|
||||||
|
|
||||||
|
{this.renderEditor()}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
/*
|
||||||
|
<div className='brewJump' onClick={this.brewJump}>
|
||||||
|
<i className='fa fa-arrow-right' />
|
||||||
|
</div>
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = BrewEditor;
|
||||||
|
|
||||||
@@ -1,14 +1,21 @@
|
|||||||
|
|
||||||
.editor{
|
.brewEditor{
|
||||||
position : relative;
|
position : relative;
|
||||||
width : 100%;
|
width : 100%;
|
||||||
|
|
||||||
.codeEditor{
|
.codeEditor{
|
||||||
height : 100%;
|
height : 100%;
|
||||||
.pageLine{
|
.pageLine{
|
||||||
background-color : fade(#333, 15%);
|
background-color : fade(#333, 15%);
|
||||||
border-bottom : #333 solid 1px;
|
border-bottom : #333 solid 1px;
|
||||||
}
|
}
|
||||||
|
.block{
|
||||||
|
color : purple;
|
||||||
|
//font-style: italic;
|
||||||
|
}
|
||||||
|
.columnSplit{
|
||||||
|
font-style : italic;
|
||||||
|
color : grey;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.brewJump{
|
.brewJump{
|
||||||
@@ -25,5 +32,4 @@
|
|||||||
justify-content:center;
|
justify-content:center;
|
||||||
.tooltipLeft("Jump to brew page");
|
.tooltipLeft("Jump to brew page");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
14
shared/homebrewery/brewEditor/brewEditor.smart.jsx
Normal file
14
shared/homebrewery/brewEditor/brewEditor.smart.jsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
const Actions = require('homebrewery/brew.actions.js');
|
||||||
|
const Store = require('homebrewery/brew.store.js');
|
||||||
|
|
||||||
|
const BrewEditor = require('./brewEditor.jsx')
|
||||||
|
|
||||||
|
module.exports = Store.createSmartComponent(BrewEditor, ()=>{
|
||||||
|
return {
|
||||||
|
brew : Store.getBrew(),
|
||||||
|
|
||||||
|
onCodeChange : Actions.updateBrewCode,
|
||||||
|
onStyleChange : Actions.updateBrewStyle,
|
||||||
|
onMetaChange : Actions.updateMetadata,
|
||||||
|
};
|
||||||
|
});
|
||||||
76
shared/homebrewery/brewEditor/menubar/menubar.jsx
Normal file
76
shared/homebrewery/brewEditor/menubar/menubar.jsx
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
const React = require('react');
|
||||||
|
const _ = require('lodash');
|
||||||
|
const cx = require('classnames');
|
||||||
|
|
||||||
|
const SnippetMap = require('./snippet.map.js');
|
||||||
|
const SnippetGroup = require('./snippetGroup/snippetGroup.jsx');
|
||||||
|
|
||||||
|
const Menubar = React.createClass({
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
view : 'code',
|
||||||
|
onViewChange : ()=>{},
|
||||||
|
onSnippetInject : ()=>{},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
//TODO: remove
|
||||||
|
renderDevGroup : function(){
|
||||||
|
const Snippets = require('homebrewery/snippets/brew');
|
||||||
|
|
||||||
|
const snippets = _.map(Snippets, (gen, name)=>{
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
gen,
|
||||||
|
icon : 'fa-question'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return <SnippetGroup
|
||||||
|
name='All'
|
||||||
|
icon='fa-rocket'
|
||||||
|
snippets={snippets}
|
||||||
|
onClick={this.props.onSnippetInject}
|
||||||
|
key='dev'
|
||||||
|
/>
|
||||||
|
},
|
||||||
|
|
||||||
|
renderSnippets : function(){
|
||||||
|
if(this.props.view == 'meta') return ;
|
||||||
|
|
||||||
|
let mapping;
|
||||||
|
if(this.props.view == 'code') mapping = SnippetMap.brew;
|
||||||
|
if(this.props.view == 'style') mapping = SnippetMap.style;
|
||||||
|
|
||||||
|
let groups = _.map(mapping, (group)=>{
|
||||||
|
return <SnippetGroup {...group} onClick={this.props.onSnippetInject} key={group.name} />
|
||||||
|
});
|
||||||
|
|
||||||
|
groups = groups.concat(this.renderDevGroup());
|
||||||
|
|
||||||
|
return <div className='snippets'>{groups} </div>
|
||||||
|
},
|
||||||
|
render: function(){
|
||||||
|
return <div className='menubar'>
|
||||||
|
|
||||||
|
{this.renderSnippets()}
|
||||||
|
|
||||||
|
<div className='editors'>
|
||||||
|
<div className={cx('code', {selected : this.props.view == 'code'})}
|
||||||
|
onClick={this.props.onViewChange.bind(null, 'code')}>
|
||||||
|
<i className='fa fa-beer' />
|
||||||
|
</div>
|
||||||
|
<div className={cx('style', {selected : this.props.view == 'style'})}
|
||||||
|
onClick={this.props.onViewChange.bind(null, 'style')}>
|
||||||
|
<i className='fa fa-paint-brush' />
|
||||||
|
</div>
|
||||||
|
<div className={cx('meta', {selected : this.props.view == 'meta'})}
|
||||||
|
onClick={this.props.onViewChange.bind(null, 'meta')}>
|
||||||
|
<i className='fa fa-bars' />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = Menubar;
|
||||||
40
shared/homebrewery/brewEditor/menubar/menubar.less
Normal file
40
shared/homebrewery/brewEditor/menubar/menubar.less
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
|
||||||
|
.menubar{
|
||||||
|
@menuHeight : 25px;
|
||||||
|
position : relative;
|
||||||
|
height : @menuHeight;
|
||||||
|
background-color : #ddd;
|
||||||
|
.editors{
|
||||||
|
position : absolute;
|
||||||
|
display : flex;
|
||||||
|
top : 0px;
|
||||||
|
right : 0px;
|
||||||
|
height : @menuHeight;
|
||||||
|
width : 90px;
|
||||||
|
justify-content : space-between;
|
||||||
|
&>div{
|
||||||
|
height : @menuHeight;
|
||||||
|
width : @menuHeight;
|
||||||
|
cursor : pointer;
|
||||||
|
line-height : @menuHeight;
|
||||||
|
text-align : center;
|
||||||
|
&:hover,&.selected{
|
||||||
|
background-color : #999;
|
||||||
|
}
|
||||||
|
&.code{
|
||||||
|
.tooltipLeft('Brew Editor');
|
||||||
|
}
|
||||||
|
&.style{
|
||||||
|
.tooltipLeft('Style Editor');
|
||||||
|
}
|
||||||
|
&.meta{
|
||||||
|
.tooltipLeft('Metadata');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.snippets{
|
||||||
|
display : flex;
|
||||||
|
height : 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
48
shared/homebrewery/brewEditor/menubar/snippet.map.js
Normal file
48
shared/homebrewery/brewEditor/menubar/snippet.map.js
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
const Snippets = require('homebrewery/snippets');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
brew : [
|
||||||
|
{
|
||||||
|
name : 'PHB',
|
||||||
|
icon : 'fa-book',
|
||||||
|
snippets : [
|
||||||
|
{
|
||||||
|
name : 'Spell',
|
||||||
|
icon : 'fa-magic',
|
||||||
|
gen : Snippets.brew.spell
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'Table',
|
||||||
|
icon : 'fa-table',
|
||||||
|
gen : Snippets.brew.table
|
||||||
|
},
|
||||||
|
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'Mods',
|
||||||
|
icon : 'fa-gear',
|
||||||
|
snippets : []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
style : [
|
||||||
|
{
|
||||||
|
name : 'Print',
|
||||||
|
icon : 'fa-print',
|
||||||
|
snippets : [
|
||||||
|
{
|
||||||
|
name : 'Ink Friendly',
|
||||||
|
icon : 'fa-tint',
|
||||||
|
gen : Snippets.style.inkFriendly
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'A4 Page Size',
|
||||||
|
icon : 'fa-file',
|
||||||
|
gen : Snippets.style.a4
|
||||||
|
},
|
||||||
|
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
|
||||||
|
const React = require('react');
|
||||||
|
const _ = require('lodash');
|
||||||
|
const cx = require('classnames');
|
||||||
|
|
||||||
|
const SnippetGroup = React.createClass({
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
name : '',
|
||||||
|
icon : 'fa-rocket',
|
||||||
|
snippets : [],
|
||||||
|
onClick : function(){},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
handleSnippetClick : function(snippet){
|
||||||
|
this.props.onClick(snippet.gen());
|
||||||
|
},
|
||||||
|
renderSnippets : function(){
|
||||||
|
return _.map(this.props.snippets, (snippet)=>{
|
||||||
|
return <div className='snippet' key={snippet.name} onClick={this.handleSnippetClick.bind(this, snippet)}>
|
||||||
|
<i className={'fa fa-fw ' + snippet.icon} />
|
||||||
|
{snippet.name}
|
||||||
|
</div>
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
render : function(){
|
||||||
|
return <div className='snippetGroup'>
|
||||||
|
<div className='text'>
|
||||||
|
<i className={'fa fa-fw ' + this.props.icon} />
|
||||||
|
<span className='groupName'>{this.props.name}</span>
|
||||||
|
</div>
|
||||||
|
<div className='dropdown'>
|
||||||
|
{this.renderSnippets()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
},
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = SnippetGroup;
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user