mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2026-01-23 16:33:05 +00:00
Compare commits
115 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
20b719d0de | ||
|
|
8b04cc9269 | ||
|
|
96466211c7 | ||
|
|
34741291c7 | ||
|
|
fcb0cd8ee7 | ||
|
|
aafac16af2 | ||
|
|
911d1d4f9c | ||
|
|
2cd7b44e12 | ||
|
|
bfccd1d9e4 | ||
|
|
bf1bf6c191 | ||
|
|
cc5cb677a1 | ||
|
|
2dcd7101f3 | ||
|
|
f4e7e46a04 | ||
|
|
8b3b7cb5aa | ||
|
|
77081b39b4 | ||
|
|
a06236e3ff | ||
|
|
c8585775be | ||
|
|
439cdfa1ea | ||
|
|
4c36f254c4 | ||
|
|
b6815593fd | ||
|
|
84054d1aae | ||
|
|
6cd6b3c1c6 | ||
|
|
907dbe4416 | ||
|
|
47429d804a | ||
|
|
1d54ab49e6 | ||
|
|
f634c451ff | ||
|
|
778c0ca0b1 | ||
|
|
f4985b68ca | ||
|
|
c8875cff94 | ||
|
|
40f1d4c790 | ||
|
|
7818b6db46 | ||
|
|
4163e95938 | ||
|
|
fed0536673 | ||
|
|
a4b92af351 | ||
|
|
4584b83c60 | ||
|
|
c57ca0b27f | ||
|
|
c04cd23213 | ||
|
|
140f064786 | ||
|
|
24aec1c649 | ||
|
|
8caae18a12 | ||
|
|
3532d75365 | ||
|
|
ea81bb5ebc | ||
|
|
8d217f8785 | ||
|
|
d35db1f702 | ||
|
|
f6b058f3c9 | ||
|
|
5ab7c29b9d | ||
|
|
c5ecb9d57d | ||
|
|
d0c473878a | ||
|
|
796df9a1ac | ||
|
|
9e8e403195 | ||
|
|
a369871a06 | ||
|
|
f2f45f3657 | ||
|
|
e86ce5cf06 | ||
|
|
8d73ff6833 | ||
|
|
c4397d34f8 | ||
|
|
66c0c96a4f | ||
|
|
838b64c589 | ||
|
|
730dde730c | ||
|
|
d867aa7ce1 | ||
|
|
761597e71f | ||
|
|
4bde5fcbf8 | ||
|
|
ff0aa56ddc | ||
|
|
74f92f3e44 | ||
|
|
84285f7359 | ||
|
|
ec0de7a408 | ||
|
|
9ea99236ff | ||
|
|
f806328e75 | ||
|
|
15ac397b63 | ||
|
|
b8105eb147 | ||
|
|
31cbd9ef40 | ||
|
|
eaab6de691 | ||
|
|
d417c76c56 | ||
|
|
2f15cc5611 | ||
|
|
eb08172fb1 | ||
|
|
0cc87a4f0f | ||
|
|
004dc79eb2 | ||
|
|
a8a70c2d70 | ||
|
|
825c259fba | ||
|
|
cbbb7292d9 | ||
|
|
9519a0b4e4 | ||
|
|
9dd16b6dd5 | ||
|
|
d693301c37 | ||
|
|
fd5d142c16 | ||
|
|
ee63d2d857 | ||
|
|
7c3946fb03 | ||
|
|
3e69e8c1aa | ||
|
|
1a0bc1952c | ||
|
|
7e2c3381ae | ||
|
|
4f4ef908e0 | ||
|
|
3fe2360d92 | ||
|
|
13a2a7efd2 | ||
|
|
073fb73bde | ||
|
|
847615ef8e | ||
|
|
3583c2e776 | ||
|
|
2a753ccc7c | ||
|
|
55c529473a | ||
|
|
164f646e08 | ||
|
|
6f4962926c | ||
|
|
f18a181e2e | ||
|
|
c4bff6afa0 | ||
|
|
63e8a0d9b7 | ||
|
|
ce3fda683b | ||
|
|
9594b73b0d | ||
|
|
78ad4bea09 | ||
|
|
ed1b5252be | ||
|
|
bf27250990 | ||
|
|
bab11d692e | ||
|
|
3350b04f64 | ||
|
|
5ebba25183 | ||
|
|
8f77ac9e56 | ||
|
|
3f728e7993 | ||
|
|
3f22572f98 | ||
|
|
a52f628fdf | ||
|
|
fe63133d7c | ||
|
|
3247cab214 |
7
.bithoundrc
Normal file
7
.bithoundrc
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"unused-ignores": [
|
||||
"react-dom"
|
||||
]
|
||||
}
|
||||
}
|
||||
33
.circleci/config.yml
Normal file
33
.circleci/config.yml
Normal file
@@ -0,0 +1,33 @@
|
||||
# Javascript Node CircleCI 2.0 configuration file
|
||||
#
|
||||
# Check https://circleci.com/docs/2.0/language-javascript/ for more details
|
||||
#
|
||||
version: 2
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
- image: circleci/node:8.9
|
||||
- image: circleci/mongo:3.4-jessie
|
||||
|
||||
working_directory: ~/repo
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
# Download and cache dependencies
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-dependencies-{{ checksum "package.json" }}
|
||||
# fallback to using the latest cache if no exact match is found
|
||||
- v1-dependencies-
|
||||
|
||||
- run: npm install
|
||||
|
||||
- save_cache:
|
||||
paths:
|
||||
- node_modules
|
||||
key: v1-dependencies-{{ checksum "package.json" }}
|
||||
|
||||
# run tests!
|
||||
- run: npm run circleci
|
||||
|
||||
6
.dockerignore
Normal file
6
.dockerignore
Normal file
@@ -0,0 +1,6 @@
|
||||
node_modules
|
||||
npm-debug.log
|
||||
.git
|
||||
Dockerfile
|
||||
docker-compose.yml
|
||||
tests
|
||||
77
.eslintrc.js
Normal file
77
.eslintrc.js
Normal file
@@ -0,0 +1,77 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
parserOptions : {
|
||||
ecmaVersion : 9,
|
||||
sourceType : 'module',
|
||||
ecmaFeatures : {
|
||||
jsx : true
|
||||
}
|
||||
},
|
||||
env : {
|
||||
browser : true,
|
||||
},
|
||||
plugins : ['react'],
|
||||
rules : {
|
||||
/** Errors **/
|
||||
'camelcase' : ['error', { properties: 'never' }],
|
||||
'func-style' : ['error', 'expression', { allowArrowFunctions: true }],
|
||||
'no-array-constructor' : 'error',
|
||||
'no-iterator' : 'error',
|
||||
'no-nested-ternary' : 'error',
|
||||
'no-new-object' : 'error',
|
||||
'no-proto' : 'error',
|
||||
'react/jsx-no-bind' : ['error', { allowArrowFunctions: true }],
|
||||
'react/jsx-uses-react' : 'error',
|
||||
'react/prefer-es6-class' : ['error', 'never'],
|
||||
|
||||
/** Warnings **/
|
||||
'max-lines' : ['warn', {
|
||||
max : 200,
|
||||
skipComments : true,
|
||||
skipBlankLines : true,
|
||||
}],
|
||||
'max-depth' : ['warn', { max: 4 }],
|
||||
'max-params' : ['warn', { max: 4 }],
|
||||
'no-restricted-syntax' : ['warn', 'ClassDeclaration', 'SwitchStatement'],
|
||||
'no-unused-vars' : ['warn', {
|
||||
vars : 'all',
|
||||
args : 'none',
|
||||
varsIgnorePattern : 'config|_|cx|createClass'
|
||||
}],
|
||||
'react/jsx-uses-vars' : 'warn',
|
||||
|
||||
/** Fixable **/
|
||||
'arrow-parens' : ['warn', 'always'],
|
||||
'brace-style' : ['warn', '1tbs', { allowSingleLine: true }],
|
||||
'jsx-quotes' : ['warn', 'prefer-single'],
|
||||
'no-var' : 'warn',
|
||||
'prefer-const' : 'warn',
|
||||
'prefer-template' : 'warn',
|
||||
'quotes' : ['warn', 'single', { 'allowTemplateLiterals': true } ],
|
||||
'semi' : ['warn', 'always'],
|
||||
|
||||
/** Whitespace **/
|
||||
'array-bracket-spacing' : ['warn', 'never'],
|
||||
'arrow-spacing' : ['warn', { before: false, after: false }],
|
||||
'comma-spacing' : ['warn', { before: false, after: true }],
|
||||
'indent' : ['warn', 'tab'],
|
||||
'keyword-spacing' : ['warn', {
|
||||
before : true,
|
||||
after : true,
|
||||
overrides : {
|
||||
if : { 'before': false, 'after': false }
|
||||
}
|
||||
}],
|
||||
'key-spacing' : ['warn', {
|
||||
multiLine : { beforeColon: true, afterColon: true, align: 'colon' },
|
||||
singleLine : { beforeColon: false, afterColon: true }
|
||||
}],
|
||||
'linebreak-style' : ['warn', 'unix'],
|
||||
'no-trailing-spaces' : 'warn',
|
||||
'no-whitespace-before-property' : 'warn',
|
||||
'object-curly-spacing' : ['warn', 'always'],
|
||||
'react/jsx-indent-props' : ['warn', 'tab'],
|
||||
'space-in-parens' : ['warn', 'never'],
|
||||
'template-curly-spacing' : ['warn', 'never'],
|
||||
}
|
||||
};
|
||||
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
package-lock.json binary
|
||||
14
.github/issue_template.md
vendored
14
.github/issue_template.md
vendored
@@ -1,9 +1,21 @@
|
||||
<!-- CLICK "Preview" FOR INSTRUCTIONS IN A MORE READABLE FORMAT -->
|
||||
|
||||
## Before you submit
|
||||
|
||||
- Support questions are better asked on the subreddit [r/homebrewery](https://www.reddit.com/r/homebrewery/)
|
||||
- Read the [contributing guidelines](https://github.com/stolksdorf/homebrewery/blob/master/contributing.md).
|
||||
- If it's an issue, please make sure it's reproducible
|
||||
- Ensure the issue isn't already reported.
|
||||
|
||||
|
||||
*Delete the above section and the instructions in the sections below before submitting*
|
||||
|
||||
|
||||
## Description
|
||||
|
||||
### Additional Details
|
||||
If this is a *feature request*, explain why it should be added. Specific use-cases are best.
|
||||
|
||||
For *bug reports*, please provide as much *relevant* info as possible.
|
||||
|
||||
**Share Link** :
|
||||
|
||||
|
||||
14
.gitignore
vendored
14
.gitignore
vendored
@@ -1,16 +1,10 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
#Ignore our built files
|
||||
build/*
|
||||
|
||||
# Ignore sensitive stuff
|
||||
config/local.json
|
||||
|
||||
node_modules
|
||||
storage
|
||||
.idea
|
||||
*.swp
|
||||
|
||||
*.log
|
||||
build/*
|
||||
config/local.*
|
||||
|
||||
todo.md
|
||||
14
Dockerfile
Normal file
14
Dockerfile
Normal file
@@ -0,0 +1,14 @@
|
||||
FROM node:8
|
||||
|
||||
# Create app directory
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Bundle app source
|
||||
COPY . .
|
||||
|
||||
ENV NODE_ENV=docker
|
||||
|
||||
RUN yarn
|
||||
|
||||
EXPOSE 8000
|
||||
CMD [ "yarn", "start" ]
|
||||
14
README.md
14
README.md
@@ -1,13 +1,13 @@
|
||||
# The Homebrewery
|
||||
The Homebrewery is a tool for making authnetic looking [D&D content](http://dnd.wizards.com/products/tabletop-games/rpg-products/rpg_playershandbook) using only [Markdown](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet). Check it out [here](http://homebrewery.naturalcrit.com).
|
||||
The Homebrewery is a tool for making authentic looking [D&D content](http://dnd.wizards.com/products/tabletop-games/rpg-products/rpg_playershandbook) using only [Markdown](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet). Check it out [here](http://homebrewery.naturalcrit.com).
|
||||
|
||||
|
||||
### issues, suggestions, bugs
|
||||
If you run into any issues using The Homebrewery, please submit an issues [here](/issues)
|
||||
If you run into any issues using The Homebrewery, please submit an issue [here](/issues).
|
||||
|
||||
|
||||
### local dev
|
||||
Homebrewery is open source, so feel free to clone it, tinker with it, or run your own local version.
|
||||
The Homebrewery is open source, so feel free to clone it, tinker with it, or run your own local version.
|
||||
|
||||
#### pre-reqs
|
||||
1. install [node](https://nodejs.org/en/)
|
||||
@@ -20,14 +20,16 @@ Homebrewery is open source, so feel free to clone it, tinker with it, or run you
|
||||
1. `npm start`
|
||||
|
||||
#### standalone PHB stylesheet
|
||||
If you just want the stylesheet that is generated to make pages look like they are from the PLayer's Handbook, you have find it [here](https://github.com/stolksdorf/homebrewery/blob/master/phb.standalone.css)
|
||||
If you just want the stylesheet that is generated to make pages look like they are from the Player's Handbook, you will find it [here](https://github.com/stolksdorf/homebrewery/blob/master/phb.standalone.css).
|
||||
|
||||
If you are developing locally and would like to generate your own, follow the above steps and then run `npm run phb`.
|
||||
|
||||
### changelog
|
||||
|
||||
You can check out the changelog [here](https://github.com/stolksdorf/homebrewery/blob/master/changelog.md)
|
||||
You can check out the changelog [here](https://github.com/stolksdorf/homebrewery/blob/master/changelog.md).
|
||||
|
||||
### license
|
||||
|
||||
This project is licensed under [MIT](./license)
|
||||
This project is licensed under [MIT](./license). Which means you are free to use The Homebrewery in any way that you want, except for claiming that you made it yourself.
|
||||
|
||||
If you wish to sell or in some way gain profit for what's created on this site, it's your responsibility to ensure you have the proper licenses/rights for any images or resources used.
|
||||
|
||||
@@ -1,39 +1,39 @@
|
||||
var React = require('react');
|
||||
var _ = require('lodash');
|
||||
var cx = require('classnames');
|
||||
|
||||
var HomebrewAdmin = require('./homebrewAdmin/homebrewAdmin.jsx');
|
||||
|
||||
var Admin = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
url : "",
|
||||
admin_key : "",
|
||||
homebrews : [],
|
||||
};
|
||||
},
|
||||
|
||||
render : function(){
|
||||
var self = this;
|
||||
return(
|
||||
<div className='admin'>
|
||||
|
||||
<header>
|
||||
<div className='container'>
|
||||
<i className='fa fa-rocket' />
|
||||
naturalcrit admin
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div className='container'>
|
||||
|
||||
<HomebrewAdmin homebrews={this.props.homebrews} admin_key={this.props.admin_key} />
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Admin;
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
|
||||
const HomebrewAdmin = require('./homebrewAdmin/homebrewAdmin.jsx');
|
||||
|
||||
const Admin = createClass({
|
||||
getDefaultProps : function() {
|
||||
return {
|
||||
url : '',
|
||||
admin_key : '',
|
||||
homebrews : [],
|
||||
};
|
||||
},
|
||||
|
||||
render : function(){
|
||||
return (
|
||||
<div className='admin'>
|
||||
|
||||
<header>
|
||||
<div className='container'>
|
||||
<i className='fa fa-rocket' />
|
||||
naturalcrit admin
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div className='container'>
|
||||
|
||||
<HomebrewAdmin homebrews={this.props.homebrews} admin_key={this.props.admin_key} />
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Admin;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
|
||||
@@ -6,36 +7,36 @@ const request = require('superagent');
|
||||
const Moment = require('moment');
|
||||
|
||||
|
||||
const BrewLookup = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
const BrewLookup = createClass({
|
||||
getDefaultProps : function() {
|
||||
return {
|
||||
adminKey : '',
|
||||
};
|
||||
},
|
||||
getInitialState: function() {
|
||||
getInitialState : function() {
|
||||
return {
|
||||
query:'',
|
||||
query : '',
|
||||
resultBrew : null,
|
||||
searching : false
|
||||
searching : false
|
||||
};
|
||||
},
|
||||
|
||||
handleChange : function(e){
|
||||
this.setState({
|
||||
query : e.target.value
|
||||
})
|
||||
});
|
||||
},
|
||||
lookup : function(){
|
||||
this.setState({ searching : true });
|
||||
this.setState({ searching: true });
|
||||
|
||||
request.get(`/admin/lookup/${this.state.query}`)
|
||||
.query({ admin_key : this.props.adminKey })
|
||||
.end((err, res) => {
|
||||
.query({ admin_key: this.props.adminKey })
|
||||
.end((err, res)=>{
|
||||
this.setState({
|
||||
searching : false,
|
||||
searching : false,
|
||||
resultBrew : (err ? null : res.body)
|
||||
});
|
||||
})
|
||||
});
|
||||
},
|
||||
|
||||
renderFoundBrew : function(){
|
||||
@@ -46,21 +47,21 @@ const BrewLookup = React.createClass({
|
||||
return <div className='brewRow'>
|
||||
<div>{brew.title}</div>
|
||||
<div>{brew.authors.join(', ')}</div>
|
||||
<div><a href={'/edit/' + brew.editId} target='_blank'>/edit/{brew.editId}</a></div>
|
||||
<div><a href={'/share/' + brew.shareId} target='_blank'>/share/{brew.shareId}</a></div>
|
||||
<div><a href={`/edit/${brew.editId}`} target='_blank' rel='noopener noreferrer'>/edit/{brew.editId}</a></div>
|
||||
<div><a href={`/share/${brew.shareId}`} target='_blank' rel='noopener noreferrer'>/share/{brew.shareId}</a></div>
|
||||
<div>{Moment(brew.updatedAt).fromNow()}</div>
|
||||
<div>{brew.views}</div>
|
||||
</div>
|
||||
</div>;
|
||||
},
|
||||
|
||||
render: function(){
|
||||
render : function(){
|
||||
return <div className='brewLookup'>
|
||||
<h1>Brew Lookup</h1>
|
||||
<input type='text' value={this.state.query} onChange={this.handleChange} placeholder='edit or share id...' />
|
||||
<button onClick={this.lookup}><i className='fa fa-search'/></button>
|
||||
|
||||
{this.renderFoundBrew()}
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,72 +1,66 @@
|
||||
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>
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
|
||||
const request = require('superagent');
|
||||
|
||||
const BrewSearch = createClass({
|
||||
|
||||
getDefaultProps : function() {
|
||||
return {
|
||||
admin_key : ''
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState : function() {
|
||||
return {
|
||||
searchTerm : '',
|
||||
brew : null,
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
search : function(){
|
||||
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],
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
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 +1,170 @@
|
||||
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;
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
const request = require('superagent');
|
||||
|
||||
const Moment = require('moment');
|
||||
|
||||
|
||||
const BrewLookup = require('./brewLookup/brewLookup.jsx');
|
||||
|
||||
|
||||
const HomebrewAdmin = createClass({
|
||||
getDefaultProps : function() {
|
||||
return {
|
||||
admin_key : ''
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState : function() {
|
||||
return {
|
||||
page : 0,
|
||||
count : 20,
|
||||
brewCache : {},
|
||||
total : 0,
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
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;
|
||||
const newCache = _.extend({}, this.state.brewCache);
|
||||
newCache[page] = res.body.brews;
|
||||
this.setState({
|
||||
brewCache : newCache,
|
||||
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(){
|
||||
let 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(-1)}/>
|
||||
{outOf}
|
||||
<i className='fa fa-chevron-right' onClick={()=>this.handlePageChange(1)}/>
|
||||
</div>;
|
||||
},
|
||||
|
||||
|
||||
renderBrews : function(){
|
||||
const 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' rel='noopener noreferrer'>{brew.editId}</a></td>
|
||||
<td><a href={`/share/${brew.shareId}`} target='_blank' rel='noopener noreferrer'>{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(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(){
|
||||
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,4 +1,5 @@
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
|
||||
@@ -6,77 +7,67 @@ const Markdown = require('naturalcrit/markdown.js');
|
||||
const ErrorBar = require('./errorBar/errorBar.jsx');
|
||||
|
||||
//TODO: move to the brew renderer
|
||||
const RenderWarnings = require('homebrewery/renderWarnings/renderWarnings.jsx')
|
||||
const RenderWarnings = require('homebrewery/renderWarnings/renderWarnings.jsx');
|
||||
|
||||
const PAGE_HEIGHT = 1056;
|
||||
const PPR_THRESHOLD = 50;
|
||||
|
||||
const BrewRenderer = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
const BrewRenderer = createClass({
|
||||
getDefaultProps : function() {
|
||||
return {
|
||||
text : '',
|
||||
text : '',
|
||||
errors : []
|
||||
};
|
||||
},
|
||||
getInitialState: function() {
|
||||
getInitialState : function() {
|
||||
const pages = this.props.text.split('\\page');
|
||||
|
||||
return {
|
||||
viewablePageNumber: 0,
|
||||
height : 0,
|
||||
isMounted : false,
|
||||
viewablePageNumber : 0,
|
||||
height : 0,
|
||||
isMounted : false,
|
||||
|
||||
usePPR : true,
|
||||
|
||||
pages : pages,
|
||||
pages : pages,
|
||||
usePPR : pages.length >= PPR_THRESHOLD,
|
||||
|
||||
errors : []
|
||||
};
|
||||
},
|
||||
height : 0,
|
||||
pageHeight : PAGE_HEIGHT,
|
||||
height : 0,
|
||||
lastRender : <div></div>,
|
||||
|
||||
componentDidMount: function() {
|
||||
componentDidMount : function() {
|
||||
this.updateSize();
|
||||
window.addEventListener("resize", this.updateSize);
|
||||
window.addEventListener('resize', this.updateSize);
|
||||
},
|
||||
componentWillUnmount: function() {
|
||||
window.removeEventListener("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;
|
||||
|
||||
componentWillReceiveProps : function(nextProps) {
|
||||
const pages = nextProps.text.split('\\page');
|
||||
this.setState({
|
||||
pages : pages,
|
||||
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);
|
||||
|
||||
this.setState({
|
||||
height : this.refs.main.parentNode.clientHeight,
|
||||
height : this.refs.main.parentNode.clientHeight,
|
||||
isMounted : true
|
||||
});
|
||||
},
|
||||
|
||||
handleScroll : function(e){
|
||||
this.setState({
|
||||
viewablePageNumber : Math.floor(e.target.scrollTop / this.pageHeight)
|
||||
});
|
||||
const target = e.target;
|
||||
this.setState((prevState)=>({
|
||||
viewablePageNumber : Math.floor(target.scrollTop / target.scrollHeight * prevState.pages.length)
|
||||
}));
|
||||
},
|
||||
|
||||
shouldRender : function(pageText, index){
|
||||
if(!this.state.isMounted) return false;
|
||||
|
||||
var viewIndex = this.state.viewablePageNumber;
|
||||
const viewIndex = this.state.viewablePageNumber;
|
||||
if(index == viewIndex - 3) return true;
|
||||
if(index == viewIndex - 2) return true;
|
||||
if(index == viewIndex - 1) return true;
|
||||
@@ -94,7 +85,7 @@ const BrewRenderer = React.createClass({
|
||||
renderPageInfo : function(){
|
||||
return <div className='pageInfo'>
|
||||
{this.state.viewablePageNumber + 1} / {this.state.pages.length}
|
||||
</div>
|
||||
</div>;
|
||||
},
|
||||
|
||||
renderPPRmsg : function(){
|
||||
@@ -102,17 +93,17 @@ const BrewRenderer = React.createClass({
|
||||
|
||||
return <div className='ppr_msg'>
|
||||
Partial Page Renderer enabled, because your brew is so large. May effect rendering.
|
||||
</div>
|
||||
</div>;
|
||||
},
|
||||
|
||||
renderDummyPage : function(index){
|
||||
return <div className='phb' id={`p${index + 1}`} key={index}>
|
||||
<i className='fa fa-spinner fa-spin' />
|
||||
</div>
|
||||
</div>;
|
||||
},
|
||||
|
||||
renderPage : function(pageText, index){
|
||||
return <div className='phb' id={`p${index + 1}`} dangerouslySetInnerHTML={{__html:Markdown.render(pageText)}} key={index} />
|
||||
return <div className='phb' id={`p${index + 1}`} dangerouslySetInnerHTML={{ __html: Markdown.render(pageText) }} key={index} />;
|
||||
},
|
||||
|
||||
renderPages : function(){
|
||||
@@ -120,7 +111,7 @@ const BrewRenderer = React.createClass({
|
||||
return _.map(this.state.pages, (page, index)=>{
|
||||
if(this.shouldRender(page, index)){
|
||||
return this.renderPage(page, index);
|
||||
}else{
|
||||
} else {
|
||||
return this.renderDummyPage(index);
|
||||
}
|
||||
});
|
||||
@@ -133,20 +124,24 @@ const BrewRenderer = React.createClass({
|
||||
},
|
||||
|
||||
render : function(){
|
||||
return <div className='brewRenderer'
|
||||
onScroll={this.handleScroll}
|
||||
ref='main'
|
||||
style={{height : this.state.height}}>
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className='brewRenderer'
|
||||
onScroll={this.handleScroll}
|
||||
ref='main'
|
||||
style={{ height: this.state.height }}>
|
||||
|
||||
<ErrorBar errors={this.props.errors} />
|
||||
<RenderWarnings />
|
||||
<ErrorBar errors={this.props.errors} />
|
||||
<RenderWarnings />
|
||||
|
||||
<div className='pages' ref='pages'>
|
||||
{this.renderPages()}
|
||||
</div>
|
||||
{this.renderPageInfo()}
|
||||
{this.renderPPRmsg()}
|
||||
</div>
|
||||
<div className='pages' ref='pages'>
|
||||
{this.renderPages()}
|
||||
</div>
|
||||
</div>;
|
||||
{this.renderPageInfo()}
|
||||
{this.renderPPRmsg()}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -4,29 +4,8 @@
|
||||
position : relative;
|
||||
}
|
||||
.brewRenderer{
|
||||
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;
|
||||
}
|
||||
will-change : transform;
|
||||
overflow-y : scroll;
|
||||
.pages{
|
||||
margin : 30px 0px;
|
||||
&>.phb{
|
||||
@@ -36,4 +15,26 @@
|
||||
box-shadow : 1px 4px 14px #000;
|
||||
}
|
||||
}
|
||||
}
|
||||
.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;
|
||||
}
|
||||
@@ -1,15 +1,16 @@
|
||||
var React = require('react');
|
||||
var _ = require('lodash');
|
||||
var cx = require('classnames');
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
|
||||
var ErrorBar = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
const ErrorBar = createClass({
|
||||
getDefaultProps : function() {
|
||||
return {
|
||||
errors : []
|
||||
};
|
||||
},
|
||||
|
||||
hasOpenError : false,
|
||||
hasOpenError : false,
|
||||
hasCloseError : false,
|
||||
hasMatchError : false,
|
||||
|
||||
@@ -19,20 +20,20 @@ var ErrorBar = React.createClass({
|
||||
this.hasMatchError = false;
|
||||
|
||||
|
||||
var errors = _.map(this.props.errors, (err, idx) => {
|
||||
const errors = _.map(this.props.errors, (err, idx)=>{
|
||||
if(err.id == 'OPEN') this.hasOpenError = true;
|
||||
if(err.id == 'CLOSE') this.hasCloseError = true;
|
||||
if(err.id == 'MISMATCH') this.hasMatchError = true;
|
||||
return <li key={idx}>
|
||||
Line {err.line} : {err.text}, '{err.type}' tag
|
||||
</li>
|
||||
</li>;
|
||||
});
|
||||
|
||||
return <ul>{errors}</ul>
|
||||
return <ul>{errors}</ul>;
|
||||
},
|
||||
|
||||
renderProtip : function(){
|
||||
var msg = [];
|
||||
const msg = [];
|
||||
if(this.hasOpenError){
|
||||
msg.push(<div>
|
||||
An unmatched opening tag means there's an opened tag that isn't closed, you need to close a tag, like this {'</div>'}. Make sure to match types!
|
||||
@@ -53,7 +54,7 @@ var ErrorBar = React.createClass({
|
||||
return <div className='protips'>
|
||||
<h4>Protips!</h4>
|
||||
{msg}
|
||||
</div>
|
||||
</div>;
|
||||
},
|
||||
|
||||
render : function(){
|
||||
@@ -66,7 +67,7 @@ var ErrorBar = React.createClass({
|
||||
{this.renderErrors()}
|
||||
<hr />
|
||||
{this.renderProtip()}
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,145 +1,146 @@
|
||||
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;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
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 = 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,21 +1,22 @@
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
const request = require("superagent");
|
||||
const request = require('superagent');
|
||||
|
||||
const SYSTEMS = ['5e', '4e', '3.5e', 'Pathfinder']
|
||||
const SYSTEMS = ['5e', '4e', '3.5e', 'Pathfinder'];
|
||||
|
||||
const MetadataEditor = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
const MetadataEditor = createClass({
|
||||
getDefaultProps : function() {
|
||||
return {
|
||||
metadata: {
|
||||
editId : null,
|
||||
title : '',
|
||||
metadata : {
|
||||
editId : null,
|
||||
title : '',
|
||||
description : '',
|
||||
tags : '',
|
||||
published : false,
|
||||
authors : [],
|
||||
systems : []
|
||||
tags : '',
|
||||
published : false,
|
||||
authors : [],
|
||||
systems : []
|
||||
},
|
||||
onChange : ()=>{}
|
||||
};
|
||||
@@ -24,12 +25,12 @@ const MetadataEditor = React.createClass({
|
||||
handleFieldChange : function(name, e){
|
||||
this.props.onChange(_.merge({}, this.props.metadata, {
|
||||
[name] : e.target.value
|
||||
}))
|
||||
}));
|
||||
},
|
||||
handleSystem : function(system, e){
|
||||
if(e.target.checked){
|
||||
this.props.metadata.systems.push(system);
|
||||
}else{
|
||||
} else {
|
||||
this.props.metadata.systems = _.without(this.props.metadata.systems, system);
|
||||
}
|
||||
this.props.onChange(this.props.metadata);
|
||||
@@ -41,10 +42,10 @@ const MetadataEditor = React.createClass({
|
||||
},
|
||||
|
||||
handleDelete : 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;
|
||||
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.metadata.editId)
|
||||
request.get(`/api/remove/${this.props.metadata.editId}`)
|
||||
.send()
|
||||
.end(function(err, res){
|
||||
window.location.href = '/';
|
||||
@@ -67,21 +68,21 @@ const MetadataEditor = React.createClass({
|
||||
<input
|
||||
type='checkbox'
|
||||
checked={_.includes(this.props.metadata.systems, val)}
|
||||
onChange={this.handleSystem.bind(null, val)} />
|
||||
onChange={(e)=>this.handleSystem(val, e)} />
|
||||
{val}
|
||||
</label>
|
||||
</label>;
|
||||
});
|
||||
},
|
||||
|
||||
renderPublish : function(){
|
||||
if(this.props.metadata.published){
|
||||
return <button className='unpublish' onClick={this.handlePublish.bind(null, false)}>
|
||||
return <button className='unpublish' onClick={()=>this.handlePublish(false)}>
|
||||
<i className='fa fa-ban' /> unpublish
|
||||
</button>
|
||||
}else{
|
||||
return <button className='publish' onClick={this.handlePublish.bind(null, true)}>
|
||||
</button>;
|
||||
} else {
|
||||
return <button className='publish' onClick={()=>this.handlePublish(true)}>
|
||||
<i className='fa fa-globe' /> publish
|
||||
</button>
|
||||
</button>;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -95,7 +96,7 @@ const MetadataEditor = React.createClass({
|
||||
<i className='fa fa-trash' /> delete brew
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
},
|
||||
|
||||
renderAuthors : function(){
|
||||
@@ -108,7 +109,7 @@ const MetadataEditor = React.createClass({
|
||||
<div className='value'>
|
||||
{text}
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
},
|
||||
|
||||
renderShareToReddit : function(){
|
||||
@@ -117,13 +118,13 @@ const MetadataEditor = React.createClass({
|
||||
return <div className='field reddit'>
|
||||
<label>reddit</label>
|
||||
<div className='value'>
|
||||
<a href={this.getRedditLink()} target='_blank'>
|
||||
<a href={this.getRedditLink()} target='_blank' rel='noopener noreferrer'>
|
||||
<button className='publish'>
|
||||
<i className='fa fa-reddit-alien' /> share to reddit
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
},
|
||||
|
||||
render : function(){
|
||||
@@ -132,18 +133,18 @@ const MetadataEditor = React.createClass({
|
||||
<label>title</label>
|
||||
<input type='text' className='value'
|
||||
value={this.props.metadata.title}
|
||||
onChange={this.handleFieldChange.bind(null, 'title')} />
|
||||
onChange={(e)=>this.handleFieldChange('title', e)} />
|
||||
</div>
|
||||
<div className='field description'>
|
||||
<label>description</label>
|
||||
<textarea value={this.props.metadata.description} className='value'
|
||||
onChange={this.handleFieldChange.bind(null, 'description')} />
|
||||
onChange={(e)=>this.handleFieldChange('description', e)} />
|
||||
</div>
|
||||
{/*}
|
||||
<div className='field tags'>
|
||||
<label>tags</label>
|
||||
<textarea value={this.props.metadata.tags}
|
||||
onChange={this.handleFieldChange.bind(null, 'tags')} />
|
||||
onChange={(e)=>this.handleFieldChange('tags', e)} />
|
||||
</div>
|
||||
*/}
|
||||
|
||||
@@ -168,7 +169,7 @@ const MetadataEditor = React.createClass({
|
||||
|
||||
{this.renderDelete()}
|
||||
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
|
||||
@@ -8,14 +9,14 @@ 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() {
|
||||
const Snippetbar = createClass({
|
||||
getDefaultProps : function() {
|
||||
return {
|
||||
brew : '',
|
||||
brew : '',
|
||||
onInject : ()=>{},
|
||||
onToggle : ()=>{},
|
||||
showmeta : false
|
||||
@@ -23,7 +24,7 @@ const Snippetbar = React.createClass({
|
||||
},
|
||||
|
||||
handleSnippetClick : function(injectedText){
|
||||
this.props.onInject(injectedText)
|
||||
this.props.onInject(injectedText);
|
||||
},
|
||||
|
||||
renderSnippetGroups : function(){
|
||||
@@ -35,18 +36,18 @@ const Snippetbar = React.createClass({
|
||||
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})}
|
||||
<div className={cx('toggleMeta', { selected: this.props.showmeta })}
|
||||
onClick={this.props.onToggle}>
|
||||
<i className='fa fa-bars' />
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -57,13 +58,13 @@ module.exports = Snippetbar;
|
||||
|
||||
|
||||
|
||||
const SnippetGroup = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
const SnippetGroup = createClass({
|
||||
getDefaultProps : function() {
|
||||
return {
|
||||
brew : '',
|
||||
groupName : '',
|
||||
icon : 'fa-rocket',
|
||||
snippets : [],
|
||||
brew : '',
|
||||
groupName : '',
|
||||
icon : 'fa-rocket',
|
||||
snippets : [],
|
||||
onSnippetClick : function(){},
|
||||
};
|
||||
},
|
||||
@@ -72,23 +73,23 @@ const SnippetGroup = React.createClass({
|
||||
},
|
||||
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} />
|
||||
return <div className='snippet' key={snippet.name} onClick={()=>this.handleSnippetClick(snippet)}>
|
||||
<i className={`fa fa-fw ${snippet.icon}`} />
|
||||
{snippet.name}
|
||||
</div>
|
||||
})
|
||||
</div>;
|
||||
});
|
||||
},
|
||||
|
||||
render : function(){
|
||||
return <div className='snippetGroup'>
|
||||
<div className='text'>
|
||||
<i className={'fa fa-fw ' + this.props.icon} />
|
||||
<i className={`fa fa-fw ${this.props.icon}`} />
|
||||
<span className='groupName'>{this.props.groupName}</span>
|
||||
</div>
|
||||
<div className='dropdown'>
|
||||
{this.renderSnippets()}
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
},
|
||||
|
||||
});
|
||||
@@ -1,42 +1,42 @@
|
||||
var _ = require('lodash');
|
||||
|
||||
module.exports = function(classname){
|
||||
|
||||
classname = _.sample(['archivist', 'fancyman', 'linguist', 'fletcher',
|
||||
'notary', 'berserker-typist', 'fishmongerer', 'manicurist', 'haberdasher', 'concierge'])
|
||||
|
||||
classname = classname.toLowerCase();
|
||||
|
||||
var hitDie = _.sample([4, 6, 8, 10, 12]);
|
||||
|
||||
var abilityList = ["Strength", "Dexerity", "Constitution", "Wisdom", "Charisma", "Intelligence"];
|
||||
var skillList = ["Acrobatics ", "Animal Handling", "Arcana", "Athletics", "Deception", "History", "Insight", "Intimidation", "Investigation", "Medicine", "Nature", "Perception", "Performance", "Persuasion", "Religion", "Sleight of Hand", "Stealth", "Survival"];
|
||||
|
||||
|
||||
return [
|
||||
"## Class Features",
|
||||
"As a " + classname + ", you gain the following class features",
|
||||
"#### Hit Points",
|
||||
"___",
|
||||
"- **Hit Dice:** 1d" + hitDie + " per " + classname + " level",
|
||||
"- **Hit Points at 1st Level:** " + hitDie + " + your Constitution modifier",
|
||||
"- **Hit Points at Higher Levels:** 1d" + hitDie + " (or " + (hitDie/2 + 1) + ") + your Constitution modifier per " + classname + " level after 1st",
|
||||
"",
|
||||
"#### Proficiencies",
|
||||
"___",
|
||||
"- **Armor:** " + (_.sampleSize(["Light armor", "Medium armor", "Heavy armor", "Shields"], _.random(0,3)).join(', ') || "None"),
|
||||
"- **Weapons:** " + (_.sampleSize(["Squeegee", "Rubber Chicken", "Simple weapons", "Martial weapons"], _.random(0,2)).join(', ') || "None"),
|
||||
"- **Tools:** " + (_.sampleSize(["Artian's tools", "one musical instrument", "Thieve's tools"], _.random(0,2)).join(', ') || "None"),
|
||||
"",
|
||||
"___",
|
||||
"- **Saving Throws:** " + (_.sampleSize(abilityList, 2).join(', ')),
|
||||
"- **Skills:** Choose two from " + (_.sampleSize(skillList, _.random(4, 6)).join(', ')),
|
||||
"",
|
||||
"#### Equipment",
|
||||
"You start with the following equipment, in addition to the equipment granted by your background:",
|
||||
"- *(a)* a martial weapon and a shield or *(b)* two martial weapons",
|
||||
"- *(a)* five javelins or *(b)* any simple melee weapon",
|
||||
"- " + (_.sample(["10 lint fluffs", "1 button", "a cherished lost sock"])),
|
||||
"\n\n\n"
|
||||
].join('\n');
|
||||
}
|
||||
const _ = require('lodash');
|
||||
|
||||
module.exports = function(classname){
|
||||
|
||||
classname = _.sample(['archivist', 'fancyman', 'linguist', 'fletcher',
|
||||
'notary', 'berserker-typist', 'fishmongerer', 'manicurist', 'haberdasher', 'concierge']);
|
||||
|
||||
classname = classname.toLowerCase();
|
||||
|
||||
const hitDie = _.sample([4, 6, 8, 10, 12]);
|
||||
|
||||
const abilityList = ['Strength', 'Dexerity', 'Constitution', 'Wisdom', 'Charisma', 'Intelligence'];
|
||||
const skillList = ['Acrobatics ', 'Animal Handling', 'Arcana', 'Athletics', 'Deception', 'History', 'Insight', 'Intimidation', 'Investigation', 'Medicine', 'Nature', 'Perception', 'Performance', 'Persuasion', 'Religion', 'Sleight of Hand', 'Stealth', 'Survival'];
|
||||
|
||||
|
||||
return [
|
||||
'## Class Features',
|
||||
`As a ${classname}, you gain the following class features`,
|
||||
'#### Hit Points',
|
||||
'___',
|
||||
`- **Hit Dice:** 1d${hitDie} per ${classname} level`,
|
||||
`- **Hit Points at 1st Level:** ${hitDie} + your Constitution modifier`,
|
||||
`- **Hit Points at Higher Levels:** 1d${hitDie} (or ${hitDie/2 + 1}) + your Constitution modifier per ${classname} level after 1st`,
|
||||
'',
|
||||
'#### Proficiencies',
|
||||
'___',
|
||||
`- **Armor:** ${_.sampleSize(['Light armor', 'Medium armor', 'Heavy armor', 'Shields'], _.random(0, 3)).join(', ') || 'None'}`,
|
||||
`- **Weapons:** ${_.sampleSize(['Squeegee', 'Rubber Chicken', 'Simple weapons', 'Martial weapons'], _.random(0, 2)).join(', ') || 'None'}`,
|
||||
`- **Tools:** ${_.sampleSize(['Artian\'s tools', 'one musical instrument', 'Thieve\'s tools'], _.random(0, 2)).join(', ') || 'None'}`,
|
||||
'',
|
||||
'___',
|
||||
`- **Saving Throws:** ${_.sampleSize(abilityList, 2).join(', ')}`,
|
||||
`- **Skills:** Choose two from ${_.sampleSize(skillList, _.random(4, 6)).join(', ')}`,
|
||||
'',
|
||||
'#### Equipment',
|
||||
'You start with the following equipment, in addition to the equipment granted by your background:',
|
||||
'- *(a)* a martial weapon and a shield or *(b)* two martial weapons',
|
||||
'- *(a)* five javelins or *(b)* any simple melee weapon',
|
||||
`- ${_.sample(['10 lint fluffs', '1 button', 'a cherished lost sock'])}`,
|
||||
'\n\n\n'
|
||||
].join('\n');
|
||||
};
|
||||
|
||||
@@ -1,114 +1,114 @@
|
||||
var _ = require('lodash');
|
||||
|
||||
var features = [
|
||||
"Astrological Botany",
|
||||
"Astrological Chemistry",
|
||||
"Biochemical Sorcery",
|
||||
"Civil Alchemy",
|
||||
"Consecrated Biochemistry",
|
||||
"Demonic Anthropology",
|
||||
"Divinatory Mineralogy",
|
||||
"Genetic Banishing",
|
||||
"Hermetic Geography",
|
||||
"Immunological Incantations",
|
||||
"Nuclear Illusionism",
|
||||
"Ritual Astronomy",
|
||||
"Seismological Divination",
|
||||
"Spiritual Biochemistry",
|
||||
"Statistical Occultism",
|
||||
"Police Necromancer",
|
||||
"Sixgun Poisoner",
|
||||
"Pharmaceutical Gunslinger",
|
||||
"Infernal Banker",
|
||||
"Spell Analyst",
|
||||
"Gunslinger Corruptor",
|
||||
"Torque Interfacer",
|
||||
"Exo Interfacer",
|
||||
"Gunpowder Torturer",
|
||||
"Orbital Gravedigger",
|
||||
"Phased Linguist",
|
||||
"Mathematical Pharmacist",
|
||||
"Plasma Outlaw",
|
||||
"Malefic Chemist",
|
||||
"Police Cultist"
|
||||
];
|
||||
|
||||
var classnames = ['Archivist', 'Fancyman', 'Linguist', 'Fletcher',
|
||||
'Notary', 'Berserker-Typist', 'Fishmongerer', 'Manicurist', 'Haberdasher', 'Concierge'];
|
||||
|
||||
var levels = ["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th", "11th", "12th", "13th", "14th", "15th", "16th", "17th", "18th", "19th", "20th"]
|
||||
|
||||
var profBonus = [2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,6,6,6,6];
|
||||
|
||||
var getFeature = (level)=>{
|
||||
var res = []
|
||||
if(_.includes([4,6,8,12,14,16,19], level+1)){
|
||||
res = ["Ability Score Improvement"]
|
||||
}
|
||||
res = _.union(res, _.sampleSize(features, _.sample([0,1,1,1,1,1])));
|
||||
if(!res.length) return "─";
|
||||
return res.join(', ');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
full : function(){
|
||||
var classname = _.sample(classnames)
|
||||
|
||||
var maxes = [4,3,3,3,3,2,2,1,1]
|
||||
var drawSlots = function(Slots){
|
||||
var slots = Number(Slots);
|
||||
return _.times(9, function(i){
|
||||
var max = maxes[i];
|
||||
if(slots < 1) return "—";
|
||||
var res = _.min([max, slots]);
|
||||
slots -= res;
|
||||
return res;
|
||||
}).join(' | ')
|
||||
}
|
||||
|
||||
|
||||
var cantrips = 3;
|
||||
var spells = 1;
|
||||
var slots = 2;
|
||||
return "<div class='classTable wide'>\n##### The " + classname + "\n" +
|
||||
"| Level | Proficiency Bonus | Features | Cantrips Known | Spells Known | 1st | 2nd | 3rd | 4th | 5th | 6th | 7th | 8th | 9th |\n"+
|
||||
"|:---:|:---:|:---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|\n" +
|
||||
_.map(levels, function(levelName, level){
|
||||
var res = [
|
||||
levelName,
|
||||
"+" + profBonus[level],
|
||||
getFeature(level),
|
||||
cantrips,
|
||||
spells,
|
||||
drawSlots(slots)
|
||||
].join(' | ');
|
||||
|
||||
cantrips += _.random(0,1);
|
||||
spells += _.random(0,1);
|
||||
slots += _.random(0,2);
|
||||
|
||||
return "| " + res + " |";
|
||||
}).join('\n') +'\n</div>\n\n';
|
||||
},
|
||||
|
||||
half : function(){
|
||||
var classname = _.sample(classnames)
|
||||
|
||||
var featureScore = 1
|
||||
return "<div class='classTable'>\n##### The " + classname + "\n" +
|
||||
"| Level | Proficiency Bonus | Features | " + _.sample(features) + "|\n" +
|
||||
"|:---:|:---:|:---|:---:|\n" +
|
||||
_.map(levels, function(levelName, level){
|
||||
var res = [
|
||||
levelName,
|
||||
"+" + profBonus[level],
|
||||
getFeature(level),
|
||||
"+" + featureScore
|
||||
].join(' | ');
|
||||
|
||||
featureScore += _.random(0,1);
|
||||
|
||||
return "| " + res + " |";
|
||||
}).join('\n') +'\n</div>\n\n';
|
||||
}
|
||||
const _ = require('lodash');
|
||||
|
||||
const features = [
|
||||
'Astrological Botany',
|
||||
'Astrological Chemistry',
|
||||
'Biochemical Sorcery',
|
||||
'Civil Alchemy',
|
||||
'Consecrated Biochemistry',
|
||||
'Demonic Anthropology',
|
||||
'Divinatory Mineralogy',
|
||||
'Genetic Banishing',
|
||||
'Hermetic Geography',
|
||||
'Immunological Incantations',
|
||||
'Nuclear Illusionism',
|
||||
'Ritual Astronomy',
|
||||
'Seismological Divination',
|
||||
'Spiritual Biochemistry',
|
||||
'Statistical Occultism',
|
||||
'Police Necromancer',
|
||||
'Sixgun Poisoner',
|
||||
'Pharmaceutical Gunslinger',
|
||||
'Infernal Banker',
|
||||
'Spell Analyst',
|
||||
'Gunslinger Corruptor',
|
||||
'Torque Interfacer',
|
||||
'Exo Interfacer',
|
||||
'Gunpowder Torturer',
|
||||
'Orbital Gravedigger',
|
||||
'Phased Linguist',
|
||||
'Mathematical Pharmacist',
|
||||
'Plasma Outlaw',
|
||||
'Malefic Chemist',
|
||||
'Police Cultist'
|
||||
];
|
||||
|
||||
const classnames = ['Archivist', 'Fancyman', 'Linguist', 'Fletcher',
|
||||
'Notary', 'Berserker-Typist', 'Fishmongerer', 'Manicurist', 'Haberdasher', 'Concierge'];
|
||||
|
||||
const levels = ['1st', '2nd', '3rd', '4th', '5th', '6th', '7th', '8th', '9th', '10th', '11th', '12th', '13th', '14th', '15th', '16th', '17th', '18th', '19th', '20th'];
|
||||
|
||||
const profBonus = [2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6];
|
||||
|
||||
const getFeature = (level)=>{
|
||||
let res = [];
|
||||
if(_.includes([4, 6, 8, 12, 14, 16, 19], level+1)){
|
||||
res = ['Ability Score Improvement'];
|
||||
}
|
||||
res = _.union(res, _.sampleSize(features, _.sample([0, 1, 1, 1, 1, 1])));
|
||||
if(!res.length) return '─';
|
||||
return res.join(', ');
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
full : function(){
|
||||
const classname = _.sample(classnames);
|
||||
|
||||
const maxes = [4, 3, 3, 3, 3, 2, 2, 1, 1];
|
||||
const drawSlots = function(Slots){
|
||||
let slots = Number(Slots);
|
||||
return _.times(9, function(i){
|
||||
const max = maxes[i];
|
||||
if(slots < 1) return '—';
|
||||
const res = _.min([max, slots]);
|
||||
slots -= res;
|
||||
return res;
|
||||
}).join(' | ');
|
||||
};
|
||||
|
||||
|
||||
let cantrips = 3;
|
||||
let spells = 1;
|
||||
let slots = 2;
|
||||
return `<div class='classTable wide'>\n##### The ${classname}\n` +
|
||||
`| Level | Proficiency Bonus | Features | Cantrips Known | Spells Known | 1st | 2nd | 3rd | 4th | 5th | 6th | 7th | 8th | 9th |\n`+
|
||||
`|:---:|:---:|:---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|\n${
|
||||
_.map(levels, function(levelName, level){
|
||||
const res = [
|
||||
levelName,
|
||||
`+${profBonus[level]}`,
|
||||
getFeature(level),
|
||||
cantrips,
|
||||
spells,
|
||||
drawSlots(slots)
|
||||
].join(' | ');
|
||||
|
||||
cantrips += _.random(0, 1);
|
||||
spells += _.random(0, 1);
|
||||
slots += _.random(0, 2);
|
||||
|
||||
return `| ${res} |`;
|
||||
}).join('\n')}\n</div>\n\n`;
|
||||
},
|
||||
|
||||
half : function(){
|
||||
const classname = _.sample(classnames);
|
||||
|
||||
let featureScore = 1;
|
||||
return `<div class='classTable'>\n##### The ${classname}\n` +
|
||||
`| Level | Proficiency Bonus | Features | ${_.sample(features)}|\n` +
|
||||
`|:---:|:---:|:---|:---:|\n${
|
||||
_.map(levels, function(levelName, level){
|
||||
const res = [
|
||||
levelName,
|
||||
`+${profBonus[level]}`,
|
||||
getFeature(level),
|
||||
`+${featureScore}`
|
||||
].join(' | ');
|
||||
|
||||
featureScore += _.random(0, 1);
|
||||
|
||||
return `| ${res} |`;
|
||||
}).join('\n')}\n</div>\n\n`;
|
||||
}
|
||||
};
|
||||
@@ -1,104 +1,104 @@
|
||||
var _ = require('lodash');
|
||||
const _ = require('lodash');
|
||||
|
||||
var titles = [
|
||||
"The Burning Gallows",
|
||||
"The Ring of Nenlast",
|
||||
"Below the Blind Tavern",
|
||||
"Below the Hungering River",
|
||||
"Before Bahamut's Land",
|
||||
"The Cruel Grave from Within",
|
||||
"The Strength of Trade Road",
|
||||
"Through The Raven Queen's Worlds",
|
||||
"Within the Settlement",
|
||||
"The Crown from Within",
|
||||
"The Merchant Within the Battlefield",
|
||||
"Ioun's Fading Traveler",
|
||||
"The Legion Ingredient",
|
||||
"The Explorer Lure",
|
||||
"Before the Charming Badlands",
|
||||
"The Living Dead Above the Fearful Cage",
|
||||
"Vecna's Hidden Sage",
|
||||
"Bahamut's Demonspawn",
|
||||
"Across Gruumsh's Elemental Chaos",
|
||||
"The Blade of Orcus",
|
||||
"Beyond Revenge",
|
||||
"Brain of Insanity",
|
||||
"Breed Battle!, A New Beginning",
|
||||
"Evil Lake, A New Beginning",
|
||||
"Invasion of the Gigantic Cat, Part II",
|
||||
"Kraken War 2020",
|
||||
"The Body Whisperers",
|
||||
"The Diabolical Tales of the Ape-Women",
|
||||
"The Doctor Immortal",
|
||||
"The Doctor from Heaven",
|
||||
"The Graveyard",
|
||||
"Azure Core",
|
||||
"Core Battle",
|
||||
"Core of Heaven: The Guardian of Amazement",
|
||||
"Deadly Amazement III",
|
||||
"Dry Chaos IX",
|
||||
"Gate Thunder",
|
||||
"Guardian: Skies of the Dark Wizard",
|
||||
"Lute of Eternity",
|
||||
"Mercury's Planet: Brave Evolution",
|
||||
"Ruby of Atlantis: The Quake of Peace",
|
||||
"Sky of Zelda: The Thunder of Force",
|
||||
"Vyse's Skies",
|
||||
"White Greatness III",
|
||||
"Yellow Divinity",
|
||||
"Zidane's Ghost"
|
||||
const titles = [
|
||||
'The Burning Gallows',
|
||||
'The Ring of Nenlast',
|
||||
'Below the Blind Tavern',
|
||||
'Below the Hungering River',
|
||||
'Before Bahamut\'s Land',
|
||||
'The Cruel Grave from Within',
|
||||
'The Strength of Trade Road',
|
||||
'Through The Raven Queen\'s Worlds',
|
||||
'Within the Settlement',
|
||||
'The Crown from Within',
|
||||
'The Merchant Within the Battlefield',
|
||||
'Ioun\'s Fading Traveler',
|
||||
'The Legion Ingredient',
|
||||
'The Explorer Lure',
|
||||
'Before the Charming Badlands',
|
||||
'The Living Dead Above the Fearful Cage',
|
||||
'Vecna\'s Hidden Sage',
|
||||
'Bahamut\'s Demonspawn',
|
||||
'Across Gruumsh\'s Elemental Chaos',
|
||||
'The Blade of Orcus',
|
||||
'Beyond Revenge',
|
||||
'Brain of Insanity',
|
||||
'Breed Battle!, A New Beginning',
|
||||
'Evil Lake, A New Beginning',
|
||||
'Invasion of the Gigantic Cat, Part II',
|
||||
'Kraken War 2020',
|
||||
'The Body Whisperers',
|
||||
'The Diabolical Tales of the Ape-Women',
|
||||
'The Doctor Immortal',
|
||||
'The Doctor from Heaven',
|
||||
'The Graveyard',
|
||||
'Azure Core',
|
||||
'Core Battle',
|
||||
'Core of Heaven: The Guardian of Amazement',
|
||||
'Deadly Amazement III',
|
||||
'Dry Chaos IX',
|
||||
'Gate Thunder',
|
||||
'Guardian: Skies of the Dark Wizard',
|
||||
'Lute of Eternity',
|
||||
'Mercury\'s Planet: Brave Evolution',
|
||||
'Ruby of Atlantis: The Quake of Peace',
|
||||
'Sky of Zelda: The Thunder of Force',
|
||||
'Vyse\'s Skies',
|
||||
'White Greatness III',
|
||||
'Yellow Divinity',
|
||||
'Zidane\'s Ghost'
|
||||
];
|
||||
|
||||
var subtitles = [
|
||||
"In an ominous universe, a botanist opposes terrorism.",
|
||||
"In a demon-haunted city, in an age of lies and hate, a physicist tries to find an ancient treasure and battles a mob of aliens.",
|
||||
"In a land of corruption, two cyberneticists and a dungeon delver search for freedom.",
|
||||
"In an evil empire of horror, two rangers battle the forces of hell.",
|
||||
"In a lost city, in an age of sorcery, a librarian quests for revenge.",
|
||||
"In a universe of illusions and danger, three time travellers and an adventurer search for justice.",
|
||||
"In a forgotten universe of barbarism, in an era of terror and mysticism, a virtual reality programmer and a spy try to find vengance and battle crime.",
|
||||
"In a universe of demons, in an era of insanity and ghosts, three bodyguards and a bodyguard try to find vengance.",
|
||||
"In a kingdom of corruption and battle, seven artificial intelligences try to save the last living fertile woman.",
|
||||
"In a universe of virutal reality and agony, in an age of ghosts and ghosts, a fortune-teller and a wanderer try to avert the apocalypse.",
|
||||
"In a crime-infested kingdom, three martial artists quest for the truth and oppose evil.",
|
||||
"In a terrifying universe of lost souls, in an era of lost souls, eight dancers fight evil.",
|
||||
"In a galaxy of confusion and insanity, three martial artists and a duke battle a mob of psychics.",
|
||||
"In an amazing kingdom, a wizard and a secretary hope to prevent the destruction of mankind.",
|
||||
"In a kingdom of deception, a reporter searches for fame.",
|
||||
"In a hellish empire, a swordswoman and a duke try to find the ultimate weapon and battle a conspiracy.",
|
||||
"In an evil galaxy of illusion, in a time of technology and misery, seven psychiatrists battle crime.",
|
||||
"In a dark city of confusion, three swordswomen and a singer battle lawlessness.",
|
||||
"In an ominous empire, in an age of hate, two philosophers and a student try to find justice and battle a mob of mages intent on stealing the souls of the innocent.",
|
||||
"In a kingdom of panic, six adventurers oppose lawlessness.",
|
||||
"In a land of dreams and hopelessness, three hackers and a cyborg search for justice.",
|
||||
"On a planet of mysticism, three travelers and a fire fighter quest for the ultimate weapon and oppose evil.",
|
||||
"In a wicked universe, five seers fight lawlessness.",
|
||||
"In a kingdom of death, in an era of illusion and blood, four colonists search for fame.",
|
||||
"In an amazing kingdom, in an age of sorcery and lost souls, eight space pirates quest for freedom.",
|
||||
"In a cursed empire, five inventors oppose terrorism.",
|
||||
"On a crime-ridden planet of conspiracy, a watchman and an artificial intelligence try to find love and oppose lawlessness.",
|
||||
"In a forgotten land, a reporter and a spy try to stop the apocalypse.",
|
||||
"In a forbidden land of prophecy, a scientist and an archivist oppose a cabal of barbarians intent on stealing the souls of the innocent.",
|
||||
"On an infernal world of illusion, a grave robber and a watchman try to find revenge and combat a syndicate of mages intent on stealing the source of all magic.",
|
||||
"In a galaxy of dark magic, four fighters seek freedom.",
|
||||
"In an empire of deception, six tomb-robbers quest for the ultimate weapon and combat an army of raiders.",
|
||||
"In a kingdom of corruption and lost souls, in an age of panic, eight planetologists oppose evil.",
|
||||
"In a galaxy of misery and hopelessness, in a time of agony and pain, five planetologists search for vengance.",
|
||||
"In a universe of technology and insanity, in a time of sorcery, a computer techician quests for hope.",
|
||||
"On a planet of dark magic and barbarism, in an age of horror and blasphemy, seven librarians search for fame.",
|
||||
"In an empire of dark magic, in a time of blood and illusions, four monks try to find the ultimate weapon and combat terrorism.",
|
||||
"In a forgotten empire of dark magic, six kings try to prevent the destruction of mankind.",
|
||||
"In a galaxy of dark magic and horror, in an age of hopelessness, four marines and an outlaw combat evil.",
|
||||
"In a mysterious city of illusion, in an age of computerization, a witch-hunter tries to find the ultimate weapon and opposes an evil corporation.",
|
||||
"In a damned kingdom of technology, a virtual reality programmer and a fighter seek fame.",
|
||||
"In a hellish kingdom, in an age of blasphemy and blasphemy, an astrologer searches for fame.",
|
||||
"In a damned world of devils, an alien and a ranger quest for love and oppose a syndicate of demons.",
|
||||
"In a cursed galaxy, in a time of pain, seven librarians hope to avert the apocalypse.",
|
||||
"In a crime-infested galaxy, in an era of hopelessness and panic, three champions and a grave robber try to solve the ultimate crime."
|
||||
const subtitles = [
|
||||
'In an ominous universe, a botanist opposes terrorism.',
|
||||
'In a demon-haunted city, in an age of lies and hate, a physicist tries to find an ancient treasure and battles a mob of aliens.',
|
||||
'In a land of corruption, two cyberneticists and a dungeon delver search for freedom.',
|
||||
'In an evil empire of horror, two rangers battle the forces of hell.',
|
||||
'In a lost city, in an age of sorcery, a librarian quests for revenge.',
|
||||
'In a universe of illusions and danger, three time travellers and an adventurer search for justice.',
|
||||
'In a forgotten universe of barbarism, in an era of terror and mysticism, a virtual reality programmer and a spy try to find vengance and battle crime.',
|
||||
'In a universe of demons, in an era of insanity and ghosts, three bodyguards and a bodyguard try to find vengance.',
|
||||
'In a kingdom of corruption and battle, seven artificial intelligences try to save the last living fertile woman.',
|
||||
'In a universe of virutal reality and agony, in an age of ghosts and ghosts, a fortune-teller and a wanderer try to avert the apocalypse.',
|
||||
'In a crime-infested kingdom, three martial artists quest for the truth and oppose evil.',
|
||||
'In a terrifying universe of lost souls, in an era of lost souls, eight dancers fight evil.',
|
||||
'In a galaxy of confusion and insanity, three martial artists and a duke battle a mob of psychics.',
|
||||
'In an amazing kingdom, a wizard and a secretary hope to prevent the destruction of mankind.',
|
||||
'In a kingdom of deception, a reporter searches for fame.',
|
||||
'In a hellish empire, a swordswoman and a duke try to find the ultimate weapon and battle a conspiracy.',
|
||||
'In an evil galaxy of illusion, in a time of technology and misery, seven psychiatrists battle crime.',
|
||||
'In a dark city of confusion, three swordswomen and a singer battle lawlessness.',
|
||||
'In an ominous empire, in an age of hate, two philosophers and a student try to find justice and battle a mob of mages intent on stealing the souls of the innocent.',
|
||||
'In a kingdom of panic, six adventurers oppose lawlessness.',
|
||||
'In a land of dreams and hopelessness, three hackers and a cyborg search for justice.',
|
||||
'On a planet of mysticism, three travelers and a fire fighter quest for the ultimate weapon and oppose evil.',
|
||||
'In a wicked universe, five seers fight lawlessness.',
|
||||
'In a kingdom of death, in an era of illusion and blood, four colonists search for fame.',
|
||||
'In an amazing kingdom, in an age of sorcery and lost souls, eight space pirates quest for freedom.',
|
||||
'In a cursed empire, five inventors oppose terrorism.',
|
||||
'On a crime-ridden planet of conspiracy, a watchman and an artificial intelligence try to find love and oppose lawlessness.',
|
||||
'In a forgotten land, a reporter and a spy try to stop the apocalypse.',
|
||||
'In a forbidden land of prophecy, a scientist and an archivist oppose a cabal of barbarians intent on stealing the souls of the innocent.',
|
||||
'On an infernal world of illusion, a grave robber and a watchman try to find revenge and combat a syndicate of mages intent on stealing the source of all magic.',
|
||||
'In a galaxy of dark magic, four fighters seek freedom.',
|
||||
'In an empire of deception, six tomb-robbers quest for the ultimate weapon and combat an army of raiders.',
|
||||
'In a kingdom of corruption and lost souls, in an age of panic, eight planetologists oppose evil.',
|
||||
'In a galaxy of misery and hopelessness, in a time of agony and pain, five planetologists search for vengance.',
|
||||
'In a universe of technology and insanity, in a time of sorcery, a computer techician quests for hope.',
|
||||
'On a planet of dark magic and barbarism, in an age of horror and blasphemy, seven librarians search for fame.',
|
||||
'In an empire of dark magic, in a time of blood and illusions, four monks try to find the ultimate weapon and combat terrorism.',
|
||||
'In a forgotten empire of dark magic, six kings try to prevent the destruction of mankind.',
|
||||
'In a galaxy of dark magic and horror, in an age of hopelessness, four marines and an outlaw combat evil.',
|
||||
'In a mysterious city of illusion, in an age of computerization, a witch-hunter tries to find the ultimate weapon and opposes an evil corporation.',
|
||||
'In a damned kingdom of technology, a virtual reality programmer and a fighter seek fame.',
|
||||
'In a hellish kingdom, in an age of blasphemy and blasphemy, an astrologer searches for fame.',
|
||||
'In a damned world of devils, an alien and a ranger quest for love and oppose a syndicate of demons.',
|
||||
'In a cursed galaxy, in a time of pain, seven librarians hope to avert the apocalypse.',
|
||||
'In a crime-infested galaxy, in an era of hopelessness and panic, three champions and a grave robber try to solve the ultimate crime.'
|
||||
];
|
||||
|
||||
|
||||
module.exports = () => {
|
||||
module.exports = ()=>{
|
||||
return `<style>
|
||||
.phb#p1{ text-align:center; }
|
||||
.phb#p1:after{ display:none; }
|
||||
@@ -113,5 +113,5 @@ module.exports = () => {
|
||||
##### ${_.sample(subtitles)}
|
||||
</div>
|
||||
|
||||
\\page`
|
||||
}
|
||||
\\page`;
|
||||
};
|
||||
@@ -1,43 +1,43 @@
|
||||
var _ = require('lodash');
|
||||
const _ = require('lodash');
|
||||
|
||||
var ClassFeatureGen = require('./classfeature.gen.js');
|
||||
const ClassFeatureGen = require('./classfeature.gen.js');
|
||||
|
||||
var ClassTableGen = require('./classtable.gen.js');
|
||||
const ClassTableGen = require('./classtable.gen.js');
|
||||
|
||||
module.exports = function(){
|
||||
|
||||
var classname = _.sample(['Archivist', 'Fancyman', 'Linguist', 'Fletcher',
|
||||
'Notary', 'Berserker-Typist', 'Fishmongerer', 'Manicurist', 'Haberdasher', 'Concierge'])
|
||||
const classname = _.sample(['Archivist', 'Fancyman', 'Linguist', 'Fletcher',
|
||||
'Notary', 'Berserker-Typist', 'Fishmongerer', 'Manicurist', 'Haberdasher', 'Concierge']);
|
||||
|
||||
|
||||
var image = _.sample(_.map([
|
||||
"http://orig01.deviantart.net/4682/f/2007/099/f/c/bard_stick_figure_by_wrpigeek.png",
|
||||
"http://img07.deviantart.net/a3c9/i/2007/099/3/a/archer_stick_figure_by_wrpigeek.png",
|
||||
"http://pre04.deviantart.net/d596/th/pre/f/2007/099/5/2/adventurer_stick_figure_by_wrpigeek.png",
|
||||
"http://img13.deviantart.net/d501/i/2007/099/d/4/black_mage_stick_figure_by_wrpigeek.png",
|
||||
"http://img09.deviantart.net/5cf3/i/2007/099/d/d/dark_knight_stick_figure_by_wrpigeek.png",
|
||||
"http://pre01.deviantart.net/7a34/th/pre/f/2007/099/6/3/monk_stick_figure_by_wrpigeek.png",
|
||||
"http://img11.deviantart.net/5dcc/i/2007/099/d/1/mystic_knight_stick_figure_by_wrpigeek.png",
|
||||
"http://pre08.deviantart.net/ad45/th/pre/f/2007/099/a/0/thief_stick_figure_by_wrpigeek.png",
|
||||
], function(url){
|
||||
return "<img src = '" + url + "' style='max-width:8cm;max-height:25cm' />"
|
||||
}))
|
||||
const image = _.sample(_.map([
|
||||
'http://orig01.deviantart.net/4682/f/2007/099/f/c/bard_stick_figure_by_wrpigeek.png',
|
||||
'http://img07.deviantart.net/a3c9/i/2007/099/3/a/archer_stick_figure_by_wrpigeek.png',
|
||||
'http://pre04.deviantart.net/d596/th/pre/f/2007/099/5/2/adventurer_stick_figure_by_wrpigeek.png',
|
||||
'http://img13.deviantart.net/d501/i/2007/099/d/4/black_mage_stick_figure_by_wrpigeek.png',
|
||||
'http://img09.deviantart.net/5cf3/i/2007/099/d/d/dark_knight_stick_figure_by_wrpigeek.png',
|
||||
'http://pre01.deviantart.net/7a34/th/pre/f/2007/099/6/3/monk_stick_figure_by_wrpigeek.png',
|
||||
'http://img11.deviantart.net/5dcc/i/2007/099/d/1/mystic_knight_stick_figure_by_wrpigeek.png',
|
||||
'http://pre08.deviantart.net/ad45/th/pre/f/2007/099/a/0/thief_stick_figure_by_wrpigeek.png',
|
||||
], function(url){
|
||||
return `<img src = '${url}' style='max-width:8cm;max-height:25cm' />`;
|
||||
}));
|
||||
|
||||
|
||||
return [
|
||||
return `${[
|
||||
image,
|
||||
"",
|
||||
"```",
|
||||
"```",
|
||||
"<div style='margin-top:240px'></div>\n\n",
|
||||
"## " + classname,
|
||||
"Cool intro stuff will go here",
|
||||
'',
|
||||
'```',
|
||||
'```',
|
||||
'<div style=\'margin-top:240px\'></div>\n\n',
|
||||
`## ${classname}`,
|
||||
'Cool intro stuff will go here',
|
||||
|
||||
"\\page",
|
||||
'\\page',
|
||||
ClassTableGen(classname),
|
||||
ClassFeatureGen(classname),
|
||||
|
||||
|
||||
|
||||
].join('\n') + '\n\n\n';
|
||||
].join('\n')}\n\n\n`;
|
||||
};
|
||||
@@ -1,60 +1,60 @@
|
||||
var _ = require('lodash');
|
||||
const _ = require('lodash');
|
||||
|
||||
var spellNames = [
|
||||
"Astral Rite of Acne",
|
||||
"Create Acne",
|
||||
"Cursed Ramen Erruption",
|
||||
"Dark Chant of the Dentists",
|
||||
"Erruption of Immaturity",
|
||||
"Flaming Disc of Inconvenience",
|
||||
"Heal Bad Hygene",
|
||||
"Heavenly Transfiguration of the Cream Devil",
|
||||
"Hellish Cage of Mucus",
|
||||
"Irritate Peanut Butter Fairy",
|
||||
"Luminous Erruption of Tea",
|
||||
"Mystic Spell of the Poser",
|
||||
"Sorcerous Enchantment of the Chimneysweep",
|
||||
"Steak Sauce Ray",
|
||||
"Talk to Groupie",
|
||||
"Astonishing Chant of Chocolate",
|
||||
"Astounding Pasta Puddle",
|
||||
"Ball of Annoyance",
|
||||
"Cage of Yarn",
|
||||
"Control Noodles Elemental",
|
||||
"Create Nervousness",
|
||||
"Cure Baldness",
|
||||
"Cursed Ritual of Bad Hair",
|
||||
"Dispell Piles in Dentist",
|
||||
"Eliminate Florists",
|
||||
"Illusionary Transfiguration of the Babysitter",
|
||||
"Necromantic Armor of Salad Dressing",
|
||||
"Occult Transfiguration of Foot Fetish",
|
||||
"Protection from Mucus Giant",
|
||||
"Tinsel Blast",
|
||||
"Alchemical Evocation of the Goths",
|
||||
"Call Fangirl",
|
||||
"Divine Spell of Crossdressing",
|
||||
"Dominate Ramen Giant",
|
||||
"Eliminate Vindictiveness in Gym Teacher",
|
||||
"Extra-Planar Spell of Irritation",
|
||||
"Induce Whining in Babysitter",
|
||||
"Invoke Complaining",
|
||||
"Magical Enchantment of Arrogance",
|
||||
"Occult Globe of Salad Dressing",
|
||||
"Overwhelming Enchantment of the Chocolate Fairy",
|
||||
"Sorcerous Dandruff Globe",
|
||||
"Spiritual Invocation of the Costumers",
|
||||
"Ultimate Rite of the Confetti Angel",
|
||||
"Ultimate Ritual of Mouthwash",
|
||||
const spellNames = [
|
||||
'Astral Rite of Acne',
|
||||
'Create Acne',
|
||||
'Cursed Ramen Erruption',
|
||||
'Dark Chant of the Dentists',
|
||||
'Erruption of Immaturity',
|
||||
'Flaming Disc of Inconvenience',
|
||||
'Heal Bad Hygene',
|
||||
'Heavenly Transfiguration of the Cream Devil',
|
||||
'Hellish Cage of Mucus',
|
||||
'Irritate Peanut Butter Fairy',
|
||||
'Luminous Erruption of Tea',
|
||||
'Mystic Spell of the Poser',
|
||||
'Sorcerous Enchantment of the Chimneysweep',
|
||||
'Steak Sauce Ray',
|
||||
'Talk to Groupie',
|
||||
'Astonishing Chant of Chocolate',
|
||||
'Astounding Pasta Puddle',
|
||||
'Ball of Annoyance',
|
||||
'Cage of Yarn',
|
||||
'Control Noodles Elemental',
|
||||
'Create Nervousness',
|
||||
'Cure Baldness',
|
||||
'Cursed Ritual of Bad Hair',
|
||||
'Dispell Piles in Dentist',
|
||||
'Eliminate Florists',
|
||||
'Illusionary Transfiguration of the Babysitter',
|
||||
'Necromantic Armor of Salad Dressing',
|
||||
'Occult Transfiguration of Foot Fetish',
|
||||
'Protection from Mucus Giant',
|
||||
'Tinsel Blast',
|
||||
'Alchemical Evocation of the Goths',
|
||||
'Call Fangirl',
|
||||
'Divine Spell of Crossdressing',
|
||||
'Dominate Ramen Giant',
|
||||
'Eliminate Vindictiveness in Gym Teacher',
|
||||
'Extra-Planar Spell of Irritation',
|
||||
'Induce Whining in Babysitter',
|
||||
'Invoke Complaining',
|
||||
'Magical Enchantment of Arrogance',
|
||||
'Occult Globe of Salad Dressing',
|
||||
'Overwhelming Enchantment of the Chocolate Fairy',
|
||||
'Sorcerous Dandruff Globe',
|
||||
'Spiritual Invocation of the Costumers',
|
||||
'Ultimate Rite of the Confetti Angel',
|
||||
'Ultimate Ritual of Mouthwash',
|
||||
];
|
||||
|
||||
module.exports = {
|
||||
|
||||
spellList : function(){
|
||||
var levels = ['Cantrips (0 Level)', '2nd Level', '3rd Level', '4th Level', '5th Level', '6th Level', '7th Level', '8th Level', '9th Level'];
|
||||
const levels = ['Cantrips (0 Level)', '2nd Level', '3rd Level', '4th Level', '5th Level', '6th Level', '7th Level', '8th Level', '9th Level'];
|
||||
|
||||
var content = _.map(levels, (level)=>{
|
||||
var spells = _.map(_.sampleSize(spellNames, _.random(5, 15)), (spell)=>{
|
||||
const content = _.map(levels, (level)=>{
|
||||
const spells = _.map(_.sampleSize(spellNames, _.random(5, 15)), (spell)=>{
|
||||
return `- ${spell}`;
|
||||
}).join('\n');
|
||||
return `##### ${level} \n${spells} \n`;
|
||||
@@ -64,28 +64,28 @@ module.exports = {
|
||||
},
|
||||
|
||||
spell : function(){
|
||||
var level = ["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th"];
|
||||
var spellSchools = ["abjuration", "conjuration", "divination", "enchantment", "evocation", "illusion", "necromancy", "transmutation"];
|
||||
const level = ['1st', '2nd', '3rd', '4th', '5th', '6th', '7th', '8th', '9th'];
|
||||
const spellSchools = ['abjuration', 'conjuration', 'divination', 'enchantment', 'evocation', 'illusion', 'necromancy', 'transmutation'];
|
||||
|
||||
|
||||
var components = _.sampleSize(["V", "S", "M"], _.random(1,3)).join(', ');
|
||||
if(components.indexOf("M") !== -1){
|
||||
components += " (" + _.sampleSize(['a small doll', 'a crushed button worth at least 1cp', 'discarded gum wrapper'], _.random(1,3)).join(', ') + ")"
|
||||
let components = _.sampleSize(['V', 'S', 'M'], _.random(1, 3)).join(', ');
|
||||
if(components.indexOf('M') !== -1){
|
||||
components += ` (${_.sampleSize(['a small doll', 'a crushed button worth at least 1cp', 'discarded gum wrapper'], _.random(1, 3)).join(', ')})`;
|
||||
}
|
||||
|
||||
return [
|
||||
"#### " + _.sample(spellNames),
|
||||
"*" + _.sample(level) + "-level " + _.sample(spellSchools) + "*",
|
||||
"___",
|
||||
"- **Casting Time:** 1 action",
|
||||
"- **Range:** " + _.sample(["Self", "Touch", "30 feet", "60 feet"]),
|
||||
"- **Components:** " + components,
|
||||
"- **Duration:** " + _.sample(["Until dispelled", "1 round", "Instantaneous", "Concentration, up to 10 minutes", "1 hour"]),
|
||||
"",
|
||||
"A flame, equivalent in brightness to a torch, springs from from an object that you touch. ",
|
||||
"The effect look like a regular flame, but it creates no heat and doesn't use oxygen. ",
|
||||
"A *continual flame* can be covered or hidden but not smothered or quenched.",
|
||||
"\n\n\n"
|
||||
`#### ${_.sample(spellNames)}`,
|
||||
`*${_.sample(level)}-level ${_.sample(spellSchools)}*`,
|
||||
'___',
|
||||
'- **Casting Time:** 1 action',
|
||||
`- **Range:** ${_.sample(['Self', 'Touch', '30 feet', '60 feet'])}`,
|
||||
`- **Components:** ${components}`,
|
||||
`- **Duration:** ${_.sample(['Until dispelled', '1 round', 'Instantaneous', 'Concentration, up to 10 minutes', '1 hour'])}`,
|
||||
'',
|
||||
'A flame, equivalent in brightness to a torch, springs from from an object that you touch. ',
|
||||
'The effect look like a regular flame, but it creates no heat and doesn\'t use oxygen. ',
|
||||
'A *continual flame* can be covered or hidden but not smothered or quenched.',
|
||||
'\n\n\n'
|
||||
].join('\n');
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,196 +1,196 @@
|
||||
var _ = require('lodash');
|
||||
|
||||
var genList = function(list, max){
|
||||
return _.sampleSize(list, _.random(0,max)).join(', ') || "None";
|
||||
}
|
||||
|
||||
var getMonsterName = function(){
|
||||
return _.sample([
|
||||
"All-devouring Baseball Imp",
|
||||
"All-devouring Gumdrop Wraith",
|
||||
"Chocolate Hydra",
|
||||
"Devouring Peacock",
|
||||
"Economy-sized Colossus of the Lemonade Stand",
|
||||
"Ghost Pigeon",
|
||||
"Gibbering Duck",
|
||||
"Sparklemuffin Peacock Spider",
|
||||
"Gum Elemental",
|
||||
"Illiterate Construct of the Candy Store",
|
||||
"Ineffable Chihuahua",
|
||||
"Irritating Death Hamster",
|
||||
"Irritating Gold Mouse",
|
||||
"Juggernaut Snail",
|
||||
"Juggernaut of the Sock Drawer",
|
||||
"Koala of the Cosmos",
|
||||
"Mad Koala of the West",
|
||||
"Milk Djinni of the Lemonade Stand",
|
||||
"Mind Ferret",
|
||||
"Mystic Salt Spider",
|
||||
"Necrotic Halitosis Angel",
|
||||
"Pinstriped Famine Sheep",
|
||||
"Ritalin Leech",
|
||||
"Shocker Kangaroo",
|
||||
"Stellar Tennis Juggernaut",
|
||||
"Wailing Quail of the Sun",
|
||||
"Angel Pigeon",
|
||||
"Anime Sphinx",
|
||||
"Bored Avalanche Sheep of the Wasteland",
|
||||
"Devouring Nougat Sphinx of the Sock Drawer",
|
||||
"Djinni of the Footlocker",
|
||||
"Ectoplasmic Jazz Devil",
|
||||
"Flatuent Angel",
|
||||
"Gelatinous Duck of the Dream-Lands",
|
||||
"Gelatinous Mouse",
|
||||
"Golem of the Footlocker",
|
||||
"Lich Wombat",
|
||||
"Mechanical Sloth of the Past",
|
||||
"Milkshake Succubus",
|
||||
"Puffy Bone Peacock of the East",
|
||||
"Rainbow Manatee",
|
||||
"Rune Parrot",
|
||||
"Sand Cow",
|
||||
"Sinister Vanilla Dragon",
|
||||
"Snail of the North",
|
||||
"Spider of the Sewer",
|
||||
"Stellar Sawdust Leech",
|
||||
"Storm Anteater of Hell",
|
||||
"Stupid Spirit of the Brewery",
|
||||
"Time Kangaroo",
|
||||
"Tomb Poodle",
|
||||
]);
|
||||
}
|
||||
|
||||
var getType = function(){
|
||||
return _.sample(['Tiny', 'Small', 'Medium', 'Large', 'Gargantuan', 'Stupidly vast']) + " " + _.sample(['beast', 'fiend', 'annoyance', 'guy', 'cutie'])
|
||||
}
|
||||
|
||||
var getAlignment = function(){
|
||||
return _.sample([
|
||||
"annoying evil",
|
||||
"chaotic gossipy",
|
||||
"chaotic sloppy",
|
||||
"depressed neutral",
|
||||
"lawful bogus",
|
||||
"lawful coy",
|
||||
"manic-depressive evil",
|
||||
"narrow-minded neutral",
|
||||
"neutral annoying",
|
||||
"neutral ignorant",
|
||||
"oedpipal neutral",
|
||||
"silly neutral",
|
||||
"unoriginal neutral",
|
||||
"weird neutral",
|
||||
"wordy evil",
|
||||
"unaligned"
|
||||
]);
|
||||
};
|
||||
|
||||
var getStats = function(){
|
||||
return '>|' + _.times(6, function(){
|
||||
var num = _.random(1,20);
|
||||
var mod = Math.ceil(num/2 - 5)
|
||||
return num + " (" + (mod >= 0 ? '+'+mod : mod ) + ")"
|
||||
}).join('|') + '|';
|
||||
}
|
||||
|
||||
var genAbilities = function(){
|
||||
return _.sample([
|
||||
"> ***Pack Tactics.*** These guys work together. Like super well, you don't even know.",
|
||||
"> ***False Appearance. *** While the armor reamin motionless, it is indistinguishable from a normal suit of armor.",
|
||||
]);
|
||||
}
|
||||
|
||||
var genAction = function(){
|
||||
var name = _.sample([
|
||||
"Abdominal Drop",
|
||||
"Airplane Hammer",
|
||||
"Atomic Death Throw",
|
||||
"Bulldog Rake",
|
||||
"Corkscrew Strike",
|
||||
"Crossed Splash",
|
||||
"Crossface Suplex",
|
||||
"DDT Powerbomb",
|
||||
"Dual Cobra Wristlock",
|
||||
"Dual Throw",
|
||||
"Elbow Hold",
|
||||
"Gory Body Sweep",
|
||||
"Heel Jawbreaker",
|
||||
"Jumping Driver",
|
||||
"Open Chin Choke",
|
||||
"Scorpion Flurry",
|
||||
"Somersault Stump Fists",
|
||||
"Suffering Wringer",
|
||||
"Super Hip Submission",
|
||||
"Super Spin",
|
||||
"Team Elbow",
|
||||
"Team Foot",
|
||||
"Tilt-a-whirl Chin Sleeper",
|
||||
"Tilt-a-whirl Eye Takedown",
|
||||
"Turnbuckle Roll"
|
||||
])
|
||||
|
||||
return "> ***" + name + ".*** *Melee Weapon Attack:* +4 to hit, reach 5ft., one target. *Hit* 5 (1d6 + 2) ";
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
|
||||
full : function(){
|
||||
return [
|
||||
"___",
|
||||
"___",
|
||||
"> ## " + getMonsterName(),
|
||||
">*" + getType() + ", " + getAlignment() + "*",
|
||||
"> ___",
|
||||
"> - **Armor Class** " + _.random(10,20),
|
||||
"> - **Hit Points** " + _.random(1, 150) + "(1d4 + 5)",
|
||||
"> - **Speed** " + _.random(0,50) + "ft.",
|
||||
">___",
|
||||
">|STR|DEX|CON|INT|WIS|CHA|",
|
||||
">|:---:|:---:|:---:|:---:|:---:|:---:|",
|
||||
getStats(),
|
||||
">___",
|
||||
"> - **Condition Immunities** " + genList(["groggy", "swagged", "weak-kneed", "buzzed", "groovy", "melancholy", "drunk"], 3),
|
||||
"> - **Senses** passive Perception " + _.random(3, 20),
|
||||
"> - **Languages** " + genList(["Common", "Pottymouth", "Gibberish", "Latin", "Jive"], 2),
|
||||
"> - **Challenge** " + _.random(0, 15) + " (" + _.random(10,10000)+ " XP)",
|
||||
"> ___",
|
||||
_.times(_.random(3,6), function(){
|
||||
return genAbilities()
|
||||
}).join('\n>\n'),
|
||||
"> ### Actions",
|
||||
_.times(_.random(4,6), function(){
|
||||
return genAction()
|
||||
}).join('\n>\n'),
|
||||
].join('\n') + '\n\n\n';
|
||||
},
|
||||
|
||||
half : function(){
|
||||
return [
|
||||
"___",
|
||||
"> ## " + getMonsterName(),
|
||||
">*" + getType() + ", " + getAlignment() + "*",
|
||||
"> ___",
|
||||
"> - **Armor Class** " + _.random(10,20),
|
||||
"> - **Hit Points** " + _.random(1, 150) + "(1d4 + 5)",
|
||||
"> - **Speed** " + _.random(0,50) + "ft.",
|
||||
">___",
|
||||
">|STR|DEX|CON|INT|WIS|CHA|",
|
||||
">|:---:|:---:|:---:|:---:|:---:|:---:|",
|
||||
getStats(),
|
||||
">___",
|
||||
"> - **Condition Immunities** " + genList(["groggy", "swagged", "weak-kneed", "buzzed", "groovy", "melancholy", "drunk"], 3),
|
||||
"> - **Senses** passive Perception " + _.random(3, 20),
|
||||
"> - **Languages** " + genList(["Common", "Pottymouth", "Gibberish", "Latin", "Jive"], 2),
|
||||
"> - **Challenge** " + _.random(0, 15) + " (" + _.random(10,10000)+ " XP)",
|
||||
"> ___",
|
||||
_.times(_.random(0,2), function(){
|
||||
return genAbilities()
|
||||
}).join('\n>\n'),
|
||||
"> ### Actions",
|
||||
_.times(_.random(1,2), function(){
|
||||
return genAction()
|
||||
}).join('\n>\n'),
|
||||
].join('\n') + '\n\n\n';
|
||||
}
|
||||
}
|
||||
const _ = require('lodash');
|
||||
|
||||
const genList = function(list, max){
|
||||
return _.sampleSize(list, _.random(0, max)).join(', ') || 'None';
|
||||
};
|
||||
|
||||
const getMonsterName = function(){
|
||||
return _.sample([
|
||||
'All-devouring Baseball Imp',
|
||||
'All-devouring Gumdrop Wraith',
|
||||
'Chocolate Hydra',
|
||||
'Devouring Peacock',
|
||||
'Economy-sized Colossus of the Lemonade Stand',
|
||||
'Ghost Pigeon',
|
||||
'Gibbering Duck',
|
||||
'Sparklemuffin Peacock Spider',
|
||||
'Gum Elemental',
|
||||
'Illiterate Construct of the Candy Store',
|
||||
'Ineffable Chihuahua',
|
||||
'Irritating Death Hamster',
|
||||
'Irritating Gold Mouse',
|
||||
'Juggernaut Snail',
|
||||
'Juggernaut of the Sock Drawer',
|
||||
'Koala of the Cosmos',
|
||||
'Mad Koala of the West',
|
||||
'Milk Djinni of the Lemonade Stand',
|
||||
'Mind Ferret',
|
||||
'Mystic Salt Spider',
|
||||
'Necrotic Halitosis Angel',
|
||||
'Pinstriped Famine Sheep',
|
||||
'Ritalin Leech',
|
||||
'Shocker Kangaroo',
|
||||
'Stellar Tennis Juggernaut',
|
||||
'Wailing Quail of the Sun',
|
||||
'Angel Pigeon',
|
||||
'Anime Sphinx',
|
||||
'Bored Avalanche Sheep of the Wasteland',
|
||||
'Devouring Nougat Sphinx of the Sock Drawer',
|
||||
'Djinni of the Footlocker',
|
||||
'Ectoplasmic Jazz Devil',
|
||||
'Flatuent Angel',
|
||||
'Gelatinous Duck of the Dream-Lands',
|
||||
'Gelatinous Mouse',
|
||||
'Golem of the Footlocker',
|
||||
'Lich Wombat',
|
||||
'Mechanical Sloth of the Past',
|
||||
'Milkshake Succubus',
|
||||
'Puffy Bone Peacock of the East',
|
||||
'Rainbow Manatee',
|
||||
'Rune Parrot',
|
||||
'Sand Cow',
|
||||
'Sinister Vanilla Dragon',
|
||||
'Snail of the North',
|
||||
'Spider of the Sewer',
|
||||
'Stellar Sawdust Leech',
|
||||
'Storm Anteater of Hell',
|
||||
'Stupid Spirit of the Brewery',
|
||||
'Time Kangaroo',
|
||||
'Tomb Poodle',
|
||||
]);
|
||||
};
|
||||
|
||||
const getType = function(){
|
||||
return `${_.sample(['Tiny', 'Small', 'Medium', 'Large', 'Gargantuan', 'Stupidly vast'])} ${_.sample(['beast', 'fiend', 'annoyance', 'guy', 'cutie'])}`;
|
||||
};
|
||||
|
||||
const getAlignment = function(){
|
||||
return _.sample([
|
||||
'annoying evil',
|
||||
'chaotic gossipy',
|
||||
'chaotic sloppy',
|
||||
'depressed neutral',
|
||||
'lawful bogus',
|
||||
'lawful coy',
|
||||
'manic-depressive evil',
|
||||
'narrow-minded neutral',
|
||||
'neutral annoying',
|
||||
'neutral ignorant',
|
||||
'oedpipal neutral',
|
||||
'silly neutral',
|
||||
'unoriginal neutral',
|
||||
'weird neutral',
|
||||
'wordy evil',
|
||||
'unaligned'
|
||||
]);
|
||||
};
|
||||
|
||||
const getStats = function(){
|
||||
return `>|${_.times(6, function(){
|
||||
const num = _.random(1, 20);
|
||||
const mod = Math.ceil(num/2 - 5);
|
||||
return `${num} (${mod >= 0 ? `+${mod}` : mod})`;
|
||||
}).join('|')}|`;
|
||||
};
|
||||
|
||||
const genAbilities = function(){
|
||||
return _.sample([
|
||||
'> ***Pack Tactics.*** These guys work together. Like super well, you don\'t even know.',
|
||||
'> ***False Appearance. *** While the armor reamin motionless, it is indistinguishable from a normal suit of armor.',
|
||||
]);
|
||||
};
|
||||
|
||||
const genAction = function(){
|
||||
const name = _.sample([
|
||||
'Abdominal Drop',
|
||||
'Airplane Hammer',
|
||||
'Atomic Death Throw',
|
||||
'Bulldog Rake',
|
||||
'Corkscrew Strike',
|
||||
'Crossed Splash',
|
||||
'Crossface Suplex',
|
||||
'DDT Powerbomb',
|
||||
'Dual Cobra Wristlock',
|
||||
'Dual Throw',
|
||||
'Elbow Hold',
|
||||
'Gory Body Sweep',
|
||||
'Heel Jawbreaker',
|
||||
'Jumping Driver',
|
||||
'Open Chin Choke',
|
||||
'Scorpion Flurry',
|
||||
'Somersault Stump Fists',
|
||||
'Suffering Wringer',
|
||||
'Super Hip Submission',
|
||||
'Super Spin',
|
||||
'Team Elbow',
|
||||
'Team Foot',
|
||||
'Tilt-a-whirl Chin Sleeper',
|
||||
'Tilt-a-whirl Eye Takedown',
|
||||
'Turnbuckle Roll'
|
||||
]);
|
||||
|
||||
return `> ***${name}.*** *Melee Weapon Attack:* +4 to hit, reach 5ft., one target. *Hit* 5 (1d6 + 2) `;
|
||||
};
|
||||
|
||||
|
||||
module.exports = {
|
||||
|
||||
full : function(){
|
||||
return `${[
|
||||
'___',
|
||||
'___',
|
||||
`> ## ${getMonsterName()}`,
|
||||
`>*${getType()}, ${getAlignment()}*`,
|
||||
'> ___',
|
||||
`> - **Armor Class** ${_.random(10, 20)}`,
|
||||
`> - **Hit Points** ${_.random(1, 150)}(1d4 + 5)`,
|
||||
`> - **Speed** ${_.random(0, 50)}ft.`,
|
||||
'>___',
|
||||
'>|STR|DEX|CON|INT|WIS|CHA|',
|
||||
'>|:---:|:---:|:---:|:---:|:---:|:---:|',
|
||||
getStats(),
|
||||
'>___',
|
||||
`> - **Condition Immunities** ${genList(['groggy', 'swagged', 'weak-kneed', 'buzzed', 'groovy', 'melancholy', 'drunk'], 3)}`,
|
||||
`> - **Senses** passive Perception ${_.random(3, 20)}`,
|
||||
`> - **Languages** ${genList(['Common', 'Pottymouth', 'Gibberish', 'Latin', 'Jive'], 2)}`,
|
||||
`> - **Challenge** ${_.random(0, 15)} (${_.random(10, 10000)} XP)`,
|
||||
'> ___',
|
||||
_.times(_.random(3, 6), function(){
|
||||
return genAbilities();
|
||||
}).join('\n>\n'),
|
||||
'> ### Actions',
|
||||
_.times(_.random(4, 6), function(){
|
||||
return genAction();
|
||||
}).join('\n>\n'),
|
||||
].join('\n')}\n\n\n`;
|
||||
},
|
||||
|
||||
half : function(){
|
||||
return `${[
|
||||
'___',
|
||||
`> ## ${getMonsterName()}`,
|
||||
`>*${getType()}, ${getAlignment()}*`,
|
||||
'> ___',
|
||||
`> - **Armor Class** ${_.random(10, 20)}`,
|
||||
`> - **Hit Points** ${_.random(1, 150)}(1d4 + 5)`,
|
||||
`> - **Speed** ${_.random(0, 50)}ft.`,
|
||||
'>___',
|
||||
'>|STR|DEX|CON|INT|WIS|CHA|',
|
||||
'>|:---:|:---:|:---:|:---:|:---:|:---:|',
|
||||
getStats(),
|
||||
'>___',
|
||||
`> - **Condition Immunities** ${genList(['groggy', 'swagged', 'weak-kneed', 'buzzed', 'groovy', 'melancholy', 'drunk'], 3)}`,
|
||||
`> - **Senses** passive Perception ${_.random(3, 20)}`,
|
||||
`> - **Languages** ${genList(['Common', 'Pottymouth', 'Gibberish', 'Latin', 'Jive'], 2)}`,
|
||||
`> - **Challenge** ${_.random(0, 15)} (${_.random(10, 10000)} XP)`,
|
||||
'> ___',
|
||||
_.times(_.random(0, 2), function(){
|
||||
return genAbilities();
|
||||
}).join('\n>\n'),
|
||||
'> ### Actions',
|
||||
_.times(_.random(1, 2), function(){
|
||||
return genAction();
|
||||
}).join('\n>\n'),
|
||||
].join('\n')}\n\n\n`;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,80 +1,81 @@
|
||||
var MagicGen = require('./magic.gen.js');
|
||||
var ClassTableGen = require('./classtable.gen.js');
|
||||
var MonsterBlockGen = require('./monsterblock.gen.js');
|
||||
var ClassFeatureGen = require('./classfeature.gen.js');
|
||||
var FullClassGen = require('./fullclass.gen.js');
|
||||
var CoverPageGen = require('./coverpage.gen.js');
|
||||
var TableOfContentsGen = require('./tableOfContents.gen.js');
|
||||
/* eslint-disable max-lines */
|
||||
|
||||
const MagicGen = require('./magic.gen.js');
|
||||
const ClassTableGen = require('./classtable.gen.js');
|
||||
const MonsterBlockGen = require('./monsterblock.gen.js');
|
||||
const ClassFeatureGen = require('./classfeature.gen.js');
|
||||
const CoverPageGen = require('./coverpage.gen.js');
|
||||
const TableOfContentsGen = require('./tableOfContents.gen.js');
|
||||
|
||||
|
||||
module.exports = [
|
||||
|
||||
{
|
||||
groupName : 'Editor',
|
||||
icon : 'fa-pencil',
|
||||
snippets : [
|
||||
icon : 'fa-pencil',
|
||||
snippets : [
|
||||
{
|
||||
name : "Column Break",
|
||||
name : 'Column Break',
|
||||
icon : 'fa-columns',
|
||||
gen : "```\n```\n\n"
|
||||
gen : '```\n```\n\n'
|
||||
},
|
||||
{
|
||||
name : "New Page",
|
||||
name : 'New Page',
|
||||
icon : 'fa-file-text',
|
||||
gen : "\\page\n\n"
|
||||
gen : '\\page\n\n'
|
||||
},
|
||||
{
|
||||
name : "Vertical Spacing",
|
||||
name : 'Vertical Spacing',
|
||||
icon : 'fa-arrows-v',
|
||||
gen : "<div style='margin-top:140px'></div>\n\n"
|
||||
gen : '<div style=\'margin-top:140px\'></div>\n\n'
|
||||
},
|
||||
{
|
||||
name : "Wide Block",
|
||||
name : 'Wide Block',
|
||||
icon : 'fa-arrows-h',
|
||||
gen : "<div class='wide'>\nEverything in here will be extra wide. Tables, text, everything! Beware though, CSS columns can behave a bit weird sometimes.\n</div>\n"
|
||||
gen : '<div class=\'wide\'>\nEverything in here will be extra wide. Tables, text, everything! Beware though, CSS columns can behave a bit weird sometimes.\n</div>\n'
|
||||
},
|
||||
{
|
||||
name : "Image",
|
||||
name : 'Image',
|
||||
icon : 'fa-image',
|
||||
gen : [
|
||||
"<img ",
|
||||
" src='https://s-media-cache-ak0.pinimg.com/736x/4a/81/79/4a8179462cfdf39054a418efd4cb743e.jpg' ",
|
||||
" style='width:325px' />",
|
||||
"Credit: Kyounghwan Kim"
|
||||
gen : [
|
||||
'<img ',
|
||||
' src=\'https://s-media-cache-ak0.pinimg.com/736x/4a/81/79/4a8179462cfdf39054a418efd4cb743e.jpg\' ',
|
||||
' style=\'width:325px\' />',
|
||||
'Credit: Kyounghwan Kim'
|
||||
].join('\n')
|
||||
},
|
||||
{
|
||||
name : "Background Image",
|
||||
name : 'Background Image',
|
||||
icon : 'fa-tree',
|
||||
gen : [
|
||||
"<img ",
|
||||
" src='http://i.imgur.com/hMna6G0.png' ",
|
||||
" style='position:absolute; top:50px; right:30px; width:280px' />"
|
||||
gen : [
|
||||
'<img ',
|
||||
' src=\'http://i.imgur.com/hMna6G0.png\' ',
|
||||
' style=\'position:absolute; top:50px; right:30px; width:280px\' />'
|
||||
].join('\n')
|
||||
},
|
||||
|
||||
{
|
||||
name : "Page Number",
|
||||
name : 'Page Number',
|
||||
icon : 'fa-bookmark',
|
||||
gen : "<div class='pageNumber'>1</div>\n<div class='footnote'>PART 1 | FANCINESS</div>\n\n"
|
||||
gen : '<div class=\'pageNumber\'>1</div>\n<div class=\'footnote\'>PART 1 | FANCINESS</div>\n\n'
|
||||
},
|
||||
|
||||
{
|
||||
name : "Auto-incrementing Page Number",
|
||||
name : 'Auto-incrementing Page Number',
|
||||
icon : 'fa-sort-numeric-asc',
|
||||
gen : "<div class='pageNumber auto'></div>\n"
|
||||
gen : '<div class=\'pageNumber auto\'></div>\n'
|
||||
},
|
||||
|
||||
{
|
||||
name : "Link to page",
|
||||
name : 'Link to page',
|
||||
icon : 'fa-link',
|
||||
gen : "[Click here](#p3) to go to page 3\n"
|
||||
gen : '[Click here](#p3) to go to page 3\n'
|
||||
},
|
||||
|
||||
{
|
||||
name : "Table of Contents",
|
||||
name : 'Table of Contents',
|
||||
icon : 'fa-book',
|
||||
gen : TableOfContentsGen
|
||||
gen : TableOfContentsGen
|
||||
},
|
||||
|
||||
|
||||
@@ -86,63 +87,63 @@ module.exports = [
|
||||
|
||||
{
|
||||
groupName : 'PHB',
|
||||
icon : 'fa-book',
|
||||
snippets : [
|
||||
icon : 'fa-book',
|
||||
snippets : [
|
||||
{
|
||||
name : 'Spell',
|
||||
icon : 'fa-magic',
|
||||
gen : MagicGen.spell,
|
||||
gen : MagicGen.spell,
|
||||
},
|
||||
{
|
||||
name : 'Spell List',
|
||||
icon : 'fa-list',
|
||||
gen : MagicGen.spellList,
|
||||
gen : MagicGen.spellList,
|
||||
},
|
||||
{
|
||||
name : 'Class Feature',
|
||||
icon : 'fa-trophy',
|
||||
gen : ClassFeatureGen,
|
||||
gen : ClassFeatureGen,
|
||||
},
|
||||
{
|
||||
name : 'Note',
|
||||
icon : 'fa-sticky-note',
|
||||
gen : function(){
|
||||
gen : function(){
|
||||
return [
|
||||
"> ##### Time to Drop Knowledge",
|
||||
"> Use notes to point out some interesting information. ",
|
||||
"> ",
|
||||
"> **Tables and lists** both work within a note."
|
||||
'> ##### Time to Drop Knowledge',
|
||||
'> Use notes to point out some interesting information. ',
|
||||
'> ',
|
||||
'> **Tables and lists** both work within a note.'
|
||||
].join('\n');
|
||||
},
|
||||
},
|
||||
{
|
||||
name : 'Descriptive Text Box',
|
||||
icon : 'fa-sticky-note-o',
|
||||
gen : function(){
|
||||
gen : function(){
|
||||
return [
|
||||
"<div class='descriptive'>",
|
||||
"##### Time to Drop Knowledge",
|
||||
"Use notes to point out some interesting information. ",
|
||||
"",
|
||||
"**Tables and lists** both work within a note.",
|
||||
"</div>"
|
||||
'<div class=\'descriptive\'>',
|
||||
'##### Time to Drop Knowledge',
|
||||
'Use notes to point out some interesting information. ',
|
||||
'',
|
||||
'**Tables and lists** both work within a note.',
|
||||
'</div>'
|
||||
].join('\n');
|
||||
},
|
||||
},
|
||||
{
|
||||
name : 'Monster Stat Block',
|
||||
icon : 'fa-bug',
|
||||
gen : MonsterBlockGen.half,
|
||||
gen : MonsterBlockGen.half,
|
||||
},
|
||||
{
|
||||
name : 'Wide Monster Stat Block',
|
||||
icon : 'fa-paw',
|
||||
gen : MonsterBlockGen.full,
|
||||
gen : MonsterBlockGen.full,
|
||||
},
|
||||
{
|
||||
name : 'Cover Page',
|
||||
icon : 'fa-file-word-o',
|
||||
gen : CoverPageGen,
|
||||
gen : CoverPageGen,
|
||||
},
|
||||
]
|
||||
},
|
||||
@@ -153,77 +154,77 @@ module.exports = [
|
||||
|
||||
{
|
||||
groupName : 'Tables',
|
||||
icon : 'fa-table',
|
||||
snippets : [
|
||||
icon : 'fa-table',
|
||||
snippets : [
|
||||
{
|
||||
name : "Class Table",
|
||||
name : 'Class Table',
|
||||
icon : 'fa-table',
|
||||
gen : ClassTableGen.full,
|
||||
gen : ClassTableGen.full,
|
||||
},
|
||||
{
|
||||
name : "Half Class Table",
|
||||
name : 'Half Class Table',
|
||||
icon : 'fa-list-alt',
|
||||
gen : ClassTableGen.half,
|
||||
gen : ClassTableGen.half,
|
||||
},
|
||||
{
|
||||
name : 'Table',
|
||||
icon : 'fa-th-list',
|
||||
gen : function(){
|
||||
gen : function(){
|
||||
return [
|
||||
"##### Cookie Tastiness",
|
||||
"| Tastiness | Cookie Type |",
|
||||
"|:----:|:-------------|",
|
||||
"| -5 | Raisin |",
|
||||
"| 8th | Chocolate Chip |",
|
||||
"| 11th | 2 or lower |",
|
||||
"| 14th | 3 or lower |",
|
||||
"| 17th | 4 or lower |\n\n",
|
||||
'##### Cookie Tastiness',
|
||||
'| Tastiness | Cookie Type |',
|
||||
'|:----:|:-------------|',
|
||||
'| -5 | Raisin |',
|
||||
'| 8th | Chocolate Chip |',
|
||||
'| 11th | 2 or lower |',
|
||||
'| 14th | 3 or lower |',
|
||||
'| 17th | 4 or lower |\n\n',
|
||||
].join('\n');
|
||||
},
|
||||
},
|
||||
{
|
||||
name : 'Wide Table',
|
||||
icon : 'fa-list',
|
||||
gen : function(){
|
||||
gen : function(){
|
||||
return [
|
||||
"<div class='wide'>",
|
||||
"##### Cookie Tastiness",
|
||||
"| Tastiness | Cookie Type |",
|
||||
"|:----:|:-------------|",
|
||||
"| -5 | Raisin |",
|
||||
"| 8th | Chocolate Chip |",
|
||||
"| 11th | 2 or lower |",
|
||||
"| 14th | 3 or lower |",
|
||||
"| 17th | 4 or lower |",
|
||||
"</div>\n\n"
|
||||
'<div class=\'wide\'>',
|
||||
'##### Cookie Tastiness',
|
||||
'| Tastiness | Cookie Type |',
|
||||
'|:----:|:-------------|',
|
||||
'| -5 | Raisin |',
|
||||
'| 8th | Chocolate Chip |',
|
||||
'| 11th | 2 or lower |',
|
||||
'| 14th | 3 or lower |',
|
||||
'| 17th | 4 or lower |',
|
||||
'</div>\n\n'
|
||||
].join('\n');
|
||||
},
|
||||
},
|
||||
{
|
||||
name : 'Split Table',
|
||||
icon : 'fa-th-large',
|
||||
gen : function(){
|
||||
return [
|
||||
"<div style='column-count:2'>",
|
||||
"| d10 | Damage Type |",
|
||||
"|:---:|:------------|",
|
||||
"| 1 | Acid |",
|
||||
"| 2 | Cold |",
|
||||
"| 3 | Fire |",
|
||||
"| 4 | Force |",
|
||||
"| 5 | Lightning |",
|
||||
"",
|
||||
"```",
|
||||
"```",
|
||||
"",
|
||||
"| d10 | Damage Type |",
|
||||
"|:---:|:------------|",
|
||||
"| 6 | Necrotic |",
|
||||
"| 7 | Poison |",
|
||||
"| 8 | Psychic |",
|
||||
"| 9 | Radiant |",
|
||||
"| 10 | Thunder |",
|
||||
"</div>\n\n",
|
||||
gen : function(){
|
||||
return [
|
||||
'<div style=\'column-count:2\'>',
|
||||
'| d10 | Damage Type |',
|
||||
'|:---:|:------------|',
|
||||
'| 1 | Acid |',
|
||||
'| 2 | Cold |',
|
||||
'| 3 | Fire |',
|
||||
'| 4 | Force |',
|
||||
'| 5 | Lightning |',
|
||||
'',
|
||||
'```',
|
||||
'```',
|
||||
'',
|
||||
'| d10 | Damage Type |',
|
||||
'|:---:|:------------|',
|
||||
'| 6 | Necrotic |',
|
||||
'| 7 | Poison |',
|
||||
'| 8 | Psychic |',
|
||||
'| 9 | Radiant |',
|
||||
'| 10 | Thunder |',
|
||||
'</div>\n\n',
|
||||
].join('\n');
|
||||
},
|
||||
}
|
||||
@@ -237,12 +238,12 @@ module.exports = [
|
||||
|
||||
{
|
||||
groupName : 'Print',
|
||||
icon : 'fa-print',
|
||||
snippets : [
|
||||
icon : 'fa-print',
|
||||
snippets : [
|
||||
{
|
||||
name : "A4 PageSize",
|
||||
name : 'A4 PageSize',
|
||||
icon : 'fa-file-o',
|
||||
gen : ['<style>',
|
||||
gen : ['<style>',
|
||||
' .phb{',
|
||||
' width : 210mm;',
|
||||
' height : 296.8mm;',
|
||||
@@ -251,9 +252,9 @@ module.exports = [
|
||||
].join('\n')
|
||||
},
|
||||
{
|
||||
name : "Ink Friendly",
|
||||
name : 'Ink Friendly',
|
||||
icon : 'fa-tint',
|
||||
gen : ['<style>',
|
||||
gen : ['<style>',
|
||||
' .phb{ background : white;}',
|
||||
' .phb img{ display : none;}',
|
||||
' .phb hr+blockquote{background : white;}',
|
||||
@@ -264,4 +265,4 @@ module.exports = [
|
||||
]
|
||||
},
|
||||
|
||||
]
|
||||
];
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
const _ = require('lodash');
|
||||
|
||||
const getTOC = (pages) => {
|
||||
const getTOC = (pages)=>{
|
||||
const add1 = (title, page)=>{
|
||||
res.push({
|
||||
title : title,
|
||||
page : page + 1,
|
||||
title : title,
|
||||
page : page + 1,
|
||||
children : []
|
||||
});
|
||||
}
|
||||
};
|
||||
const add2 = (title, page)=>{
|
||||
if(!_.last(res)) add1('', page);
|
||||
_.last(res).children.push({
|
||||
title : title,
|
||||
page : page + 1,
|
||||
title : title,
|
||||
page : page + 1,
|
||||
children : []
|
||||
});
|
||||
}
|
||||
};
|
||||
const add3 = (title, page)=>{
|
||||
if(!_.last(res)) add1('', page);
|
||||
if(!_.last(_.last(res).children)) add2('', page);
|
||||
_.last(_.last(res).children).children.push({
|
||||
title : title,
|
||||
page : page + 1,
|
||||
title : title,
|
||||
page : page + 1,
|
||||
children : []
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let res = [];
|
||||
const res = [];
|
||||
_.each(pages, (page, pageNum)=>{
|
||||
const lines = page.split('\n');
|
||||
_.each(lines, (line) => {
|
||||
_.each(lines, (line)=>{
|
||||
if(_.startsWith(line, '# ')){
|
||||
const title = line.replace('# ', '');
|
||||
add1(title, pageNum)
|
||||
add1(title, pageNum);
|
||||
}
|
||||
if(_.startsWith(line, '## ')){
|
||||
const title = line.replace('## ', '');
|
||||
@@ -42,21 +42,21 @@ const getTOC = (pages) => {
|
||||
const title = line.replace('### ', '');
|
||||
add3(title, pageNum);
|
||||
}
|
||||
})
|
||||
});
|
||||
});
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = function(brew){
|
||||
const pages = brew.split('\\page');
|
||||
const TOC = getTOC(pages);
|
||||
const markdown = _.reduce(TOC, (r, g1, idx1)=>{
|
||||
r.push(`- **[${idx1 + 1} ${g1.title}](#p${g1.page})**`)
|
||||
r.push(`- **[${idx1 + 1} ${g1.title}](#p${g1.page})**`);
|
||||
if(g1.children.length){
|
||||
_.each(g1.children, (g2, idx2) => {
|
||||
_.each(g1.children, (g2, idx2)=>{
|
||||
r.push(` - [${idx1 + 1}.${idx2 + 1} ${g2.title}](#p${g2.page})`);
|
||||
if(g2.children.length){
|
||||
_.each(g2.children, (g3, idx3) => {
|
||||
_.each(g2.children, (g3, idx3)=>{
|
||||
r.push(` - [${idx1 + 1}.${idx2 + 1}.${idx3 + 1} ${g3.title}](#p${g3.page})`);
|
||||
});
|
||||
}
|
||||
@@ -69,4 +69,4 @@ module.exports = function(brew){
|
||||
##### Table Of Contents
|
||||
${markdown}
|
||||
</div>\n`;
|
||||
}
|
||||
};
|
||||
@@ -1,89 +1,90 @@
|
||||
const React = require('react');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
|
||||
const CreateRouter = require('pico-router').createRouter;
|
||||
|
||||
const HomePage = require('./pages/homePage/homePage.jsx');
|
||||
const EditPage = require('./pages/editPage/editPage.jsx');
|
||||
const UserPage = require('./pages/userPage/userPage.jsx');
|
||||
const SharePage = require('./pages/sharePage/sharePage.jsx');
|
||||
const NewPage = require('./pages/newPage/newPage.jsx');
|
||||
const ErrorPage = require('./pages/errorPage/errorPage.jsx');
|
||||
const PrintPage = require('./pages/printPage/printPage.jsx');
|
||||
|
||||
let Router;
|
||||
const Homebrew = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
url : '',
|
||||
welcomeText : '',
|
||||
changelog : '',
|
||||
version : '0.0.0',
|
||||
account : null,
|
||||
brew : {
|
||||
title : '',
|
||||
text : '',
|
||||
shareId : null,
|
||||
editId : null,
|
||||
createdAt : null,
|
||||
updatedAt : null,
|
||||
}
|
||||
};
|
||||
},
|
||||
componentWillMount: function() {
|
||||
global.account = this.props.account;
|
||||
global.version = this.props.version;
|
||||
|
||||
|
||||
Router = CreateRouter({
|
||||
'/edit/:id' : (args) => {
|
||||
if(!this.props.brew.editId){
|
||||
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) => {
|
||||
return <UserPage
|
||||
username={args.username}
|
||||
brews={this.props.brews}
|
||||
/>
|
||||
},
|
||||
'/print/:id' : (args, query) => {
|
||||
return <PrintPage brew={this.props.brew} query={query}/>;
|
||||
},
|
||||
'/print' : (args, query) => {
|
||||
return <PrintPage query={query}/>;
|
||||
},
|
||||
'/new' : (args) => {
|
||||
return <NewPage />
|
||||
},
|
||||
'/changelog' : (args) => {
|
||||
return <SharePage
|
||||
brew={{title : 'Changelog', text : this.props.changelog}} />
|
||||
},
|
||||
'*' : <HomePage
|
||||
welcomeText={this.props.welcomeText} />,
|
||||
});
|
||||
},
|
||||
render : function(){
|
||||
return <div className='homebrew'>
|
||||
<Router initialUrl={this.props.url}/>
|
||||
</div>
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Homebrew;
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
|
||||
const CreateRouter = require('pico-router').createRouter;
|
||||
|
||||
const HomePage = require('./pages/homePage/homePage.jsx');
|
||||
const EditPage = require('./pages/editPage/editPage.jsx');
|
||||
const UserPage = require('./pages/userPage/userPage.jsx');
|
||||
const SharePage = require('./pages/sharePage/sharePage.jsx');
|
||||
const NewPage = require('./pages/newPage/newPage.jsx');
|
||||
const ErrorPage = require('./pages/errorPage/errorPage.jsx');
|
||||
const PrintPage = require('./pages/printPage/printPage.jsx');
|
||||
|
||||
let Router;
|
||||
const Homebrew = createClass({
|
||||
getDefaultProps : function() {
|
||||
return {
|
||||
url : '',
|
||||
welcomeText : '',
|
||||
changelog : '',
|
||||
version : '0.0.0',
|
||||
account : null,
|
||||
brew : {
|
||||
title : '',
|
||||
text : '',
|
||||
shareId : null,
|
||||
editId : null,
|
||||
createdAt : null,
|
||||
updatedAt : null,
|
||||
}
|
||||
};
|
||||
},
|
||||
componentWillMount : function() {
|
||||
global.account = this.props.account;
|
||||
global.version = this.props.version;
|
||||
|
||||
|
||||
Router = CreateRouter({
|
||||
'/edit/:id' : (args)=>{
|
||||
if(!this.props.brew.editId){
|
||||
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)=>{
|
||||
return <UserPage
|
||||
username={args.username}
|
||||
brews={this.props.brews}
|
||||
/>;
|
||||
},
|
||||
'/print/:id' : (args, query)=>{
|
||||
return <PrintPage brew={this.props.brew} query={query}/>;
|
||||
},
|
||||
'/print' : (args, query)=>{
|
||||
return <PrintPage query={query}/>;
|
||||
},
|
||||
'/new' : (args)=>{
|
||||
return <NewPage />;
|
||||
},
|
||||
'/changelog' : (args)=>{
|
||||
return <SharePage
|
||||
brew={{ title: 'Changelog', text: this.props.changelog }} />;
|
||||
},
|
||||
'*' : <HomePage
|
||||
welcomeText={this.props.welcomeText} />,
|
||||
});
|
||||
},
|
||||
render : function(){
|
||||
return <div className='homebrew'>
|
||||
<Router defaultUrl={this.props.url}/>
|
||||
</div>;
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Homebrew;
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
const React = require('react');
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
|
||||
module.exports = function(props){
|
||||
if(global.account){
|
||||
return <Nav.item href={`/user/${global.account.username}`} color='yellow' icon='fa-user'>
|
||||
{global.account.username}
|
||||
</Nav.item>
|
||||
}
|
||||
let url = '';
|
||||
if(typeof window !== 'undefined'){
|
||||
url = window.location.href
|
||||
}
|
||||
return <Nav.item href={`http://naturalcrit.com/login?redirect=${url}`} color='teal' icon='fa-sign-in'>
|
||||
login
|
||||
</Nav.item>
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
|
||||
module.exports = function(props){
|
||||
if(global.account){
|
||||
return <Nav.item href={`/user/${global.account.username}`} color='yellow' icon='fa-user'>
|
||||
{global.account.username}
|
||||
</Nav.item>;
|
||||
}
|
||||
let url = '';
|
||||
if(typeof window !== 'undefined'){
|
||||
url = window.location.href;
|
||||
}
|
||||
return <Nav.item href={`http://naturalcrit.com/login?redirect=${url}`} color='teal' icon='fa-sign-in'>
|
||||
login
|
||||
</Nav.item>;
|
||||
};
|
||||
@@ -1,33 +1,34 @@
|
||||
var React = require('react');
|
||||
var _ = require('lodash');
|
||||
var cx = require('classnames');
|
||||
var Nav = require('naturalcrit/nav/nav.jsx');
|
||||
|
||||
const MAX_TITLE_LENGTH = 50;
|
||||
|
||||
|
||||
var EditTitle = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
title : '',
|
||||
onChange : function(){}
|
||||
};
|
||||
},
|
||||
|
||||
handleChange : function(e){
|
||||
if(e.target.value.length > MAX_TITLE_LENGTH) return;
|
||||
this.props.onChange(e.target.value);
|
||||
},
|
||||
render : function(){
|
||||
return <Nav.item className='editTitle'>
|
||||
<input placeholder='Brew Title' type='text' value={this.props.title} onChange={this.handleChange} />
|
||||
|
||||
<div className={cx('charCount', {'max' : this.props.title.length >= MAX_TITLE_LENGTH})}>
|
||||
{this.props.title.length}/{MAX_TITLE_LENGTH}
|
||||
</div>
|
||||
</Nav.item>
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
|
||||
const MAX_TITLE_LENGTH = 50;
|
||||
|
||||
|
||||
const EditTitle = createClass({
|
||||
getDefaultProps : function() {
|
||||
return {
|
||||
title : '',
|
||||
onChange : function(){}
|
||||
};
|
||||
},
|
||||
|
||||
handleChange : function(e){
|
||||
if(e.target.value.length > MAX_TITLE_LENGTH) return;
|
||||
this.props.onChange(e.target.value);
|
||||
},
|
||||
render : function(){
|
||||
return <Nav.item className='editTitle'>
|
||||
<input placeholder='Brew Title' type='text' value={this.props.title} onChange={this.handleChange} />
|
||||
|
||||
<div className={cx('charCount', { 'max': this.props.title.length >= MAX_TITLE_LENGTH })}>
|
||||
{this.props.title.length}/{MAX_TITLE_LENGTH}
|
||||
</div>
|
||||
</Nav.item>;
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
module.exports = EditTitle;
|
||||
@@ -1,8 +1,13 @@
|
||||
var React = require('react');
|
||||
var Nav = require('naturalcrit/nav/nav.jsx');
|
||||
|
||||
module.exports = function(props){
|
||||
return <Nav.item newTab={true} href='https://github.com/stolksdorf/homebrewery/issues' color='red' icon='fa-bug'>
|
||||
report issue
|
||||
</Nav.item>
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
|
||||
module.exports = function(props){
|
||||
return <Nav.item
|
||||
newTab={true}
|
||||
color='red'
|
||||
icon='fa-bug'
|
||||
href={`https://www.reddit.com/r/homebrewery/submit?selftext=true&title=${encodeURIComponent('[Issue] Describe Your Issue Here')}`} >
|
||||
report issue
|
||||
</Nav.item>;
|
||||
};
|
||||
@@ -1,49 +1,50 @@
|
||||
const React = require('react');
|
||||
const _ = require('lodash');
|
||||
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
|
||||
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(){
|
||||
return <Nav.base>
|
||||
<Nav.section>
|
||||
<Nav.logo />
|
||||
<Nav.item href='/' className='homebrewLogo'>
|
||||
<div>The Homebrewery</div>
|
||||
</Nav.item>
|
||||
<Nav.item>{`v${this.state.ver}`}</Nav.item>
|
||||
|
||||
{/*this.renderChromeWarning()*/}
|
||||
</Nav.section>
|
||||
{this.props.children}
|
||||
</Nav.base>
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Navbar;
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
|
||||
const Navbar = 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(){
|
||||
return <Nav.base>
|
||||
<Nav.section>
|
||||
<Nav.logo />
|
||||
<Nav.item href='/' className='homebrewLogo'>
|
||||
<div>The Homebrewery</div>
|
||||
</Nav.item>
|
||||
<Nav.item>{`v${this.state.ver}`}</Nav.item>
|
||||
|
||||
{/*this.renderChromeWarning()*/}
|
||||
</Nav.section>
|
||||
{this.props.children}
|
||||
</Nav.base>;
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Navbar;
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
var React = require('react');
|
||||
var Nav = require('naturalcrit/nav/nav.jsx');
|
||||
|
||||
module.exports = function(props){
|
||||
return <Nav.item
|
||||
className='patreon'
|
||||
newTab={true}
|
||||
href='https://www.patreon.com/stolksdorf'
|
||||
color='green'
|
||||
icon='fa-heart'>
|
||||
help out
|
||||
</Nav.item>
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
|
||||
module.exports = function(props){
|
||||
return <Nav.item
|
||||
className='patreon'
|
||||
newTab={true}
|
||||
href='https://www.patreon.com/stolksdorf'
|
||||
color='green'
|
||||
icon='fa-heart'>
|
||||
help out
|
||||
</Nav.item>;
|
||||
};
|
||||
@@ -1,8 +1,9 @@
|
||||
var React = require('react');
|
||||
var Nav = require('naturalcrit/nav/nav.jsx');
|
||||
|
||||
module.exports = function(props){
|
||||
return <Nav.item newTab={true} href={'/print/' + props.shareId +'?dialog=true'} color='purple' icon='fa-file-pdf-o'>
|
||||
get PDF
|
||||
</Nav.item>
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
|
||||
module.exports = function(props){
|
||||
return <Nav.item newTab={true} href={`/print/${props.shareId}?dialog=true`} color='purple' icon='fa-file-pdf-o'>
|
||||
get PDF
|
||||
</Nav.item>;
|
||||
};
|
||||
@@ -1,199 +1,200 @@
|
||||
var React = require('react');
|
||||
var _ = require('lodash');
|
||||
var cx = require('classnames');
|
||||
var Moment = require('moment');
|
||||
|
||||
var Nav = require('naturalcrit/nav/nav.jsx');
|
||||
|
||||
const VIEW_KEY = 'homebrewery-recently-viewed';
|
||||
const EDIT_KEY = 'homebrewery-recently-edited';
|
||||
|
||||
var BaseItem = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
storageKey : '',
|
||||
text : '',
|
||||
currentBrew:{
|
||||
title : '',
|
||||
id : '',
|
||||
url : ''
|
||||
}
|
||||
};
|
||||
},
|
||||
getInitialState: function() {
|
||||
return {
|
||||
showDropdown: false,
|
||||
brews : []
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
var brews = JSON.parse(localStorage.getItem(this.props.storageKey) || '[]');
|
||||
|
||||
brews = _.filter(brews, (brew)=>{
|
||||
return brew.id !== this.props.currentBrew.id;
|
||||
});
|
||||
if(this.props.currentBrew.id){
|
||||
brews.unshift({
|
||||
id : this.props.currentBrew.id,
|
||||
url : this.props.currentBrew.url,
|
||||
title : this.props.currentBrew.title,
|
||||
ts : Date.now()
|
||||
});
|
||||
}
|
||||
brews = _.slice(brews, 0, 8);
|
||||
localStorage.setItem(this.props.storageKey, JSON.stringify(brews));
|
||||
this.setState({
|
||||
brews : brews
|
||||
});
|
||||
},
|
||||
|
||||
handleDropdown : function(show){
|
||||
this.setState({
|
||||
showDropdown : show
|
||||
})
|
||||
},
|
||||
|
||||
renderDropdown : function(){
|
||||
if(!this.state.showDropdown) return null;
|
||||
|
||||
var items = _.map(this.state.brews, (brew)=>{
|
||||
return <a href={brew.url} className='item' key={brew.id} target='_blank'>
|
||||
<span className='title'>{brew.title}</span>
|
||||
<span className='time'>{Moment(brew.ts).fromNow()}</span>
|
||||
</a>
|
||||
});
|
||||
|
||||
return <div className='dropdown'>{items}</div>
|
||||
},
|
||||
|
||||
render : function(){
|
||||
return <Nav.item icon='fa-clock-o' color='grey' className='recent'
|
||||
onMouseEnter={this.handleDropdown.bind(null, true)}
|
||||
onMouseLeave={this.handleDropdown.bind(null, false)}>
|
||||
{this.props.text}
|
||||
{this.renderDropdown()}
|
||||
</Nav.item>
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
module.exports = {
|
||||
viewed : React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
brew : {
|
||||
title : '',
|
||||
shareId : ''
|
||||
}
|
||||
};
|
||||
},
|
||||
render : function(){
|
||||
return <BaseItem text='recently viewed' storageKey={VIEW_KEY}
|
||||
currentBrew={{
|
||||
id : this.props.brew.shareId,
|
||||
title : this.props.brew.title,
|
||||
url : `/share/${this.props.brew.shareId}`
|
||||
}}
|
||||
/>
|
||||
},
|
||||
}),
|
||||
|
||||
edited : React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
brew : {
|
||||
title : '',
|
||||
editId : ''
|
||||
}
|
||||
};
|
||||
},
|
||||
render : function(){
|
||||
return <BaseItem text='recently edited' storageKey={EDIT_KEY}
|
||||
currentBrew={{
|
||||
id : this.props.brew.editId,
|
||||
title : this.props.brew.title,
|
||||
url : `/edit/${this.props.brew.editId}`
|
||||
}}
|
||||
/>
|
||||
},
|
||||
}),
|
||||
|
||||
both : React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
errorId : null
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
showDropdown: false,
|
||||
edit : [],
|
||||
view : []
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
|
||||
var edited = JSON.parse(localStorage.getItem(EDIT_KEY) || '[]');
|
||||
var viewed = JSON.parse(localStorage.getItem(VIEW_KEY) || '[]');
|
||||
|
||||
if(this.props.errorId){
|
||||
edited = _.filter(edited, (edit) => {
|
||||
return edit.id !== this.props.errorId;
|
||||
});
|
||||
viewed = _.filter(viewed, (view) => {
|
||||
return view.id !== this.props.errorId;
|
||||
});
|
||||
|
||||
localStorage.setItem(EDIT_KEY, JSON.stringify(edited));
|
||||
localStorage.setItem(VIEW_KEY, JSON.stringify(viewed));
|
||||
}
|
||||
|
||||
|
||||
this.setState({
|
||||
edit : edited,
|
||||
view : viewed
|
||||
});
|
||||
},
|
||||
|
||||
handleDropdown : function(show){
|
||||
this.setState({
|
||||
showDropdown : show
|
||||
})
|
||||
},
|
||||
|
||||
renderDropdown : function(){
|
||||
if(!this.state.showDropdown) return null;
|
||||
|
||||
var makeItems = (brews) => {
|
||||
return _.map(brews, (brew)=>{
|
||||
return <a href={brew.url} className='item' key={brew.id} target='_blank'>
|
||||
<span className='title'>{brew.title}</span>
|
||||
<span className='time'>{Moment(brew.ts).fromNow()}</span>
|
||||
</a>
|
||||
});
|
||||
};
|
||||
|
||||
return <div className='dropdown'>
|
||||
<h4>edited</h4>
|
||||
{makeItems(this.state.edit)}
|
||||
<h4>viewed</h4>
|
||||
{makeItems(this.state.view)}
|
||||
</div>
|
||||
},
|
||||
|
||||
render : function(){
|
||||
return <Nav.item icon='fa-clock-o' color='grey' className='recent'
|
||||
onMouseEnter={this.handleDropdown.bind(null, true)}
|
||||
onMouseLeave={this.handleDropdown.bind(null, false)}>
|
||||
Recent brews
|
||||
{this.renderDropdown()}
|
||||
</Nav.item>
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
const Moment = require('moment');
|
||||
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
|
||||
const VIEW_KEY = 'homebrewery-recently-viewed';
|
||||
const EDIT_KEY = 'homebrewery-recently-edited';
|
||||
|
||||
const BaseItem = createClass({
|
||||
getDefaultProps : function() {
|
||||
return {
|
||||
storageKey : '',
|
||||
text : '',
|
||||
currentBrew : {
|
||||
title : '',
|
||||
id : '',
|
||||
url : ''
|
||||
}
|
||||
};
|
||||
},
|
||||
getInitialState : function() {
|
||||
return {
|
||||
showDropdown : false,
|
||||
brews : []
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount : function() {
|
||||
let brews = JSON.parse(localStorage.getItem(this.props.storageKey) || '[]');
|
||||
|
||||
brews = _.filter(brews, (brew)=>{
|
||||
return brew.id !== this.props.currentBrew.id;
|
||||
});
|
||||
if(this.props.currentBrew.id){
|
||||
brews.unshift({
|
||||
id : this.props.currentBrew.id,
|
||||
url : this.props.currentBrew.url,
|
||||
title : this.props.currentBrew.title,
|
||||
ts : Date.now()
|
||||
});
|
||||
}
|
||||
brews = _.slice(brews, 0, 8);
|
||||
localStorage.setItem(this.props.storageKey, JSON.stringify(brews));
|
||||
this.setState({
|
||||
brews : brews
|
||||
});
|
||||
},
|
||||
|
||||
handleDropdown : function(show){
|
||||
this.setState({
|
||||
showDropdown : show
|
||||
});
|
||||
},
|
||||
|
||||
renderDropdown : function(){
|
||||
if(!this.state.showDropdown) return null;
|
||||
|
||||
const items = _.map(this.state.brews, (brew)=>{
|
||||
return <a href={brew.url} className='item' key={brew.id} target='_blank' rel='noopener noreferrer'>
|
||||
<span className='title'>{brew.title}</span>
|
||||
<span className='time'>{Moment(brew.ts).fromNow()}</span>
|
||||
</a>;
|
||||
});
|
||||
|
||||
return <div className='dropdown'>{items}</div>;
|
||||
},
|
||||
|
||||
render : function(){
|
||||
return <Nav.item icon='fa-clock-o' color='grey' className='recent'
|
||||
onMouseEnter={()=>this.handleDropdown(true)}
|
||||
onMouseLeave={()=>this.handleDropdown(false)}>
|
||||
{this.props.text}
|
||||
{this.renderDropdown()}
|
||||
</Nav.item>;
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
module.exports = {
|
||||
viewed : createClass({
|
||||
getDefaultProps : function() {
|
||||
return {
|
||||
brew : {
|
||||
title : '',
|
||||
shareId : ''
|
||||
}
|
||||
};
|
||||
},
|
||||
render : function(){
|
||||
return <BaseItem text='recently viewed' storageKey={VIEW_KEY}
|
||||
currentBrew={{
|
||||
id : this.props.brew.shareId,
|
||||
title : this.props.brew.title,
|
||||
url : `/share/${this.props.brew.shareId}`
|
||||
}}
|
||||
/>;
|
||||
},
|
||||
}),
|
||||
|
||||
edited : createClass({
|
||||
getDefaultProps : function() {
|
||||
return {
|
||||
brew : {
|
||||
title : '',
|
||||
editId : ''
|
||||
}
|
||||
};
|
||||
},
|
||||
render : function(){
|
||||
return <BaseItem text='recently edited' storageKey={EDIT_KEY}
|
||||
currentBrew={{
|
||||
id : this.props.brew.editId,
|
||||
title : this.props.brew.title,
|
||||
url : `/edit/${this.props.brew.editId}`
|
||||
}}
|
||||
/>;
|
||||
},
|
||||
}),
|
||||
|
||||
both : createClass({
|
||||
getDefaultProps : function() {
|
||||
return {
|
||||
errorId : null
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState : function() {
|
||||
return {
|
||||
showDropdown : false,
|
||||
edit : [],
|
||||
view : []
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount : function() {
|
||||
|
||||
let edited = JSON.parse(localStorage.getItem(EDIT_KEY) || '[]');
|
||||
let viewed = JSON.parse(localStorage.getItem(VIEW_KEY) || '[]');
|
||||
|
||||
if(this.props.errorId){
|
||||
edited = _.filter(edited, (edit)=>{
|
||||
return edit.id !== this.props.errorId;
|
||||
});
|
||||
viewed = _.filter(viewed, (view)=>{
|
||||
return view.id !== this.props.errorId;
|
||||
});
|
||||
|
||||
localStorage.setItem(EDIT_KEY, JSON.stringify(edited));
|
||||
localStorage.setItem(VIEW_KEY, JSON.stringify(viewed));
|
||||
}
|
||||
|
||||
|
||||
this.setState({
|
||||
edit : edited,
|
||||
view : viewed
|
||||
});
|
||||
},
|
||||
|
||||
handleDropdown : function(show){
|
||||
this.setState({
|
||||
showDropdown : show
|
||||
});
|
||||
},
|
||||
|
||||
renderDropdown : function(){
|
||||
if(!this.state.showDropdown) return null;
|
||||
|
||||
const makeItems = (brews)=>{
|
||||
return _.map(brews, (brew)=>{
|
||||
return <a href={brew.url} className='item' key={brew.id} target='_blank' rel='noopener noreferrer'>
|
||||
<span className='title'>{brew.title}</span>
|
||||
<span className='time'>{Moment(brew.ts).fromNow()}</span>
|
||||
</a>;
|
||||
});
|
||||
};
|
||||
|
||||
return <div className='dropdown'>
|
||||
<h4>edited</h4>
|
||||
{makeItems(this.state.edit)}
|
||||
<h4>viewed</h4>
|
||||
{makeItems(this.state.view)}
|
||||
</div>;
|
||||
},
|
||||
|
||||
render : function(){
|
||||
return <Nav.item icon='fa-clock-o' color='grey' className='recent'
|
||||
onMouseEnter={()=>this.handleDropdown(true)}
|
||||
onMouseLeave={()=>this.handleDropdown(false)}>
|
||||
Recent brews
|
||||
{this.renderDropdown()}
|
||||
</Nav.item>;
|
||||
}
|
||||
|
||||
})
|
||||
};
|
||||
@@ -1,51 +1,52 @@
|
||||
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>
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
|
||||
//var striptags = require('striptags');
|
||||
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
|
||||
const MAX_URL_SIZE = 2083;
|
||||
const MAIN_URL = 'https://www.reddit.com/r/UnearthedArcana/submit?selftext=true';
|
||||
|
||||
|
||||
const RedditShare = createClass({
|
||||
getDefaultProps : function() {
|
||||
return {
|
||||
brew : {
|
||||
title : '',
|
||||
sharedId : '',
|
||||
text : ''
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
getText : function(){
|
||||
|
||||
},
|
||||
|
||||
|
||||
handleClick : function(){
|
||||
const 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;
|
||||
@@ -1,230 +1,222 @@
|
||||
const React = require('react');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
const request = require("superagent");
|
||||
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
const Navbar = require('../../navbar/navbar.jsx');
|
||||
|
||||
const ReportIssue = require('../../navbar/issue.navitem.jsx');
|
||||
const PrintLink = require('../../navbar/print.navitem.jsx');
|
||||
const Account = require('../../navbar/account.navitem.jsx');
|
||||
//const RecentlyEdited = require('../../navbar/recent.navitem.jsx').edited;
|
||||
|
||||
const SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
|
||||
const Editor = require('../../editor/editor.jsx');
|
||||
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
||||
|
||||
const Markdown = require('naturalcrit/markdown.js');
|
||||
|
||||
const SAVE_TIMEOUT = 3000;
|
||||
|
||||
|
||||
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(){
|
||||
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);
|
||||
},
|
||||
componentWillUnmount: function() {
|
||||
window.onbeforeunload = function(){};
|
||||
document.removeEventListener('keydown', this.handleControlKeys);
|
||||
},
|
||||
|
||||
|
||||
handleControlKeys : function(e){
|
||||
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(){
|
||||
return <div className='editPage page'>
|
||||
{this.renderNavbar()}
|
||||
|
||||
<div className='content'>
|
||||
<SplitPane onDragFinish={this.handleSplitMove} ref='pane'>
|
||||
<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>
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = EditPage;
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
const request = require('superagent');
|
||||
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
const Navbar = require('../../navbar/navbar.jsx');
|
||||
|
||||
const ReportIssue = require('../../navbar/issue.navitem.jsx');
|
||||
const PrintLink = require('../../navbar/print.navitem.jsx');
|
||||
const Account = require('../../navbar/account.navitem.jsx');
|
||||
//const RecentlyEdited = require('../../navbar/recent.navitem.jsx').edited;
|
||||
|
||||
const SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
|
||||
const Editor = require('../../editor/editor.jsx');
|
||||
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
||||
|
||||
const Markdown = require('naturalcrit/markdown.js');
|
||||
|
||||
const SAVE_TIMEOUT = 3000;
|
||||
|
||||
|
||||
const EditPage = 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),
|
||||
};
|
||||
},
|
||||
savedBrew : null,
|
||||
|
||||
componentDidMount : function(){
|
||||
this.trySave();
|
||||
window.onbeforeunload = ()=>{
|
||||
if(this.state.isSaving || this.state.isPending){
|
||||
return 'You have unsaved changes!';
|
||||
}
|
||||
};
|
||||
|
||||
this.setState((prevState)=>({
|
||||
htmlErrors : Markdown.validate(prevState.brew.text)
|
||||
}));
|
||||
|
||||
document.addEventListener('keydown', this.handleControlKeys);
|
||||
},
|
||||
componentWillUnmount : function() {
|
||||
window.onbeforeunload = function(){};
|
||||
document.removeEventListener('keydown', this.handleControlKeys);
|
||||
},
|
||||
|
||||
|
||||
handleControlKeys : function(e){
|
||||
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((prevState)=>({
|
||||
brew : _.merge({}, prevState.brew, metadata),
|
||||
isPending : true,
|
||||
}), ()=>this.trySave());
|
||||
|
||||
},
|
||||
|
||||
handleTextChange : function(text){
|
||||
|
||||
//If there are errors, run the validator on everychange to give quick feedback
|
||||
let htmlErrors = this.state.htmlErrors;
|
||||
if(htmlErrors.length) htmlErrors = Markdown.validate(text);
|
||||
|
||||
this.setState((prevState)=>({
|
||||
brew : _.merge({}, prevState.brew, { text: text }),
|
||||
isPending : true,
|
||||
htmlErrors : htmlErrors
|
||||
}), ()=>this.trySave());
|
||||
},
|
||||
|
||||
hasChanges : function(){
|
||||
const savedBrew = this.savedBrew ? this.savedBrew : this.props.brew;
|
||||
return !_.isEqual(this.state.brew, savedBrew);
|
||||
},
|
||||
|
||||
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((prevState)=>({
|
||||
isSaving : true,
|
||||
errors : null,
|
||||
htmlErrors : Markdown.validate(prevState.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,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
renderSaveButton : function(){
|
||||
if(this.state.errors){
|
||||
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='save error' icon='fa-warning'>
|
||||
Oops!
|
||||
<div className='errorContainer'>
|
||||
Looks like there was a problem saving. <br />
|
||||
Report the issue <a target='_blank' rel='noopener noreferrer'
|
||||
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(){
|
||||
return <div className='editPage page'>
|
||||
{this.renderNavbar()}
|
||||
|
||||
<div className='content'>
|
||||
<SplitPane onDragFinish={this.handleSplitMove} ref='pane'>
|
||||
<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>;
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = EditPage;
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
var React = require('react');
|
||||
var _ = require('lodash');
|
||||
var cx = require('classnames');
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
|
||||
var Nav = require('naturalcrit/nav/nav.jsx');
|
||||
var Navbar = require('../../navbar/navbar.jsx');
|
||||
var PatreonNavItem = require('../../navbar/patreon.navitem.jsx');
|
||||
var IssueNavItem = require('../../navbar/issue.navitem.jsx');
|
||||
var RecentNavItem = require('../../navbar/recent.navitem.jsx');
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
const Navbar = require('../../navbar/navbar.jsx');
|
||||
const PatreonNavItem = require('../../navbar/patreon.navitem.jsx');
|
||||
const IssueNavItem = require('../../navbar/issue.navitem.jsx');
|
||||
const RecentNavItem = require('../../navbar/recent.navitem.jsx');
|
||||
|
||||
var BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
||||
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
||||
|
||||
var ErrorPage = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
const ErrorPage = createClass({
|
||||
getDefaultProps : function() {
|
||||
return {
|
||||
ver : '0.0.0',
|
||||
errorId: ''
|
||||
ver : '0.0.0',
|
||||
errorId : ''
|
||||
};
|
||||
},
|
||||
|
||||
@@ -39,7 +40,7 @@ var ErrorPage = React.createClass({
|
||||
<div className='content'>
|
||||
<BrewRenderer text={this.text} />
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ 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');
|
||||
const win = window.open(`/homebrew/print/${shareId}?dialog=true`, '_blank');
|
||||
win.focus();
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
@@ -1,92 +1,95 @@
|
||||
const React = require('react');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
const request = require("superagent");
|
||||
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
const Navbar = require('../../navbar/navbar.jsx');
|
||||
const PatreonNavItem = require('../../navbar/patreon.navitem.jsx');
|
||||
const IssueNavItem = require('../../navbar/issue.navitem.jsx');
|
||||
const RecentNavItem = require('../../navbar/recent.navitem.jsx');
|
||||
const AccountNavItem = require('../../navbar/account.navitem.jsx');
|
||||
|
||||
|
||||
const SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
|
||||
const Editor = require('../../editor/editor.jsx');
|
||||
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
||||
|
||||
|
||||
|
||||
const HomePage = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
welcomeText : '',
|
||||
ver : '0.0.0'
|
||||
};
|
||||
},
|
||||
getInitialState: function() {
|
||||
return {
|
||||
text: this.props.welcomeText
|
||||
};
|
||||
},
|
||||
handleSave : function(){
|
||||
request.post('/api')
|
||||
.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(){
|
||||
return <Navbar ver={this.props.ver}>
|
||||
<Nav.section>
|
||||
<PatreonNavItem />
|
||||
<IssueNavItem />
|
||||
<Nav.item newTab={true} href='/changelog' color='purple' icon='fa-file-text-o'>
|
||||
Changelog
|
||||
</Nav.item>
|
||||
<RecentNavItem.both />
|
||||
<AccountNavItem />
|
||||
{/*}
|
||||
<Nav.item href='/new' color='green' icon='fa-external-link'>
|
||||
New Brew
|
||||
</Nav.item>
|
||||
*/}
|
||||
</Nav.section>
|
||||
</Navbar>
|
||||
},
|
||||
|
||||
render : function(){
|
||||
return <div className='homePage page'>
|
||||
{this.renderNavbar()}
|
||||
|
||||
<div className='content'>
|
||||
<SplitPane onDragFinish={this.handleSplitMove} ref='pane'>
|
||||
<Editor value={this.state.text} onChange={this.handleTextChange} ref='editor'/>
|
||||
<BrewRenderer text={this.state.text} />
|
||||
</SplitPane>
|
||||
</div>
|
||||
|
||||
<div className={cx('floatingSaveButton', {show : this.props.welcomeText != this.state.text})} onClick={this.handleSave}>
|
||||
Save current <i className='fa fa-save' />
|
||||
</div>
|
||||
|
||||
<a href='/new' className='floatingNewButton'>
|
||||
Create your own <i className='fa fa-magic' />
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = HomePage;
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
const request = require('superagent');
|
||||
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
const Navbar = require('../../navbar/navbar.jsx');
|
||||
const PatreonNavItem = require('../../navbar/patreon.navitem.jsx');
|
||||
const IssueNavItem = require('../../navbar/issue.navitem.jsx');
|
||||
const RecentNavItem = require('../../navbar/recent.navitem.jsx');
|
||||
const AccountNavItem = require('../../navbar/account.navitem.jsx');
|
||||
|
||||
|
||||
const SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
|
||||
const Editor = require('../../editor/editor.jsx');
|
||||
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
||||
|
||||
|
||||
|
||||
const HomePage = createClass({
|
||||
getDefaultProps : function() {
|
||||
return {
|
||||
welcomeText : '',
|
||||
ver : '0.0.0'
|
||||
};
|
||||
|
||||
|
||||
},
|
||||
getInitialState : function() {
|
||||
return {
|
||||
text : this.props.welcomeText
|
||||
};
|
||||
},
|
||||
handleSave : function(){
|
||||
request.post('/api')
|
||||
.send({
|
||||
text : this.state.text
|
||||
})
|
||||
.end((err, res)=>{
|
||||
if(err) return;
|
||||
const brew = res.body;
|
||||
window.location = `/edit/${brew.editId}`;
|
||||
});
|
||||
},
|
||||
handleSplitMove : function(){
|
||||
this.refs.editor.update();
|
||||
},
|
||||
handleTextChange : function(text){
|
||||
this.setState({
|
||||
text : text
|
||||
});
|
||||
},
|
||||
renderNavbar : function(){
|
||||
return <Navbar ver={this.props.ver}>
|
||||
<Nav.section>
|
||||
<PatreonNavItem />
|
||||
<IssueNavItem />
|
||||
<Nav.item newTab={true} href='/changelog' color='purple' icon='fa-file-text-o'>
|
||||
Changelog
|
||||
</Nav.item>
|
||||
<RecentNavItem.both />
|
||||
<AccountNavItem />
|
||||
{/*}
|
||||
<Nav.item href='/new' color='green' icon='fa-external-link'>
|
||||
New Brew
|
||||
</Nav.item>
|
||||
*/}
|
||||
</Nav.section>
|
||||
</Navbar>;
|
||||
},
|
||||
|
||||
render : function(){
|
||||
return <div className='homePage page'>
|
||||
{this.renderNavbar()}
|
||||
|
||||
<div className='content'>
|
||||
<SplitPane onDragFinish={this.handleSplitMove} ref='pane'>
|
||||
<Editor value={this.state.text} onChange={this.handleTextChange} ref='editor'/>
|
||||
<BrewRenderer text={this.state.text} />
|
||||
</SplitPane>
|
||||
</div>
|
||||
|
||||
<div className={cx('floatingSaveButton', { show: this.props.welcomeText != this.state.text })} onClick={this.handleSave}>
|
||||
Save current <i className='fa fa-save' />
|
||||
</div>
|
||||
|
||||
<a href='/new' className='floatingNewButton'>
|
||||
Create your own <i className='fa fa-magic' />
|
||||
</a>
|
||||
</div>;
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = HomePage;
|
||||
|
||||
@@ -55,7 +55,7 @@ If you are looking for more 5e Homebrew resources check out [r/UnearthedArcana](
|
||||
|
||||
|
||||
|
||||
<img src='http://i.imgur.com/hMna6G0.png' style='position:absolute;bottom:50px;right:30px;width:280px' />
|
||||
<img src='https://i.imgur.com/hMna6G0.png' style='position:absolute;bottom:50px;right:30px;width:280px' />
|
||||
|
||||
<div class='pageNumber'>1</div>
|
||||
<div class='footnote'>PART 1 | FANCINESS</div>
|
||||
|
||||
@@ -1,161 +1,162 @@
|
||||
const React = require('react');
|
||||
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 Navbar = require('../../navbar/navbar.jsx');
|
||||
const AccountNavItem = require('../../navbar/account.navitem.jsx');
|
||||
const IssueNavItem = require('../../navbar/issue.navitem.jsx');
|
||||
|
||||
const SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
|
||||
const Editor = require('../../editor/editor.jsx');
|
||||
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
||||
|
||||
|
||||
const KEY = 'homebrewery-new';
|
||||
|
||||
const NewPage = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
metadata : {
|
||||
title : '',
|
||||
description : '',
|
||||
tags : '',
|
||||
published : false,
|
||||
authors : [],
|
||||
systems : []
|
||||
},
|
||||
|
||||
text: '',
|
||||
isSaving : false,
|
||||
errors : []
|
||||
};
|
||||
},
|
||||
componentDidMount: function() {
|
||||
const storage = localStorage.getItem(KEY);
|
||||
if(storage){
|
||||
this.setState({
|
||||
text : storage
|
||||
})
|
||||
}
|
||||
document.addEventListener('keydown', this.handleControlKeys);
|
||||
},
|
||||
componentWillUnmount: function() {
|
||||
document.removeEventListener('keydown', this.handleControlKeys);
|
||||
},
|
||||
|
||||
handleControlKeys : function(e){
|
||||
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) 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>
|
||||
},
|
||||
|
||||
render : function(){
|
||||
return <div className='newPage page'>
|
||||
{this.renderNavbar()}
|
||||
<div className='content'>
|
||||
<SplitPane onDragFinish={this.handleSplitMove} ref='pane'>
|
||||
<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>
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = NewPage;
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
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 Navbar = require('../../navbar/navbar.jsx');
|
||||
const AccountNavItem = require('../../navbar/account.navitem.jsx');
|
||||
const IssueNavItem = require('../../navbar/issue.navitem.jsx');
|
||||
|
||||
const SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
|
||||
const Editor = require('../../editor/editor.jsx');
|
||||
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
||||
|
||||
|
||||
const KEY = 'homebrewery-new';
|
||||
|
||||
const NewPage = createClass({
|
||||
getInitialState : function() {
|
||||
return {
|
||||
metadata : {
|
||||
title : '',
|
||||
description : '',
|
||||
tags : '',
|
||||
published : false,
|
||||
authors : [],
|
||||
systems : []
|
||||
},
|
||||
|
||||
text : '',
|
||||
isSaving : false,
|
||||
errors : []
|
||||
};
|
||||
},
|
||||
componentDidMount : function() {
|
||||
const storage = localStorage.getItem(KEY);
|
||||
if(storage){
|
||||
this.setState({
|
||||
text : storage
|
||||
});
|
||||
}
|
||||
document.addEventListener('keydown', this.handleControlKeys);
|
||||
},
|
||||
componentWillUnmount : function() {
|
||||
document.removeEventListener('keydown', this.handleControlKeys);
|
||||
},
|
||||
|
||||
handleControlKeys : function(e){
|
||||
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) 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>;
|
||||
},
|
||||
|
||||
render : function(){
|
||||
return <div className='newPage page'>
|
||||
{this.renderNavbar()}
|
||||
<div className='content'>
|
||||
<SplitPane onDragFinish={this.handleSplitMove} ref='pane'>
|
||||
<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>;
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = NewPage;
|
||||
|
||||
@@ -1,38 +1,41 @@
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
const Markdown = require('naturalcrit/markdown.js');
|
||||
|
||||
const PrintPage = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
const PrintPage = createClass({
|
||||
getDefaultProps : function() {
|
||||
return {
|
||||
query : {},
|
||||
brew : {
|
||||
brew : {
|
||||
text : '',
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
getInitialState : function() {
|
||||
return {
|
||||
brewText: this.props.brew.text
|
||||
brewText : this.props.brew.text
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
componentDidMount : function() {
|
||||
if(this.props.query.local){
|
||||
this.setState({ brewText : localStorage.getItem(this.props.query.local)});
|
||||
this.setState((prevState, prevProps)=>({
|
||||
brewText : localStorage.getItem(prevProps.query.local)
|
||||
}));
|
||||
}
|
||||
|
||||
if(this.props.query.dialog) window.print();
|
||||
},
|
||||
|
||||
renderPages : function(){
|
||||
return _.map(this.state.brewText.split('\\page'), (page, index) => {
|
||||
return _.map(this.state.brewText.split('\\page'), (page, index)=>{
|
||||
return <div
|
||||
className='phb'
|
||||
id={`p${index + 1}`}
|
||||
dangerouslySetInnerHTML={{__html:Markdown.render(page)}}
|
||||
dangerouslySetInnerHTML={{ __html: Markdown.render(page) }}
|
||||
key={index} />;
|
||||
});
|
||||
},
|
||||
@@ -40,7 +43,7 @@ const PrintPage = React.createClass({
|
||||
render : function(){
|
||||
return <div>
|
||||
{this.renderPages()}
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,71 +1,72 @@
|
||||
const React = require('react');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
const Navbar = require('../../navbar/navbar.jsx');
|
||||
const PrintLink = require('../../navbar/print.navitem.jsx');
|
||||
const ReportIssue = require('../../navbar/issue.navitem.jsx');
|
||||
//const RecentlyViewed = require('../../navbar/recent.navitem.jsx').viewed;
|
||||
const Account = require('../../navbar/account.navitem.jsx');
|
||||
|
||||
|
||||
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
||||
|
||||
|
||||
const SharePage = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
brew : {
|
||||
title : '',
|
||||
text : '',
|
||||
shareId : null,
|
||||
createdAt : null,
|
||||
updatedAt : null,
|
||||
views : 0
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
document.addEventListener('keydown', this.handleControlKeys);
|
||||
},
|
||||
componentWillUnmount: function() {
|
||||
document.removeEventListener('keydown', this.handleControlKeys);
|
||||
},
|
||||
handleControlKeys : function(e){
|
||||
if(!(e.ctrlKey || e.metaKey)) return;
|
||||
const P_KEY = 80;
|
||||
if(e.keyCode == P_KEY){
|
||||
window.open(`/print/${this.props.brew.shareId}?dialog=true`, '_blank').focus();
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
},
|
||||
|
||||
render : function(){
|
||||
return <div className='sharePage page'>
|
||||
<Navbar>
|
||||
<Nav.section>
|
||||
<Nav.item className='brewTitle'>{this.props.brew.title}</Nav.item>
|
||||
</Nav.section>
|
||||
|
||||
<Nav.section>
|
||||
<ReportIssue />
|
||||
{/*<RecentlyViewed brew={this.props.brew} />*/}
|
||||
<PrintLink shareId={this.props.brew.shareId} />
|
||||
<Nav.item href={'/source/' + this.props.brew.shareId} color='teal' icon='fa-code'>
|
||||
source
|
||||
</Nav.item>
|
||||
<Account />
|
||||
</Nav.section>
|
||||
</Navbar>
|
||||
|
||||
<div className='content'>
|
||||
<BrewRenderer text={this.props.brew.text} />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = SharePage;
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
const Navbar = require('../../navbar/navbar.jsx');
|
||||
const PrintLink = require('../../navbar/print.navitem.jsx');
|
||||
const ReportIssue = require('../../navbar/issue.navitem.jsx');
|
||||
//const RecentlyViewed = require('../../navbar/recent.navitem.jsx').viewed;
|
||||
const Account = require('../../navbar/account.navitem.jsx');
|
||||
|
||||
|
||||
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
||||
|
||||
|
||||
const SharePage = createClass({
|
||||
getDefaultProps : function() {
|
||||
return {
|
||||
brew : {
|
||||
title : '',
|
||||
text : '',
|
||||
shareId : null,
|
||||
createdAt : null,
|
||||
updatedAt : null,
|
||||
views : 0
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount : function() {
|
||||
document.addEventListener('keydown', this.handleControlKeys);
|
||||
},
|
||||
componentWillUnmount : function() {
|
||||
document.removeEventListener('keydown', this.handleControlKeys);
|
||||
},
|
||||
handleControlKeys : function(e){
|
||||
if(!(e.ctrlKey || e.metaKey)) return;
|
||||
const P_KEY = 80;
|
||||
if(e.keyCode == P_KEY){
|
||||
window.open(`/print/${this.props.brew.shareId}?dialog=true`, '_blank').focus();
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
},
|
||||
|
||||
render : function(){
|
||||
return <div className='sharePage page'>
|
||||
<Navbar>
|
||||
<Nav.section>
|
||||
<Nav.item className='brewTitle'>{this.props.brew.title}</Nav.item>
|
||||
</Nav.section>
|
||||
|
||||
<Nav.section>
|
||||
<ReportIssue />
|
||||
{/*<RecentlyViewed brew={this.props.brew} />*/}
|
||||
<PrintLink shareId={this.props.brew.shareId} />
|
||||
<Nav.item href={`/source/${this.props.brew.shareId}`} color='teal' icon='fa-code'>
|
||||
source
|
||||
</Nav.item>
|
||||
<Account />
|
||||
</Nav.section>
|
||||
</Navbar>
|
||||
|
||||
<div className='content'>
|
||||
<BrewRenderer text={this.props.brew.text} />
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = SharePage;
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
const moment = require('moment');
|
||||
const request = require("superagent");
|
||||
const request = require('superagent');
|
||||
|
||||
const BrewItem = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
const BrewItem = createClass({
|
||||
getDefaultProps : function() {
|
||||
return {
|
||||
brew : {
|
||||
title : '',
|
||||
title : '',
|
||||
description : '',
|
||||
|
||||
authors : []
|
||||
@@ -17,29 +18,29 @@ 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;
|
||||
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)
|
||||
request.get(`/api/remove/${this.props.brew.editId}`)
|
||||
.send()
|
||||
.end(function(err, res){
|
||||
location.reload();
|
||||
});
|
||||
},
|
||||
|
||||
renderDeleteBrewLink: function(){
|
||||
renderDeleteBrewLink : function(){
|
||||
if(!this.props.brew.editId) return;
|
||||
|
||||
return <a onClick={this.deleteBrew}>
|
||||
<i className='fa fa-trash' />
|
||||
</a>
|
||||
</a>;
|
||||
},
|
||||
renderEditLink: function(){
|
||||
renderEditLink : function(){
|
||||
if(!this.props.brew.editId) return;
|
||||
|
||||
return <a href={`/edit/${this.props.brew.editId}`} target='_blank'>
|
||||
return <a href={`/edit/${this.props.brew.editId}`} target='_blank' rel='noopener noreferrer'>
|
||||
<i className='fa fa-pencil' />
|
||||
</a>
|
||||
</a>;
|
||||
},
|
||||
|
||||
render : function(){
|
||||
@@ -62,13 +63,13 @@ const BrewItem = React.createClass({
|
||||
</div>
|
||||
|
||||
<div className='links'>
|
||||
<a href={`/share/${brew.shareId}`} target='_blank'>
|
||||
<a href={`/share/${brew.shareId}`} target='_blank' rel='noopener noreferrer'>
|
||||
<i className='fa fa-share-alt' />
|
||||
</a>
|
||||
{this.renderEditLink()}
|
||||
{this.renderDeleteBrewLink()}
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
|
||||
@@ -9,19 +10,19 @@ const RecentNavItem = require('../../navbar/recent.navitem.jsx');
|
||||
const Account = require('../../navbar/account.navitem.jsx');
|
||||
const BrewItem = require('./brewItem/brewItem.jsx');
|
||||
|
||||
const brew = {
|
||||
title : 'SUPER Long title woah now',
|
||||
authors : []
|
||||
}
|
||||
// const brew = {
|
||||
// title : 'SUPER Long title woah now',
|
||||
// authors : []
|
||||
// };
|
||||
|
||||
const BREWS = _.times(25, ()=>{ return brew});
|
||||
//const BREWS = _.times(25, ()=>{ return brew;});
|
||||
|
||||
|
||||
const UserPage = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
const UserPage = createClass({
|
||||
getDefaultProps : function() {
|
||||
return {
|
||||
username : '',
|
||||
brews : []
|
||||
brews : []
|
||||
};
|
||||
},
|
||||
|
||||
@@ -30,14 +31,14 @@ const UserPage = React.createClass({
|
||||
|
||||
const sortedBrews = _.sortBy(brews, (brew)=>{ return brew.title; });
|
||||
|
||||
return _.map(sortedBrews, (brew, idx) => {
|
||||
return <BrewItem brew={brew} key={idx}/>
|
||||
return _.map(sortedBrews, (brew, idx)=>{
|
||||
return <BrewItem brew={brew} key={idx}/>;
|
||||
});
|
||||
},
|
||||
|
||||
getSortedBrews : function(){
|
||||
return _.groupBy(this.props.brews, (brew)=>{
|
||||
return (brew.published ? 'published' : 'private')
|
||||
return (brew.published ? 'published' : 'private');
|
||||
});
|
||||
},
|
||||
|
||||
@@ -68,7 +69,7 @@ const UserPage = React.createClass({
|
||||
{this.renderPrivateBrews(brews.private)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -156,6 +156,7 @@ body {
|
||||
margin-bottom : 1em;
|
||||
font-size : 10pt;
|
||||
thead{
|
||||
display: table-row-group;
|
||||
font-weight : 800;
|
||||
th{
|
||||
vertical-align : bottom;
|
||||
@@ -337,7 +338,8 @@ body {
|
||||
p,blockquote,table{
|
||||
z-index : 15;
|
||||
-webkit-column-break-inside : avoid;
|
||||
column-break-inside : avoid;
|
||||
page-break-inside : avoid;
|
||||
break-inside : avoid;
|
||||
overflow: hidden; /* Firefox fix */
|
||||
}
|
||||
//Better spacing for spell blocks
|
||||
@@ -355,7 +357,8 @@ body {
|
||||
}
|
||||
li{
|
||||
-webkit-column-break-inside : avoid;
|
||||
column-break-inside : avoid;
|
||||
page-break-inside : avoid;
|
||||
break-inside : avoid;
|
||||
}
|
||||
}
|
||||
//*****************************
|
||||
@@ -380,20 +383,8 @@ body {
|
||||
text-indent : -1em;
|
||||
list-style-type : none;
|
||||
-webkit-column-break-inside : auto;
|
||||
column-break-inside : auto;
|
||||
}
|
||||
}
|
||||
//*****************************
|
||||
// * PRINT
|
||||
// *****************************/
|
||||
.phb.print{
|
||||
blockquote{
|
||||
box-shadow : none;
|
||||
}
|
||||
}
|
||||
@media print {
|
||||
.phb .descriptive, .phb blockquote{
|
||||
box-shadow : none;
|
||||
page-break-inside : auto;
|
||||
break-inside : auto;
|
||||
}
|
||||
}
|
||||
//*****************************
|
||||
@@ -462,7 +453,8 @@ body {
|
||||
// *****************************/
|
||||
.phb .toc{
|
||||
-webkit-column-break-inside : avoid;
|
||||
column-break-inside : avoid;
|
||||
page-break-inside : avoid;
|
||||
break-inside : avoid;
|
||||
a{
|
||||
color : black;
|
||||
text-decoration : none;
|
||||
@@ -477,4 +469,4 @@ body {
|
||||
&>ul>li{
|
||||
margin-bottom : 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
module.exports = function(vitreum){
|
||||
return `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<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 rel="icon" href="/assets/homebrew/favicon.ico" type="image/x-icon" />
|
||||
<title>The Homebrewery - NaturalCrit</title>
|
||||
${vitreum.head}
|
||||
</head>
|
||||
<body>
|
||||
<main id="reactRoot">${vitreum.body}</main>
|
||||
</body>
|
||||
${vitreum.js}
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
|
||||
module.exports = function(vitreum){
|
||||
return `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<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 rel="icon" href="/assets/homebrew/favicon.ico" type="image/x-icon" />
|
||||
<title>The Homebrewery - NaturalCrit</title>
|
||||
${vitreum.head}
|
||||
</head>
|
||||
<body>
|
||||
<main id="reactRoot">${vitreum.body}</main>
|
||||
</body>
|
||||
${vitreum.js}
|
||||
</html>
|
||||
`;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
45
contributing.md
Normal file
45
contributing.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Contributing to Homebrewery
|
||||
|
||||
## How can I contribute?
|
||||
|
||||
### Improve documentation
|
||||
|
||||
As a user of Homebrewery you're the perfect candidate to help us improve our documentation. Typo corrections, error fixes, better explanations, more examples, etc. Open issues for things that could be improved. Anything. Even improvements to this document.
|
||||
|
||||
|
||||
### Improve issues
|
||||
|
||||
Some issues are created with missing information, not reproducible, or plain invalid. Help make them easier to resolve. Handling issues takes a lot of time that we could rather spend on fixing bugs and adding features.
|
||||
|
||||
|
||||
### Write code
|
||||
|
||||
You can use issue labels to discover issues you could help out with:
|
||||
|
||||
* [`blocked` issues](https://github.com/stolksdorf/homebrewery/labels/blocked) need help getting unstuck
|
||||
* [`bug` issues](https://github.com/stolksdorf/homebrewery/labels/bug) are known bugs we'd like to fix
|
||||
* [`feature` issues](https://github.com/stolksdorf/homebrewery/labels/feature) are features we're open to including
|
||||
* [`help wanted`](https://github.com/stolksdorf/homebrewery/labels/help%20wanted) labels are especially useful.
|
||||
|
||||
If you're updating dependencies, please make sure you use npm@5.6.0 and commit the updated `package-lock.json` file.
|
||||
|
||||
You can also refer to the [Development Roadmap on Trello](https://trello.com/b/q6kE29F8/development-roadmap)
|
||||
|
||||
|
||||
## Submitting an issue
|
||||
|
||||
- The issue tracker is for issues. Use the [subreddit](https://www.reddit.com/r/homebrewery/) for support.
|
||||
- Search the issue tracker before opening an issue.
|
||||
- Use a clear and descriptive title.
|
||||
- Include as much information as possible: Steps to reproduce the issue, error message, browser type and version, etc.
|
||||
|
||||
|
||||
## Submitting a pull request
|
||||
|
||||
- Non-trivial changes are often best discussed in an issue first, to prevent you from doing unnecessary work.
|
||||
- For ambitious tasks, you should try to get your work in front of the community for feedback as soon as possible. Open a pull request as soon as you have done the minimum needed to demonstrate your idea. At this early stage, don't worry about making things perfect, or 100% complete. Add a [WIP] prefix to the title, and describe what you still need to do. This lets reviewers know not to nit-pick small details or point out improvements you already know you need to make.
|
||||
- New features should be accompanied with tests and documentation if applicable.
|
||||
- Lint and test before submitting the pull request by running `$ npm run verify`.
|
||||
- If your code is not passing Linting checks due to a non-fixable warning, and you feel it's valid (eg. we lint on a file being too long, but sometimes a file just _has_ to be long), add `/* eslint-disable [rule-name] */` to the top of the file. Be sure to justfiy your lint override in your PR description.
|
||||
- Use a clear and descriptive title for the pull request and commits.
|
||||
- You might be asked to do changes to your pull request. There's never a need to open another pull request. [Just update the existing one.](https://github.com/RichardLitt/knowledge/blob/master/github/amending-a-commit-guide.md)
|
||||
17
docker-compose.yml
Normal file
17
docker-compose.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
version: '2'
|
||||
services:
|
||||
mongodb:
|
||||
image: mongo:latest
|
||||
volumes:
|
||||
- mongodata:/data/db
|
||||
homebrewery:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
image: homebrewery
|
||||
environment:
|
||||
MONGODB_URI: mongodb://mongodb/homebrewery
|
||||
ports:
|
||||
- "8000:8000"
|
||||
volumes:
|
||||
mongodata:
|
||||
8118
package-lock.json
generated
Normal file
8118
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
51
package.json
51
package.json
@@ -1,39 +1,66 @@
|
||||
{
|
||||
"name": "homebrewery",
|
||||
"description": "Create authentic looking D&D homebrews using only markdown",
|
||||
"version": "2.7.5",
|
||||
"version": "2.8.1",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/stolksdorf/homebrewery.git"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "node scripts/dev.js",
|
||||
"quick": "node scripts/quick.js",
|
||||
"build": "node scripts/build.js",
|
||||
"lint": "eslint --fix **/*.{js,jsx}",
|
||||
"lint:dry": "eslint **/*.{js,jsx}",
|
||||
"circleci": "npm test && eslint **/*.{js,jsx} --max-warnings=0",
|
||||
"verify": "npm run lint && npm test",
|
||||
"test": "pico-check",
|
||||
"test:dev": "pico-check -v -w",
|
||||
"phb": "node scripts/phb.js",
|
||||
"prod": "set NODE_ENV=production&& npm run build",
|
||||
"prod": "set NODE_ENV=production && npm run build",
|
||||
"postinstall": "npm run build",
|
||||
"start": "node server.js"
|
||||
},
|
||||
"author": "stolksdorf",
|
||||
"license": "MIT",
|
||||
"eslintIgnore": [
|
||||
"build/*"
|
||||
],
|
||||
"pico-check": {
|
||||
"require": "./tests/test.init.js"
|
||||
},
|
||||
"babel": {
|
||||
"presets": [
|
||||
"env",
|
||||
"react"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"babel-preset-env": "^1.1.8",
|
||||
"basic-auth": "^1.0.3",
|
||||
"babel-preset-react": "^6.24.1",
|
||||
"basic-auth": "^2.0.0",
|
||||
"body-parser": "^1.14.2",
|
||||
"classnames": "^2.2.0",
|
||||
"codemirror": "^5.22.0",
|
||||
"cookie-parser": "^1.4.3",
|
||||
"create-react-class": "^15.6.3",
|
||||
"express": "^4.13.3",
|
||||
"jwt-simple": "^0.5.1",
|
||||
"lodash": "^4.11.2",
|
||||
"marked": "^0.3.5",
|
||||
"moment": "^2.11.0",
|
||||
"mongoose": "^4.3.3",
|
||||
"nconf": "^0.8.4",
|
||||
"pico-flux": "^1.1.0",
|
||||
"pico-router": "^1.1.0",
|
||||
"react": "^15.0.2",
|
||||
"react-dom": "^15.0.2",
|
||||
"mongoose": "^5.0.13",
|
||||
"nconf": "^0.10.0",
|
||||
"pico-router": "^2.1.0",
|
||||
"react": "^16.3.1",
|
||||
"react-dom": "^16.3.1",
|
||||
"shortid": "^2.2.4",
|
||||
"striptags": "^2.1.1",
|
||||
"superagent": "^1.6.1",
|
||||
"vitreum": "^4.0.12"
|
||||
"superagent": "^3.8.2",
|
||||
"vitreum": "^4.10.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^4.19.1",
|
||||
"eslint-plugin-react": "^7.7.0",
|
||||
"pico-check": "^1.0.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,19 +2,19 @@ const label = 'build';
|
||||
console.time(label);
|
||||
|
||||
const clean = require('vitreum/steps/clean.js');
|
||||
const jsx = require('vitreum/steps/jsx.js').partial;
|
||||
const lib = require('vitreum/steps/libs.js').partial;
|
||||
const less = require('vitreum/steps/less.js').partial;
|
||||
const asset = require('vitreum/steps/assets.js').partial;
|
||||
const jsx = require('vitreum/steps/jsx.js');
|
||||
const lib = require('vitreum/steps/libs.js');
|
||||
const less = require('vitreum/steps/less.js');
|
||||
const asset = require('vitreum/steps/assets.js');
|
||||
|
||||
const Proj = require('./project.json');
|
||||
|
||||
clean()
|
||||
.then(lib(Proj.libs))
|
||||
.then(jsx('homebrew', './client/homebrew/homebrew.jsx', Proj.libs, ['./shared']))
|
||||
.then(less('homebrew', ['./shared']))
|
||||
.then(jsx('admin', './client/admin/admin.jsx', Proj.libs, ['./shared']))
|
||||
.then(less('admin', ['./shared']))
|
||||
.then(asset(Proj.assets, ['./shared', './client']))
|
||||
.then(()=>jsx('homebrew', './client/homebrew/homebrew.jsx', { libs: Proj.libs, shared: ['./shared'] }))
|
||||
.then((deps)=>less('homebrew', { shared: ['./shared'] }, deps))
|
||||
.then(()=>jsx('admin', './client/admin/admin.jsx', { libs: Proj.libs, shared: ['./shared'] }))
|
||||
.then((deps)=>less('admin', { shared: ['./shared'] }, deps))
|
||||
.then(()=>asset(Proj.assets, ['./shared', './client']))
|
||||
.then(console.timeEnd.bind(console, label))
|
||||
.catch(console.error);
|
||||
@@ -1,23 +1,22 @@
|
||||
const label = 'dev';
|
||||
console.time(label);
|
||||
|
||||
const jsx = require('vitreum/steps/jsx.watch.js').partial;
|
||||
const less = require('vitreum/steps/less.watch.js').partial;
|
||||
const assets = require('vitreum/steps/assets.watch.js').partial;
|
||||
const server = require('vitreum/steps/server.watch.js').partial;
|
||||
const livereload = require('vitreum/steps/livereload.js').partial;
|
||||
const jsx = require('vitreum/steps/jsx.watch.js');
|
||||
const less = require('vitreum/steps/less.watch.js');
|
||||
const assets = require('vitreum/steps/assets.watch.js');
|
||||
const server = require('vitreum/steps/server.watch.js');
|
||||
const livereload = require('vitreum/steps/livereload.js');
|
||||
|
||||
const Proj = require('./project.json');
|
||||
|
||||
Promise.resolve()
|
||||
.then(jsx('homebrew', './client/homebrew/homebrew.jsx', Proj.libs, './shared'))
|
||||
.then(less('homebrew', './shared'))
|
||||
.then(()=>jsx('homebrew', './client/homebrew/homebrew.jsx', { libs: Proj.libs, shared: ['./shared'] }))
|
||||
.then((deps)=>less('homebrew', { shared: ['./shared'] }, deps))
|
||||
.then(()=>jsx('admin', './client/admin/admin.jsx', { libs: Proj.libs, shared: ['./shared'] }))
|
||||
.then((deps)=>less('admin', { shared: ['./shared'] }, deps))
|
||||
|
||||
.then(jsx('admin', './client/admin/admin.jsx', Proj.libs, './shared'))
|
||||
.then(less('admin', './shared'))
|
||||
|
||||
.then(assets(Proj.assets, ['./shared', './client']))
|
||||
.then(livereload())
|
||||
.then(server('./server.js', ['server']))
|
||||
.then(()=>assets(Proj.assets, ['./shared', './client']))
|
||||
.then(()=>livereload())
|
||||
.then(()=>server('./server.js', ['server']))
|
||||
.then(console.timeEnd.bind(console, label))
|
||||
.catch(console.error)
|
||||
.catch(console.error);
|
||||
@@ -1,10 +1,10 @@
|
||||
const less = require('less');
|
||||
const fs = require('fs');
|
||||
|
||||
less.render(fs.readFileSync('./client/homebrew/phbStyle/phb.style.less', 'utf8'), {compress : true})
|
||||
.then((output) => {
|
||||
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) => {
|
||||
}, (err)=>{
|
||||
console.error(err);
|
||||
});
|
||||
@@ -5,6 +5,7 @@
|
||||
"libs" : [
|
||||
"react",
|
||||
"react-dom",
|
||||
"create-react-class",
|
||||
"lodash",
|
||||
"classnames",
|
||||
"codemirror",
|
||||
@@ -13,7 +14,6 @@
|
||||
"moment",
|
||||
"superagent",
|
||||
"marked",
|
||||
"pico-router",
|
||||
"pico-flux"
|
||||
"pico-router"
|
||||
]
|
||||
}
|
||||
75
server.js
75
server.js
@@ -1,10 +1,10 @@
|
||||
const _ = require('lodash');
|
||||
const jwt = require('jwt-simple');
|
||||
const express = require("express");
|
||||
const express = require('express');
|
||||
const app = express();
|
||||
|
||||
app.use(express.static(__dirname + '/build'));''
|
||||
app.use(require('body-parser').json({limit: '25mb'}));
|
||||
app.use(express.static(`${__dirname}/build`));
|
||||
app.use(require('body-parser').json({ limit: '25mb' }));
|
||||
app.use(require('cookie-parser')());
|
||||
|
||||
const config = require('nconf')
|
||||
@@ -14,20 +14,21 @@ const config = require('nconf')
|
||||
.file('defaults', { file: 'config/default.json' });
|
||||
|
||||
//DB
|
||||
require('mongoose')
|
||||
.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.');
|
||||
});
|
||||
const mongoose = require('mongoose');
|
||||
mongoose.connect(config.get('mongodb_uri') || config.get('mongolab_uri') || 'mongodb://localhost/naturalcrit');
|
||||
mongoose.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.');
|
||||
throw 'Can not connect to Mongo';
|
||||
});
|
||||
|
||||
|
||||
//Account MIddleware
|
||||
app.use((req, res, next) => {
|
||||
app.use((req, res, next)=>{
|
||||
if(req.cookies && req.cookies.nc_session){
|
||||
try{
|
||||
try {
|
||||
req.account = jwt.decode(req.cookies.nc_session, config.get('secret'));
|
||||
}catch(e){}
|
||||
} catch (e){}
|
||||
}
|
||||
return next();
|
||||
});
|
||||
@@ -43,35 +44,35 @@ const changelogText = require('fs').readFileSync('./changelog.md', 'utf8');
|
||||
|
||||
|
||||
//Source page
|
||||
String.prototype.replaceAll = function(s,r){return this.split(s).join(r)}
|
||||
String.prototype.replaceAll = function(s, r){return this.split(s).join(r);};
|
||||
app.get('/source/:id', (req, res)=>{
|
||||
HomebrewModel.get({shareId : req.params.id})
|
||||
HomebrewModel.get({ shareId: req.params.id })
|
||||
.then((brew)=>{
|
||||
const text = brew.text.replaceAll('<', '<').replaceAll('>', '>');
|
||||
return res.send(`<code><pre>${text}</pre></code>`);
|
||||
return res.send(`<code><pre style="white-space: pre-wrap;">${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) => {
|
||||
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) => {
|
||||
.then((brews)=>{
|
||||
req.brews = brews;
|
||||
return next();
|
||||
})
|
||||
.catch((err) => {
|
||||
.catch((err)=>{
|
||||
console.log(err);
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
app.get('/edit/:id', (req, res, next)=>{
|
||||
HomebrewModel.get({editId : req.params.id})
|
||||
HomebrewModel.get({ editId: req.params.id })
|
||||
.then((brew)=>{
|
||||
req.brew = brew.sanatize();
|
||||
return next();
|
||||
@@ -84,7 +85,7 @@ app.get('/edit/:id', (req, res, next)=>{
|
||||
|
||||
//Share Page
|
||||
app.get('/share/:id', (req, res, next)=>{
|
||||
HomebrewModel.get({shareId : req.params.id})
|
||||
HomebrewModel.get({ shareId: req.params.id })
|
||||
.then((brew)=>{
|
||||
return brew.increaseView();
|
||||
})
|
||||
@@ -100,7 +101,7 @@ app.get('/share/:id', (req, res, next)=>{
|
||||
|
||||
//Print Page
|
||||
app.get('/print/:id', (req, res, next)=>{
|
||||
HomebrewModel.get({shareId : req.params.id})
|
||||
HomebrewModel.get({ shareId: req.params.id })
|
||||
.then((brew)=>{
|
||||
req.brew = brew.sanatize(true);
|
||||
return next();
|
||||
@@ -115,20 +116,20 @@ app.get('/print/:id', (req, res, next)=>{
|
||||
//Render Page
|
||||
const render = require('vitreum/steps/render');
|
||||
const templateFn = require('./client/template.js');
|
||||
app.use((req, res) => {
|
||||
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
|
||||
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);
|
||||
})
|
||||
.then((page) => {
|
||||
return res.send(page)
|
||||
})
|
||||
.catch((err) => {
|
||||
.catch((err)=>{
|
||||
console.log(err);
|
||||
return res.sendStatus(500);
|
||||
});
|
||||
@@ -137,4 +138,4 @@ app.use((req, res) => {
|
||||
|
||||
const PORT = process.env.PORT || 8000;
|
||||
app.listen(PORT);
|
||||
console.log(`server on port:${PORT}`);
|
||||
console.log(`server on port:${PORT}`);
|
||||
|
||||
@@ -20,9 +20,9 @@ process.env.ADMIN_KEY = process.env.ADMIN_KEY || 'admin_key';
|
||||
//Removes all empty brews that are older than 3 days and that are shorter than a tweet
|
||||
router.get('/api/invalid', mw.adminOnly, (req, res)=>{
|
||||
const invalidBrewQuery = HomebrewModel.find({
|
||||
'$where' : "this.text.length < 140",
|
||||
createdAt: {
|
||||
$lt: Moment().subtract(3, 'days').toDate()
|
||||
'$where' : 'this.text.length < 140',
|
||||
createdAt : {
|
||||
$lt : Moment().subtract(3, 'days').toDate()
|
||||
}
|
||||
});
|
||||
|
||||
@@ -30,28 +30,28 @@ router.get('/api/invalid', mw.adminOnly, (req, res)=>{
|
||||
invalidBrewQuery.remove().exec((err, objs)=>{
|
||||
refreshCount();
|
||||
return res.send(200);
|
||||
})
|
||||
}else{
|
||||
});
|
||||
} 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) => {
|
||||
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);
|
||||
});
|
||||
HomebrewModel.findOne({ $or : [
|
||||
{ editId: { '$regex': req.params.id, '$options': 'i' } },
|
||||
{ shareId: { '$regex': req.params.id, '$options': 'i' } },
|
||||
] }).exec((err, brew)=>{
|
||||
return res.json(brew);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -61,19 +61,19 @@ router.get('/admin/lookup/:id', mw.adminOnly, (req, res, next) => {
|
||||
const render = require('vitreum/steps/render');
|
||||
const 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')
|
||||
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,
|
||||
url : req.originalUrl,
|
||||
admin_key : process.env.ADMIN_KEY,
|
||||
})
|
||||
.then((page)=>{
|
||||
return res.send(page);
|
||||
})
|
||||
.then((page) => {
|
||||
return res.send(page)
|
||||
})
|
||||
.catch((err) => {
|
||||
.catch((err)=>{
|
||||
console.log(err);
|
||||
return res.sendStatus(500);
|
||||
});
|
||||
|
||||
@@ -1,144 +1,130 @@
|
||||
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;
|
||||
}
|
||||
const _ = require('lodash');
|
||||
const HomebrewModel = require('./homebrew.model.js').model;
|
||||
const router = require('express').Router();
|
||||
|
||||
// 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');
|
||||
const 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 +1,81 @@
|
||||
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,
|
||||
}
|
||||
const mongoose = require('mongoose');
|
||||
const shortid = require('shortid');
|
||||
const _ = require('lodash');
|
||||
|
||||
const 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)=>{
|
||||
const 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);
|
||||
}));
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
const Homebrew = mongoose.model('Homebrew', HomebrewSchema);
|
||||
|
||||
module.exports = {
|
||||
schema : HomebrewSchema,
|
||||
model : Homebrew,
|
||||
};
|
||||
@@ -1,21 +1,22 @@
|
||||
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
|
||||
const DISMISS_KEY = 'dismiss_render_warning';
|
||||
|
||||
const RenderWarnings = React.createClass({
|
||||
getInitialState: function() {
|
||||
const RenderWarnings = createClass({
|
||||
getInitialState : function() {
|
||||
return {
|
||||
warnings: {}
|
||||
warnings : {}
|
||||
};
|
||||
},
|
||||
componentDidMount: function() {
|
||||
componentDidMount : function() {
|
||||
this.checkWarnings();
|
||||
window.addEventListener('resize', this.checkWarnings);
|
||||
},
|
||||
componentWillUnmount: function() {
|
||||
componentWillUnmount : function() {
|
||||
window.removeEventListener('resize', this.checkWarnings);
|
||||
},
|
||||
warnings : {
|
||||
@@ -31,33 +32,24 @@ const RenderWarnings = React.createClass({
|
||||
</li>;
|
||||
}
|
||||
},
|
||||
zoom : function(){
|
||||
return false;
|
||||
if(window.devicePixelRatio !== 1){
|
||||
return <li key='zoom'>
|
||||
<em>Your browser is zoomed. </em> <br />
|
||||
This can cause content to jump columns.
|
||||
</li>;
|
||||
}
|
||||
}
|
||||
},
|
||||
checkWarnings : function(){
|
||||
const hideDismiss = localStorage.getItem(DISMISS_KEY);
|
||||
if(hideDismiss) return this.setState({warnings : {}});
|
||||
if(hideDismiss) return this.setState({ warnings: {} });
|
||||
|
||||
this.setState({
|
||||
warnings : _.reduce(this.warnings, (r, fn, type) => {
|
||||
warnings : _.reduce(this.warnings, (r, fn, type)=>{
|
||||
const element = fn();
|
||||
if(element) r[type] = element;
|
||||
return r;
|
||||
}, {})
|
||||
})
|
||||
});
|
||||
},
|
||||
dismiss : function(){
|
||||
localStorage.setItem(DISMISS_KEY, true);
|
||||
this.checkWarnings();
|
||||
},
|
||||
render: function(){
|
||||
render : function(){
|
||||
if(_.isEmpty(this.state.warnings)) return null;
|
||||
|
||||
return <div className='renderWarnings'>
|
||||
@@ -66,7 +58,7 @@ const RenderWarnings = React.createClass({
|
||||
<h3>Render Warnings</h3>
|
||||
<small>If this homebrew is rendering badly if might be because of the following:</small>
|
||||
<ul>{_.values(this.state.warnings)}</ul>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,73 +1,88 @@
|
||||
var React = require('react');
|
||||
var _ = require('lodash');
|
||||
var cx = require('classnames');
|
||||
|
||||
|
||||
var CodeMirror;
|
||||
if(typeof navigator !== 'undefined'){
|
||||
var CodeMirror = require('codemirror');
|
||||
|
||||
//Language Modes
|
||||
require('codemirror/mode/gfm/gfm.js'); //Github flavoured markdown
|
||||
require('codemirror/mode/javascript/javascript.js');
|
||||
}
|
||||
|
||||
|
||||
var CodeEditor = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
language : '',
|
||||
value : '',
|
||||
wrap : false,
|
||||
onChange : function(){},
|
||||
onCursorActivity : function(){},
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.codeMirror = CodeMirror(this.refs.editor,{
|
||||
value : this.props.value,
|
||||
lineNumbers: true,
|
||||
lineWrapping : this.props.wrap,
|
||||
mode : this.props.language
|
||||
});
|
||||
|
||||
this.codeMirror.on('change', this.handleChange);
|
||||
this.codeMirror.on('cursorActivity', this.handleCursorActivity);
|
||||
this.updateSize();
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(nextProps){
|
||||
if(this.codeMirror && nextProps.value !== undefined && this.codeMirror.getValue() != nextProps.value) {
|
||||
this.codeMirror.setValue(nextProps.value);
|
||||
}
|
||||
},
|
||||
|
||||
shouldComponentUpdate: function(nextProps, nextState) {
|
||||
return false;
|
||||
},
|
||||
|
||||
setCursorPosition : function(line, char){
|
||||
setTimeout(()=>{
|
||||
this.codeMirror.focus();
|
||||
this.codeMirror.doc.setCursor(line, char);
|
||||
}, 10);
|
||||
},
|
||||
|
||||
updateSize : function(){
|
||||
this.codeMirror.refresh();
|
||||
},
|
||||
|
||||
handleChange : function(editor){
|
||||
this.props.onChange(editor.getValue());
|
||||
},
|
||||
handleCursorActivity : function(){
|
||||
this.props.onCursorActivity(this.codeMirror.doc.getCursor());
|
||||
},
|
||||
|
||||
render : function(){
|
||||
return <div className='codeEditor' ref='editor' />
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = CodeEditor;
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
|
||||
|
||||
let CodeMirror;
|
||||
if(typeof navigator !== 'undefined'){
|
||||
CodeMirror = require('codemirror');
|
||||
|
||||
//Language Modes
|
||||
require('codemirror/mode/gfm/gfm.js'); //Github flavoured markdown
|
||||
require('codemirror/mode/javascript/javascript.js');
|
||||
}
|
||||
|
||||
|
||||
const CodeEditor = createClass({
|
||||
getDefaultProps : function() {
|
||||
return {
|
||||
language : '',
|
||||
value : '',
|
||||
wrap : false,
|
||||
onChange : function(){},
|
||||
onCursorActivity : function(){},
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount : function() {
|
||||
this.codeMirror = CodeMirror(this.refs.editor, {
|
||||
value : this.props.value,
|
||||
lineNumbers : true,
|
||||
lineWrapping : this.props.wrap,
|
||||
mode : this.props.language,
|
||||
extraKeys : {
|
||||
'Ctrl-B' : this.makeBold,
|
||||
'Ctrl-I' : this.makeItalic
|
||||
}
|
||||
});
|
||||
|
||||
this.codeMirror.on('change', this.handleChange);
|
||||
this.codeMirror.on('cursorActivity', this.handleCursorActivity);
|
||||
this.updateSize();
|
||||
},
|
||||
|
||||
makeBold : function() {
|
||||
const selection = this.codeMirror.getSelection();
|
||||
this.codeMirror.replaceSelection(`**${selection}**`, 'around');
|
||||
},
|
||||
|
||||
makeItalic : function() {
|
||||
const selection = this.codeMirror.getSelection();
|
||||
this.codeMirror.replaceSelection(`*${selection}*`, 'around');
|
||||
},
|
||||
|
||||
componentWillReceiveProps : function(nextProps){
|
||||
if(this.codeMirror && nextProps.value !== undefined && this.codeMirror.getValue() != nextProps.value) {
|
||||
this.codeMirror.setValue(nextProps.value);
|
||||
}
|
||||
},
|
||||
|
||||
shouldComponentUpdate : function(nextProps, nextState) {
|
||||
return false;
|
||||
},
|
||||
|
||||
setCursorPosition : function(line, char){
|
||||
setTimeout(()=>{
|
||||
this.codeMirror.focus();
|
||||
this.codeMirror.doc.setCursor(line, char);
|
||||
}, 10);
|
||||
},
|
||||
|
||||
updateSize : function(){
|
||||
this.codeMirror.refresh();
|
||||
},
|
||||
|
||||
handleChange : function(editor){
|
||||
this.props.onChange(editor.getValue());
|
||||
},
|
||||
handleCursorActivity : function(){
|
||||
this.props.onCursorActivity(this.codeMirror.doc.getCursor());
|
||||
},
|
||||
|
||||
render : function(){
|
||||
return <div className='codeEditor' ref='editor' />;
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = CodeEditor;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
var _ = require('lodash');
|
||||
var Markdown = require('marked');
|
||||
var renderer = new Markdown.Renderer();
|
||||
const _ = require('lodash');
|
||||
const Markdown = require('marked');
|
||||
const renderer = new Markdown.Renderer();
|
||||
|
||||
//Processes the markdown within an HTML block if it's just a class-wrapper
|
||||
renderer.html = function (html) {
|
||||
if(_.startsWith(_.trim(html), '<div') && _.endsWith(_.trim(html), '</div>')){
|
||||
var openTag = html.substring(0, html.indexOf('>')+1);
|
||||
const openTag = html.substring(0, html.indexOf('>')+1);
|
||||
html = html.substring(html.indexOf('>')+1);
|
||||
html = html.substring(0, html.lastIndexOf('</div>'));
|
||||
return `${openTag} ${Markdown(html)} </div>`;
|
||||
@@ -13,25 +13,33 @@ renderer.html = function (html) {
|
||||
return html;
|
||||
};
|
||||
|
||||
const sanatizeScriptTags = (content)=>{
|
||||
return content
|
||||
.replace(/<script/g, '<script')
|
||||
.replace(/<\/script>/g, '</script>');
|
||||
};
|
||||
|
||||
const tagTypes = ['div', 'span', 'a'];
|
||||
const tagRegex = new RegExp('(' +
|
||||
const tagRegex = new RegExp(`(${
|
||||
_.map(tagTypes, (type)=>{
|
||||
return `\\<${type}|\\</${type}>`;
|
||||
}).join('|') + ')', 'g');
|
||||
}).join('|')})`, 'g');
|
||||
|
||||
|
||||
module.exports = {
|
||||
marked : Markdown,
|
||||
render : (rawBrewText)=>{
|
||||
return Markdown(rawBrewText, {renderer : renderer})
|
||||
return Markdown(
|
||||
sanatizeScriptTags(rawBrewText),
|
||||
{ renderer: renderer }
|
||||
);
|
||||
},
|
||||
|
||||
validate : (rawBrewText) => {
|
||||
var errors = [];
|
||||
var leftovers = _.reduce(rawBrewText.split('\n'), (acc, line, _lineNumber) => {
|
||||
var lineNumber = _lineNumber + 1;
|
||||
var matches = line.match(tagRegex);
|
||||
validate : (rawBrewText)=>{
|
||||
const errors = [];
|
||||
const leftovers = _.reduce(rawBrewText.split('\n'), (acc, line, _lineNumber)=>{
|
||||
const lineNumber = _lineNumber + 1;
|
||||
const matches = line.match(tagRegex);
|
||||
if(!matches || !matches.length) return acc;
|
||||
|
||||
_.each(matches, (match)=>{
|
||||
@@ -48,16 +56,16 @@ module.exports = {
|
||||
line : lineNumber,
|
||||
type : type,
|
||||
text : 'Unmatched closing tag',
|
||||
id : 'CLOSE'
|
||||
id : 'CLOSE'
|
||||
});
|
||||
}else if(_.last(acc).type == type){
|
||||
} else if(_.last(acc).type == type){
|
||||
acc.pop();
|
||||
}else{
|
||||
} else {
|
||||
errors.push({
|
||||
line : _.last(acc).line + ' to ' + lineNumber,
|
||||
line : `${_.last(acc).line} to ${lineNumber}`,
|
||||
type : type,
|
||||
text : 'Type mismatch on closing tag',
|
||||
id : 'MISMATCH'
|
||||
id : 'MISMATCH'
|
||||
});
|
||||
acc.pop();
|
||||
}
|
||||
@@ -71,9 +79,9 @@ module.exports = {
|
||||
errors.push({
|
||||
line : unmatched.line,
|
||||
type : unmatched.type,
|
||||
text : "Unmatched opening tag",
|
||||
id : 'OPEN'
|
||||
})
|
||||
text : 'Unmatched opening tag',
|
||||
id : 'OPEN'
|
||||
});
|
||||
});
|
||||
|
||||
return errors;
|
||||
|
||||
@@ -1,72 +1,73 @@
|
||||
var React = require('react');
|
||||
var _ = require('lodash');
|
||||
var cx = require('classnames');
|
||||
|
||||
var NaturalCritIcon = require('naturalcrit/svg/naturalcrit.svg.jsx');
|
||||
|
||||
var Nav = {
|
||||
base : React.createClass({
|
||||
render : function(){
|
||||
return <nav>
|
||||
<div className='navContent'>
|
||||
{this.props.children}
|
||||
</div>
|
||||
</nav>
|
||||
}
|
||||
}),
|
||||
logo : function(){
|
||||
return <a className='navLogo' href="http://naturalcrit.com">
|
||||
<NaturalCritIcon />
|
||||
<span className='name'>
|
||||
Natural<span className='crit'>Crit</span>
|
||||
</span>
|
||||
</a>;
|
||||
},
|
||||
|
||||
section : React.createClass({
|
||||
render : function(){
|
||||
return <div className='navSection'>
|
||||
{this.props.children}
|
||||
</div>
|
||||
}
|
||||
}),
|
||||
|
||||
item : React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
icon : null,
|
||||
href : null,
|
||||
newTab : false,
|
||||
onClick : function(){},
|
||||
color : null
|
||||
};
|
||||
},
|
||||
handleClick : function(){
|
||||
this.props.onClick();
|
||||
},
|
||||
render : function(){
|
||||
var classes = cx('navItem', this.props.color, this.props.className);
|
||||
|
||||
var icon;
|
||||
if(this.props.icon) icon = <i className={'fa ' + this.props.icon} />;
|
||||
|
||||
const props = _.omit(this.props, ['newTab']);
|
||||
|
||||
if(this.props.href){
|
||||
return <a {...props} className={classes} target={this.props.newTab ? '_blank' : '_self'} >
|
||||
{this.props.children}
|
||||
{icon}
|
||||
</a>
|
||||
}else{
|
||||
return <div {...props} className={classes} onClick={this.handleClick} >
|
||||
{this.props.children}
|
||||
{icon}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}),
|
||||
|
||||
};
|
||||
|
||||
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
|
||||
const NaturalCritIcon = require('naturalcrit/svg/naturalcrit.svg.jsx');
|
||||
|
||||
const Nav = {
|
||||
base : createClass({
|
||||
render : function(){
|
||||
return <nav>
|
||||
<div className='navContent'>
|
||||
{this.props.children}
|
||||
</div>
|
||||
</nav>;
|
||||
}
|
||||
}),
|
||||
logo : function(){
|
||||
return <a className='navLogo' href='http://naturalcrit.com'>
|
||||
<NaturalCritIcon />
|
||||
<span className='name'>
|
||||
Natural<span className='crit'>Crit</span>
|
||||
</span>
|
||||
</a>;
|
||||
},
|
||||
|
||||
section : createClass({
|
||||
render : function(){
|
||||
return <div className='navSection'>
|
||||
{this.props.children}
|
||||
</div>;
|
||||
}
|
||||
}),
|
||||
|
||||
item : createClass({
|
||||
getDefaultProps : function() {
|
||||
return {
|
||||
icon : null,
|
||||
href : null,
|
||||
newTab : false,
|
||||
onClick : function(){},
|
||||
color : null
|
||||
};
|
||||
},
|
||||
handleClick : function(){
|
||||
this.props.onClick();
|
||||
},
|
||||
render : function(){
|
||||
const classes = cx('navItem', this.props.color, this.props.className);
|
||||
|
||||
let icon;
|
||||
if(this.props.icon) icon = <i className={`fa ${this.props.icon}`} />;
|
||||
|
||||
const props = _.omit(this.props, ['newTab']);
|
||||
|
||||
if(this.props.href){
|
||||
return <a {...props} className={classes} target={this.props.newTab ? '_blank' : '_self'} >
|
||||
{this.props.children}
|
||||
{icon}
|
||||
</a>;
|
||||
} else {
|
||||
return <div {...props} className={classes} onClick={this.handleClick} >
|
||||
{this.props.children}
|
||||
{icon}
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
}),
|
||||
|
||||
};
|
||||
|
||||
|
||||
module.exports = Nav;
|
||||
@@ -1,99 +1,100 @@
|
||||
var React = require('react');
|
||||
var _ = require('lodash');
|
||||
var cx = require('classnames');
|
||||
|
||||
var SplitPane = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
storageKey : 'naturalcrit-pane-split',
|
||||
onDragFinish : function(){} //fires when dragging
|
||||
|
||||
};
|
||||
},
|
||||
getInitialState: function() {
|
||||
return {
|
||||
size : null,
|
||||
isDragging : false
|
||||
};
|
||||
},
|
||||
componentDidMount: function() {
|
||||
var paneSize = window.localStorage.getItem(this.props.storageKey);
|
||||
if(paneSize){
|
||||
this.setState({
|
||||
size : paneSize
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
handleUp : function(){
|
||||
if(this.state.isDragging){
|
||||
this.props.onDragFinish(this.state.size);
|
||||
window.localStorage.setItem(this.props.storageKey, this.state.size);
|
||||
}
|
||||
this.setState({ isDragging : false });
|
||||
},
|
||||
handleDown : function(){
|
||||
this.setState({ isDragging : true });
|
||||
//this.unFocus()
|
||||
},
|
||||
handleMove : function(e){
|
||||
if(!this.state.isDragging) return;
|
||||
this.setState({
|
||||
size : e.pageX
|
||||
});
|
||||
},
|
||||
/*
|
||||
unFocus : function() {
|
||||
if(document.selection){
|
||||
document.selection.empty();
|
||||
}else{
|
||||
window.getSelection().removeAllRanges();
|
||||
}
|
||||
},
|
||||
*/
|
||||
renderDivider : function(){
|
||||
return <div className='divider' onMouseDown={this.handleDown}>
|
||||
<div className='dots'>
|
||||
<i className='fa fa-circle' />
|
||||
<i className='fa fa-circle' />
|
||||
<i className='fa fa-circle' />
|
||||
</div>
|
||||
</div>
|
||||
},
|
||||
|
||||
render : function(){
|
||||
return <div className='splitPane' onMouseMove={this.handleMove} onMouseUp={this.handleUp}>
|
||||
<Pane ref='pane1' width={this.state.size}>{this.props.children[0]}</Pane>
|
||||
{this.renderDivider()}
|
||||
<Pane ref='pane2'>{this.props.children[1]}</Pane>
|
||||
</div>
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
var Pane = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
width : null
|
||||
};
|
||||
},
|
||||
render : function(){
|
||||
var styles = {};
|
||||
if(this.props.width){
|
||||
styles = {
|
||||
flex : 'none',
|
||||
width : this.props.width + 'px'
|
||||
}
|
||||
}
|
||||
return <div className={cx('pane', this.props.className)} style={styles}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
module.exports = SplitPane;
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
|
||||
const SplitPane = createClass({
|
||||
getDefaultProps : function() {
|
||||
return {
|
||||
storageKey : 'naturalcrit-pane-split',
|
||||
onDragFinish : function(){} //fires when dragging
|
||||
|
||||
};
|
||||
},
|
||||
getInitialState : function() {
|
||||
return {
|
||||
size : null,
|
||||
isDragging : false
|
||||
};
|
||||
},
|
||||
componentDidMount : function() {
|
||||
const paneSize = window.localStorage.getItem(this.props.storageKey);
|
||||
if(paneSize){
|
||||
this.setState({
|
||||
size : paneSize
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
handleUp : function(){
|
||||
if(this.state.isDragging){
|
||||
this.props.onDragFinish(this.state.size);
|
||||
window.localStorage.setItem(this.props.storageKey, this.state.size);
|
||||
}
|
||||
this.setState({ isDragging: false });
|
||||
},
|
||||
handleDown : function(){
|
||||
this.setState({ isDragging: true });
|
||||
//this.unFocus()
|
||||
},
|
||||
handleMove : function(e){
|
||||
if(!this.state.isDragging) return;
|
||||
this.setState({
|
||||
size : e.pageX
|
||||
});
|
||||
},
|
||||
/*
|
||||
unFocus : function() {
|
||||
if(document.selection){
|
||||
document.selection.empty();
|
||||
}else{
|
||||
window.getSelection().removeAllRanges();
|
||||
}
|
||||
},
|
||||
*/
|
||||
renderDivider : function(){
|
||||
return <div className='divider' onMouseDown={this.handleDown}>
|
||||
<div className='dots'>
|
||||
<i className='fa fa-circle' />
|
||||
<i className='fa fa-circle' />
|
||||
<i className='fa fa-circle' />
|
||||
</div>
|
||||
</div>;
|
||||
},
|
||||
|
||||
render : function(){
|
||||
return <div className='splitPane' onMouseMove={this.handleMove} onMouseUp={this.handleUp}>
|
||||
<Pane ref='pane1' width={this.state.size}>{this.props.children[0]}</Pane>
|
||||
{this.renderDivider()}
|
||||
<Pane ref='pane2'>{this.props.children[1]}</Pane>
|
||||
</div>;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const Pane = createClass({
|
||||
getDefaultProps : function() {
|
||||
return {
|
||||
width : null
|
||||
};
|
||||
},
|
||||
render : function(){
|
||||
let styles = {};
|
||||
if(this.props.width){
|
||||
styles = {
|
||||
flex : 'none',
|
||||
width : `${this.props.width}px`
|
||||
};
|
||||
}
|
||||
return <div className={cx('pane', this.props.className)} style={styles}>
|
||||
{this.props.children}
|
||||
</div>;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
module.exports = SplitPane;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
flex-direction : row;
|
||||
.pane{
|
||||
overflow-x : hidden;
|
||||
overflow-y : hidden;
|
||||
flex : 1;
|
||||
}
|
||||
.divider{
|
||||
@@ -28,4 +29,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
module.exports = function(props){
|
||||
return <svg version="1.1" x="0px" y="0px" viewBox="0 0 80 100" enableBackground="new 0 0 80 80"><g><g><polygon fill="#000000" points="12.9,71.4 7.6,66.1 19.3,54.4 20.7,55.8 10.4,66.1 12.9,68.6 23.2,58.3 24.6,59.7 "/></g><g><path fill="#000000" d="M29,61.6c-1.7,0-3.4-0.7-4.6-1.9l-5.1-5.1c-2.5-2.5-2.5-6.6,0-9.2l0.7-0.7L34.3,59l-0.7,0.7 C32.4,60.9,30.8,61.6,29,61.6z M20.1,47.6c-1.1,1.7-0.9,4.1,0.6,5.6l5.1,5.1c0.8,0.8,2,1.3,3.2,1.3c0.9,0,1.7-0.2,2.4-0.7 L20.1,47.6z"/></g><g><path fill="#000000" d="M12.3,74.8c-0.8,0-1.5-0.3-2-0.8l-5.2-5.2c-0.5-0.5-0.8-1.2-0.8-2c0-0.8,0.3-1.5,0.8-2 c1.1-1.1,2.9-1.1,4,0l5.2,5.2c1.1,1.1,1.1,2.9,0,4C13.8,74.5,13.1,74.8,12.3,74.8z M7.1,65.9c-0.2,0-0.4,0.1-0.6,0.2 c-0.2,0.2-0.2,0.4-0.2,0.6s0.1,0.4,0.2,0.6l5.2,5.2c0.3,0.3,0.9,0.3,1.2,0c0.3-0.3,0.3-0.8,0-1.2l-5.2-5.2 C7.5,66,7.3,65.9,7.1,65.9z"/></g><g><polygon fill="#000000" points="31.7,58.7 30.3,57.3 70,17.6 70,9 62.4,9 23.3,49.4 21.9,48 61.6,7 72,7 72,18.4 "/></g><g><rect x="46" y="6.7" transform="matrix(0.7168 0.6973 -0.6973 0.7168 35.9716 -23.568)" fill="#000000" width="2" height="51.6"/></g><g><rect x="13" y="61" fill="#000000" width="2" height="7"/></g><g><rect x="17" y="57" fill="#000000" width="2" height="7"/></g></g><g><g><polygon fill="#000000" points="68.4,71.4 56.7,59.7 58.1,58.3 68.4,68.6 70.8,66.1 60.5,55.8 61.9,54.4 73.6,66.1 "/></g><g><path fill="#000000" d="M52.2,61.6c-1.7,0-3.4-0.7-4.6-1.9L46.9,59l14.3-14.3l0.7,0.7c2.5,2.5,2.5,6.6,0,9.2l-5.1,5.1 C55.6,60.9,53.9,61.6,52.2,61.6z M49.8,58.9c0.7,0.4,1.5,0.7,2.4,0.7c1.2,0,2.3-0.5,3.2-1.3l5.1-5.1c1.5-1.5,1.7-3.8,0.6-5.6 L49.8,58.9z"/></g><g><path fill="#000000" d="M68.9,74.8c-0.8,0-1.5-0.3-2-0.8c-1.1-1.1-1.1-2.9,0-4l5.2-5.2c1.1-1.1,2.9-1.1,4,0c0.5,0.5,0.8,1.2,0.8,2 c0,0.8-0.3,1.5-0.8,2L70.9,74C70.4,74.5,69.7,74.8,68.9,74.8z M74.2,65.9c-0.2,0-0.4,0.1-0.6,0.2l-5.2,5.2c-0.3,0.3-0.3,0.8,0,1.2 c0.3,0.3,0.9,0.3,1.2,0l5.2-5.2c0.2-0.2,0.2-0.4,0.2-0.6s-0.1-0.4-0.2-0.6C74.6,66,74.4,65.9,74.2,65.9z"/></g><g><rect x="38.6" y="52.3" transform="matrix(0.7082 0.706 -0.706 0.7082 50.8397 -16.4875)" fill="#000000" width="13.4" height="2"/></g><g><polygon fill="#000000" points="30.6,39.9 9,18.4 9,7 19.7,7 41.1,29.1 39.7,30.5 18.8,9 11,9 11,17.6 32,38.5 "/></g><g><rect x="47.8" y="43.1" transform="matrix(0.6959 0.7181 -0.7181 0.6959 48.1381 -25.5246)" fill="#000000" width="12.8" height="2"/></g><g><rect x="12" y="23.1" transform="matrix(0.6974 0.7167 -0.7167 0.6974 25.1384 -11.3825)" fill="#000000" width="28.1" height="2"/></g><g><rect x="43.8" y="46.4" transform="matrix(0.6974 0.7167 -0.7167 0.6974 48.7492 -20.5985)" fill="#000000" width="10" height="2"/></g><g><rect x="66" y="61" fill="#000000" width="2" height="7"/></g><g><rect x="62" y="57" fill="#000000" width="2" height="7"/></g></g></svg>;
|
||||
}
|
||||
module.exports = function(props){
|
||||
return <svg version='1.1' x='0px' y='0px' viewBox='0 0 80 100' enableBackground='new 0 0 80 80'><g><g><polygon fill='#000000' points='12.9,71.4 7.6,66.1 19.3,54.4 20.7,55.8 10.4,66.1 12.9,68.6 23.2,58.3 24.6,59.7 '/></g><g><path fill='#000000' d='M29,61.6c-1.7,0-3.4-0.7-4.6-1.9l-5.1-5.1c-2.5-2.5-2.5-6.6,0-9.2l0.7-0.7L34.3,59l-0.7,0.7 C32.4,60.9,30.8,61.6,29,61.6z M20.1,47.6c-1.1,1.7-0.9,4.1,0.6,5.6l5.1,5.1c0.8,0.8,2,1.3,3.2,1.3c0.9,0,1.7-0.2,2.4-0.7 L20.1,47.6z'/></g><g><path fill='#000000' d='M12.3,74.8c-0.8,0-1.5-0.3-2-0.8l-5.2-5.2c-0.5-0.5-0.8-1.2-0.8-2c0-0.8,0.3-1.5,0.8-2 c1.1-1.1,2.9-1.1,4,0l5.2,5.2c1.1,1.1,1.1,2.9,0,4C13.8,74.5,13.1,74.8,12.3,74.8z M7.1,65.9c-0.2,0-0.4,0.1-0.6,0.2 c-0.2,0.2-0.2,0.4-0.2,0.6s0.1,0.4,0.2,0.6l5.2,5.2c0.3,0.3,0.9,0.3,1.2,0c0.3-0.3,0.3-0.8,0-1.2l-5.2-5.2 C7.5,66,7.3,65.9,7.1,65.9z'/></g><g><polygon fill='#000000' points='31.7,58.7 30.3,57.3 70,17.6 70,9 62.4,9 23.3,49.4 21.9,48 61.6,7 72,7 72,18.4 '/></g><g><rect x='46' y='6.7' transform='matrix(0.7168 0.6973 -0.6973 0.7168 35.9716 -23.568)' fill='#000000' width='2' height='51.6'/></g><g><rect x='13' y='61' fill='#000000' width='2' height='7'/></g><g><rect x='17' y='57' fill='#000000' width='2' height='7'/></g></g><g><g><polygon fill='#000000' points='68.4,71.4 56.7,59.7 58.1,58.3 68.4,68.6 70.8,66.1 60.5,55.8 61.9,54.4 73.6,66.1 '/></g><g><path fill='#000000' d='M52.2,61.6c-1.7,0-3.4-0.7-4.6-1.9L46.9,59l14.3-14.3l0.7,0.7c2.5,2.5,2.5,6.6,0,9.2l-5.1,5.1 C55.6,60.9,53.9,61.6,52.2,61.6z M49.8,58.9c0.7,0.4,1.5,0.7,2.4,0.7c1.2,0,2.3-0.5,3.2-1.3l5.1-5.1c1.5-1.5,1.7-3.8,0.6-5.6 L49.8,58.9z'/></g><g><path fill='#000000' d='M68.9,74.8c-0.8,0-1.5-0.3-2-0.8c-1.1-1.1-1.1-2.9,0-4l5.2-5.2c1.1-1.1,2.9-1.1,4,0c0.5,0.5,0.8,1.2,0.8,2 c0,0.8-0.3,1.5-0.8,2L70.9,74C70.4,74.5,69.7,74.8,68.9,74.8z M74.2,65.9c-0.2,0-0.4,0.1-0.6,0.2l-5.2,5.2c-0.3,0.3-0.3,0.8,0,1.2 c0.3,0.3,0.9,0.3,1.2,0l5.2-5.2c0.2-0.2,0.2-0.4,0.2-0.6s-0.1-0.4-0.2-0.6C74.6,66,74.4,65.9,74.2,65.9z'/></g><g><rect x='38.6' y='52.3' transform='matrix(0.7082 0.706 -0.706 0.7082 50.8397 -16.4875)' fill='#000000' width='13.4' height='2'/></g><g><polygon fill='#000000' points='30.6,39.9 9,18.4 9,7 19.7,7 41.1,29.1 39.7,30.5 18.8,9 11,9 11,17.6 32,38.5 '/></g><g><rect x='47.8' y='43.1' transform='matrix(0.6959 0.7181 -0.7181 0.6959 48.1381 -25.5246)' fill='#000000' width='12.8' height='2'/></g><g><rect x='12' y='23.1' transform='matrix(0.6974 0.7167 -0.7167 0.6974 25.1384 -11.3825)' fill='#000000' width='28.1' height='2'/></g><g><rect x='43.8' y='46.4' transform='matrix(0.6974 0.7167 -0.7167 0.6974 48.7492 -20.5985)' fill='#000000' width='10' height='2'/></g><g><rect x='66' y='61' fill='#000000' width='2' height='7'/></g><g><rect x='62' y='57' fill='#000000' width='2' height='7'/></g></g></svg>;
|
||||
};
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
var React = require('react');
|
||||
|
||||
module.exports = function(props){
|
||||
return <svg version="1.1" x="0px" y="0px" viewBox="0 0 90 112.5" enableBackground="new 0 0 90 90" >
|
||||
<path d="M25.363,25.54c0,1.906,8.793,3.454,19.636,3.454c10.848,0,19.638-1.547,19.638-3.454c0-1.12-3.056-2.117-7.774-2.75 c-1.418,1.891-3.659,3.133-6.208,3.133c-2.85,0-5.315-1.547-6.67-3.833C33.617,22.185,25.363,23.692,25.363,25.54z"/><path d="M84.075,54.142c0-8.68-2.868-17.005-8.144-23.829c1.106-1.399,1.41-2.771,1.41-3.854c0-6.574-10.245-9.358-19.264-10.533 c0.209,0.706,0.359,1.439,0.359,2.215c0,0.09-0.022,0.17-0.028,0.26l0,0c-0.028,0.853-0.195,1.667-0.479,2.429 c9.106,1.282,14.508,3.754,14.508,5.63c0,2.644-10.688,6.486-27.439,6.486c-16.748,0-27.438-3.842-27.438-6.486 c0-2.542,9.904-6.183,25.559-6.459c-0.098-0.396-0.159-0.807-0.2-1.223c0.006,0,0.013,0,0.017,0 c-0.017-0.213-0.063-0.417-0.063-0.636c0-1.084,0.226-2.119,0.628-3.058c-6.788,0.129-30.846,1.299-30.846,11.376 c0,1.083,0.305,2.455,1.411,3.854c-5.276,6.823-8.145,15.149-8.145,23.829c0,11.548,5.187,20.107,14.693,25.115 c-0.902,3.146-1.391,7.056,1.111,8.181c2.626,1.178,5.364-2.139,7.111-5.005c4.73,1.261,10.13,1.923,16.161,1.923 c6.034,0,11.428-0.661,16.158-1.922c1.75,2.865,4.493,6.18,7.112,5.004c2.504-1.123,2.014-5.035,1.113-8.179 C78.889,74.249,84.075,65.689,84.075,54.142z M70.39,31.392c5.43,6.046,8.78,14,8.78,22.75c0,20.919-18.582,25.309-34.171,25.309 c-15.587,0-34.17-4.39-34.17-25.309c0-8.75,3.35-16.7,8.781-22.753c5.561,2.643,15.502,4.009,25.389,4.009 C54.886,35.397,64.829,34.031,70.39,31.392z"/><path d="M50.654,23.374c2.892,0,5.234-2.341,5.234-5.233c0-2.887-2.343-5.23-5.234-5.23c-2.887,0-5.231,2.343-5.231,5.23 C45.423,21.032,47.768,23.374,50.654,23.374z"/>
|
||||
<circle cx="62.905" cy="10.089" r="3.595"/>
|
||||
<circle cx="52.616" cy="5.048" r="2.73"/>
|
||||
</svg>;
|
||||
};
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
|
||||
module.exports = function(props){
|
||||
return <svg version='1.1' x='0px' y='0px' viewBox='0 0 90 112.5' enableBackground='new 0 0 90 90' >
|
||||
<path d='M25.363,25.54c0,1.906,8.793,3.454,19.636,3.454c10.848,0,19.638-1.547,19.638-3.454c0-1.12-3.056-2.117-7.774-2.75 c-1.418,1.891-3.659,3.133-6.208,3.133c-2.85,0-5.315-1.547-6.67-3.833C33.617,22.185,25.363,23.692,25.363,25.54z'/><path d='M84.075,54.142c0-8.68-2.868-17.005-8.144-23.829c1.106-1.399,1.41-2.771,1.41-3.854c0-6.574-10.245-9.358-19.264-10.533 c0.209,0.706,0.359,1.439,0.359,2.215c0,0.09-0.022,0.17-0.028,0.26l0,0c-0.028,0.853-0.195,1.667-0.479,2.429 c9.106,1.282,14.508,3.754,14.508,5.63c0,2.644-10.688,6.486-27.439,6.486c-16.748,0-27.438-3.842-27.438-6.486 c0-2.542,9.904-6.183,25.559-6.459c-0.098-0.396-0.159-0.807-0.2-1.223c0.006,0,0.013,0,0.017,0 c-0.017-0.213-0.063-0.417-0.063-0.636c0-1.084,0.226-2.119,0.628-3.058c-6.788,0.129-30.846,1.299-30.846,11.376 c0,1.083,0.305,2.455,1.411,3.854c-5.276,6.823-8.145,15.149-8.145,23.829c0,11.548,5.187,20.107,14.693,25.115 c-0.902,3.146-1.391,7.056,1.111,8.181c2.626,1.178,5.364-2.139,7.111-5.005c4.73,1.261,10.13,1.923,16.161,1.923 c6.034,0,11.428-0.661,16.158-1.922c1.75,2.865,4.493,6.18,7.112,5.004c2.504-1.123,2.014-5.035,1.113-8.179 C78.889,74.249,84.075,65.689,84.075,54.142z M70.39,31.392c5.43,6.046,8.78,14,8.78,22.75c0,20.919-18.582,25.309-34.171,25.309 c-15.587,0-34.17-4.39-34.17-25.309c0-8.75,3.35-16.7,8.781-22.753c5.561,2.643,15.502,4.009,25.389,4.009 C54.886,35.397,64.829,34.031,70.39,31.392z'/><path d='M50.654,23.374c2.892,0,5.234-2.341,5.234-5.233c0-2.887-2.343-5.23-5.234-5.23c-2.887,0-5.231,2.343-5.231,5.23 C45.423,21.032,47.768,23.374,50.654,23.374z'/>
|
||||
<circle cx='62.905' cy='10.089' r='3.595'/>
|
||||
<circle cx='52.616' cy='5.048' r='2.73'/>
|
||||
</svg>;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
var React = require('react');
|
||||
|
||||
module.exports = function(props){
|
||||
return <svg version="1.1" x="0px" y="0px" viewBox="0 0 100 100" enableBackground="new 0 0 100 100"><path d="M80.644,87.982l16.592-41.483c0.054-0.128,0.088-0.26,0.108-0.394c0.006-0.039,0.007-0.077,0.011-0.116 c0.007-0.087,0.008-0.174,0.002-0.26c-0.003-0.046-0.007-0.091-0.014-0.137c-0.014-0.089-0.036-0.176-0.063-0.262 c-0.012-0.034-0.019-0.069-0.031-0.103c-0.047-0.118-0.106-0.229-0.178-0.335c-0.004-0.006-0.006-0.012-0.01-0.018L67.999,3.358 c-0.01-0.013-0.003-0.026-0.013-0.04L68,3.315V4c0,0-0.033,0-0.037,0c-0.403-1-1.094-1.124-1.752-0.976 c0,0.004-0.004-0.012-0.007-0.012C66.201,3.016,66.194,3,66.194,3H66.19h-0.003h-0.003h-0.004h-0.003c0,0-0.004,0-0.007,0 s-0.003-0.151-0.007-0.151L20.495,15.227c-0.025,0.007-0.046-0.019-0.071-0.011c-0.087,0.028-0.172,0.041-0.253,0.083 c-0.054,0.027-0.102,0.053-0.152,0.085c-0.051,0.033-0.101,0.061-0.147,0.099c-0.044,0.036-0.084,0.073-0.124,0.113 c-0.048,0.048-0.093,0.098-0.136,0.152c-0.03,0.039-0.059,0.076-0.085,0.117c-0.046,0.07-0.084,0.145-0.12,0.223 c-0.011,0.023-0.027,0.042-0.036,0.066L2.911,57.664C2.891,57.715,3,57.768,3,57.82v0.002c0,0.186,0,0.375,0,0.562 c0,0.004,0,0.004,0,0.008c0,0,0,0,0,0.002c0,0,0,0,0,0.004v0.004v0.002c0,0.074-0.002,0.15,0.012,0.223 C3.015,58.631,3,58.631,3,58.633c0,0.004,0,0.004,0,0.008c0,0,0,0,0,0.002c0,0,0,0,0,0.004v0.004c0,0,0,0,0,0.002v0.004 c0,0.191-0.046,0.377,0.06,0.545c0-0.002-0.03,0.004-0.03,0.004c0,0.004-0.03,0.004-0.03,0.004c0,0.002,0,0.002,0,0.002 l-0.045,0.004c0.03,0.047,0.036,0.09,0.068,0.133l29.049,37.359c0.002,0.004,0,0.006,0.002,0.01c0.002,0.002,0,0.004,0.002,0.008 c0.006,0.008,0.014,0.014,0.021,0.021c0.024,0.029,0.052,0.051,0.078,0.078c0.027,0.029,0.053,0.057,0.082,0.082 c0.03,0.027,0.055,0.062,0.086,0.088c0.026,0.02,0.057,0.033,0.084,0.053c0.04,0.027,0.081,0.053,0.123,0.076 c0.005,0.004,0.01,0.008,0.016,0.01c0.087,0.051,0.176,0.09,0.269,0.123c0.042,0.014,0.082,0.031,0.125,0.043 c0.021,0.006,0.041,0.018,0.062,0.021c0.123,0.027,0.249,0.043,0.375,0.043c0.099,0,0.202-0.012,0.304-0.027l45.669-8.303 c0.057-0.01,0.108-0.021,0.163-0.037C79.547,88.992,79.562,89,79.575,89c0.004,0,0.004,0,0.004,0c0.021,0,0.039-0.027,0.06-0.035 c0.041-0.014,0.08-0.034,0.12-0.052c0.021-0.01,0.044-0.019,0.064-0.03c0.017-0.01,0.026-0.015,0.033-0.017 c0.014-0.008,0.023-0.021,0.037-0.028c0.14-0.078,0.269-0.174,0.38-0.285c0.014-0.016,0.024-0.034,0.038-0.048 c0.109-0.119,0.201-0.252,0.271-0.398c0.006-0.01,0.016-0.018,0.021-0.029c0.004-0.008,0.008-0.017,0.011-0.026 c0.002-0.004,0.003-0.006,0.005-0.01C80.627,88.021,80.635,88.002,80.644,87.982z M77.611,84.461L48.805,66.453l32.407-25.202 L77.611,84.461z M46.817,63.709L35.863,23.542l43.818,14.608L46.817,63.709z M84.668,40.542l8.926,5.952l-11.902,29.75 L84.668,40.542z M89.128,39.446L84.53,36.38l-6.129-12.257L89.128,39.446z M79.876,34.645L37.807,20.622L65.854,6.599L79.876,34.645 z M33.268,19.107l-6.485-2.162l23.781-6.487L33.268,19.107z M21.92,18.895l8.67,2.891L10.357,47.798L21.92,18.895z M32.652,24.649 l10.845,39.757L7.351,57.178L32.652,24.649z M43.472,67.857L32.969,92.363L8.462,60.855L43.472,67.857z M46.631,69.09l27.826,17.393 l-38.263,6.959L46.631,69.09z"></path></svg>;
|
||||
};
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
|
||||
module.exports = function(props){
|
||||
return <svg version='1.1' x='0px' y='0px' viewBox='0 0 100 100' enableBackground='new 0 0 100 100'><path d='M80.644,87.982l16.592-41.483c0.054-0.128,0.088-0.26,0.108-0.394c0.006-0.039,0.007-0.077,0.011-0.116 c0.007-0.087,0.008-0.174,0.002-0.26c-0.003-0.046-0.007-0.091-0.014-0.137c-0.014-0.089-0.036-0.176-0.063-0.262 c-0.012-0.034-0.019-0.069-0.031-0.103c-0.047-0.118-0.106-0.229-0.178-0.335c-0.004-0.006-0.006-0.012-0.01-0.018L67.999,3.358 c-0.01-0.013-0.003-0.026-0.013-0.04L68,3.315V4c0,0-0.033,0-0.037,0c-0.403-1-1.094-1.124-1.752-0.976 c0,0.004-0.004-0.012-0.007-0.012C66.201,3.016,66.194,3,66.194,3H66.19h-0.003h-0.003h-0.004h-0.003c0,0-0.004,0-0.007,0 s-0.003-0.151-0.007-0.151L20.495,15.227c-0.025,0.007-0.046-0.019-0.071-0.011c-0.087,0.028-0.172,0.041-0.253,0.083 c-0.054,0.027-0.102,0.053-0.152,0.085c-0.051,0.033-0.101,0.061-0.147,0.099c-0.044,0.036-0.084,0.073-0.124,0.113 c-0.048,0.048-0.093,0.098-0.136,0.152c-0.03,0.039-0.059,0.076-0.085,0.117c-0.046,0.07-0.084,0.145-0.12,0.223 c-0.011,0.023-0.027,0.042-0.036,0.066L2.911,57.664C2.891,57.715,3,57.768,3,57.82v0.002c0,0.186,0,0.375,0,0.562 c0,0.004,0,0.004,0,0.008c0,0,0,0,0,0.002c0,0,0,0,0,0.004v0.004v0.002c0,0.074-0.002,0.15,0.012,0.223 C3.015,58.631,3,58.631,3,58.633c0,0.004,0,0.004,0,0.008c0,0,0,0,0,0.002c0,0,0,0,0,0.004v0.004c0,0,0,0,0,0.002v0.004 c0,0.191-0.046,0.377,0.06,0.545c0-0.002-0.03,0.004-0.03,0.004c0,0.004-0.03,0.004-0.03,0.004c0,0.002,0,0.002,0,0.002 l-0.045,0.004c0.03,0.047,0.036,0.09,0.068,0.133l29.049,37.359c0.002,0.004,0,0.006,0.002,0.01c0.002,0.002,0,0.004,0.002,0.008 c0.006,0.008,0.014,0.014,0.021,0.021c0.024,0.029,0.052,0.051,0.078,0.078c0.027,0.029,0.053,0.057,0.082,0.082 c0.03,0.027,0.055,0.062,0.086,0.088c0.026,0.02,0.057,0.033,0.084,0.053c0.04,0.027,0.081,0.053,0.123,0.076 c0.005,0.004,0.01,0.008,0.016,0.01c0.087,0.051,0.176,0.09,0.269,0.123c0.042,0.014,0.082,0.031,0.125,0.043 c0.021,0.006,0.041,0.018,0.062,0.021c0.123,0.027,0.249,0.043,0.375,0.043c0.099,0,0.202-0.012,0.304-0.027l45.669-8.303 c0.057-0.01,0.108-0.021,0.163-0.037C79.547,88.992,79.562,89,79.575,89c0.004,0,0.004,0,0.004,0c0.021,0,0.039-0.027,0.06-0.035 c0.041-0.014,0.08-0.034,0.12-0.052c0.021-0.01,0.044-0.019,0.064-0.03c0.017-0.01,0.026-0.015,0.033-0.017 c0.014-0.008,0.023-0.021,0.037-0.028c0.14-0.078,0.269-0.174,0.38-0.285c0.014-0.016,0.024-0.034,0.038-0.048 c0.109-0.119,0.201-0.252,0.271-0.398c0.006-0.01,0.016-0.018,0.021-0.029c0.004-0.008,0.008-0.017,0.011-0.026 c0.002-0.004,0.003-0.006,0.005-0.01C80.627,88.021,80.635,88.002,80.644,87.982z M77.611,84.461L48.805,66.453l32.407-25.202 L77.611,84.461z M46.817,63.709L35.863,23.542l43.818,14.608L46.817,63.709z M84.668,40.542l8.926,5.952l-11.902,29.75 L84.668,40.542z M89.128,39.446L84.53,36.38l-6.129-12.257L89.128,39.446z M79.876,34.645L37.807,20.622L65.854,6.599L79.876,34.645 z M33.268,19.107l-6.485-2.162l23.781-6.487L33.268,19.107z M21.92,18.895l8.67,2.891L10.357,47.798L21.92,18.895z M32.652,24.649 l10.845,39.757L7.351,57.178L32.652,24.649z M43.472,67.857L32.969,92.363L8.462,60.855L43.472,67.857z M46.631,69.09l27.826,17.393 l-38.263,6.959L46.631,69.09z'></path></svg>;
|
||||
};
|
||||
7
tests/basic.test.js
Normal file
7
tests/basic.test.js
Normal file
@@ -0,0 +1,7 @@
|
||||
const test = require('pico-check');
|
||||
|
||||
test('Just setting up a spot for future tests', (t)=>{
|
||||
t.pass();
|
||||
});
|
||||
|
||||
module.exports = test;
|
||||
1
tests/test.init.js
Normal file
1
tests/test.init.js
Normal file
@@ -0,0 +1 @@
|
||||
//Set up configs and DB connectiosna nd what not in here
|
||||
Reference in New Issue
Block a user