mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2026-01-25 11:53:02 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f18663cff3 | ||
|
|
5fb6593217 | ||
|
|
ec968f47da | ||
|
|
92e110ba15 |
11
.github/issue_template.md
vendored
11
.github/issue_template.md
vendored
@@ -1,5 +1,14 @@
|
|||||||
Share link to issue brew: http://homebrewery.naturalcrit.com/share/XXXXXXX
|
**Browser Type/Version**: [Google Ultron v90.01]
|
||||||
|
|
||||||
|
**Operating System**: [GLaDOS v34.5.8]
|
||||||
|
|
||||||
|
**Issue Description**: [The thing won't thing]
|
||||||
|
|
||||||
|
**Markdown code to reproduce**:
|
||||||
|
|
||||||
|
```
|
||||||
|
# thing
|
||||||
|
> thing 2
|
||||||
|
```
|
||||||
|
|
||||||
|
**Related Images** :
|
||||||
|
|||||||
32
.gitignore
vendored
32
.gitignore
vendored
@@ -1,16 +1,16 @@
|
|||||||
# Logs
|
# Logs
|
||||||
logs
|
logs
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
#Ignore our built files
|
#Ignore our built files
|
||||||
build/*
|
build/*
|
||||||
|
architecture.json
|
||||||
# Ignore sensitive stuff
|
|
||||||
config/local.json
|
# Ignore sensitive stuff
|
||||||
|
/config/*
|
||||||
node_modules
|
!/config/default.json
|
||||||
storage
|
|
||||||
.idea
|
node_modules
|
||||||
*.swp
|
storage
|
||||||
|
.idea
|
||||||
todo.md
|
*.swp
|
||||||
|
|||||||
29
Dockerfile
Normal file
29
Dockerfile
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
FROM node:latest
|
||||||
|
|
||||||
|
MAINTAINER David Hudson <jendave@yahoo.com>
|
||||||
|
|
||||||
|
# System update
|
||||||
|
RUN apt-get -q -y update
|
||||||
|
|
||||||
|
RUN apt-get -q -y install npm
|
||||||
|
RUN apt-get -q -y install mongodb
|
||||||
|
|
||||||
|
RUN apt-get clean && rm -r /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
EXPOSE 22
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
ADD start.sh /start.sh
|
||||||
|
RUN chmod +x /start.sh
|
||||||
|
|
||||||
|
VOLUME ["/opt/apps"]
|
||||||
|
COPY . /opt/apps/naturalcrit/
|
||||||
|
WORKDIR /opt/apps/naturalcrit/
|
||||||
|
|
||||||
|
RUN npm install
|
||||||
|
RUN npm install -g gulp-cli
|
||||||
|
RUN npm install gulp
|
||||||
|
RUN gulp fresh
|
||||||
|
|
||||||
|
CMD ["/start.sh"]
|
||||||
|
|
||||||
66
README.md
66
README.md
@@ -1,33 +1,33 @@
|
|||||||
# The Homebrewery
|
# NaturalCrit
|
||||||
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).
|
A tool suite for DMs to use for D&D. Check it out [here](http://www.naturalcrit.com).
|
||||||
|
|
||||||
|
|
||||||
### issues, suggestions, bugs
|
### Getting started
|
||||||
If you run into any issues using The Homebrewery, please submit an issues [here](/issues)
|
1. Make sure you have [node](https://nodejs.org/en/)
|
||||||
|
1. Clone down the repo
|
||||||
|
1. In your terminal, head to the repo
|
||||||
### local dev
|
1. Run `npm install` to get all the dependacies
|
||||||
Homebrewery is open source, so feel free to clone it, tinker with it, or run your own local version.
|
2. Run `npm install -g gulp` to install the gulp build tool
|
||||||
|
1. Run `gulp fresh`, this will compile and build all the needed libraries (this only has to be done once, unless you add more libs)
|
||||||
#### pre-reqs
|
1. Run `gulp` to run the project locally. Should be accessible at `localhost:8000`
|
||||||
1. install [node](https://nodejs.org/en/)
|
2. Any changes to files within the proejct will be detected and the propject will automatically re-build
|
||||||
1. install [mongodb](https://www.mongodb.com/)
|
|
||||||
|
**Notes:** If you'd like to create and edit homebrews, you'll need to have MongoDB installed and running.
|
||||||
#### getting started
|
|
||||||
1. clone it
|
Have fun!
|
||||||
1. `npm install`
|
|
||||||
1. `npm build`
|
### Docker Image
|
||||||
1. `npm start`
|
You can use [Docker](https://docs.docker.com) to get up and running with NaturalCrit.
|
||||||
|
|
||||||
#### standalone PHB stylesheet
|
1. Install Docker
|
||||||
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)
|
1. Clone the repo
|
||||||
|
1. In the terminal, go to the repo
|
||||||
If you are developing locally and would like to generate your own, follow the above steps and then run `npm run phb`.
|
1. Build the docker image `docker build -t naturalcrit .`
|
||||||
|
1. Run the docker container `docker run -dit -p 8000:8000 naturalcrit`
|
||||||
### changelog
|
1. You can check out the website on your computer on port 8000
|
||||||
|
1. You may have to use `docker-machine env` to get the IP address of your docker instance
|
||||||
You can check out the changelog [here](https://github.com/stolksdorf/homebrewery/blob/master/changelog.md)
|
|
||||||
|
|
||||||
### license
|
### changelog
|
||||||
|
|
||||||
This project is licensed under [MIT](./license)
|
You can check out the changelog [here](https://github.com/stolksdorf/NaturalCrit/blob/master/changelog.md)
|
||||||
|
|||||||
111
changelog.md
Normal file
111
changelog.md
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
# changelog
|
||||||
|
|
||||||
|
### Saturday, 14/05/2016 - v2.0.0 (finally!)
|
||||||
|
|
||||||
|
I've been working on v2 for a *very* long time. I want to thank you guys for being paitent.
|
||||||
|
It started rather small, but as more and more features were added, I decided to just wait until everything was polished.
|
||||||
|
|
||||||
|
Massive changelog incoming:
|
||||||
|
|
||||||
|
#### Brews
|
||||||
|
- New flow for creating new brews. Before creating a new brew would immedaitely create a brew in the database and let you edit it. Many people would create a new brew just to experiment and close it, which lead to many "abandoned brews" (see the Under the hood section below). This started eating up a ton of database space. You now have to manually save a new brew for the first time, however Homebrewery will store anything you don't have saved in local storage, just in case your browser crashes or whatever, it will load that up when you go back to `/homebrew/new`
|
||||||
|
- Black blocking edges around notes and stat blocks when printing to PDFs have been fixed
|
||||||
|
- Borders sometimes not showing up in the second column have been fixed
|
||||||
|
- All pseudo-element borders have been replaced with reliable border images.
|
||||||
|
- Chrome can finally print to PDF as good as Chrome Canary! Updating instructions.
|
||||||
|
- Added a little page number box.
|
||||||
|
- Added in a new editable Brew Title. This will be shown in the navbar on share pages, and will default to the file name when you save as PDF. All exsisting brews will be defaulted with an empty title.
|
||||||
|
- Mutliline lists render better now
|
||||||
|
- Firefox rendering has been slithgly improved. Firefox and Chrome render things **slightly** differently, over the course of a brew, these little changes add up and lead to very noticable rendering differences between the browsers. I'm trying my best to get Firefox rendering better, but it's a difficult problem.
|
||||||
|
- A bunch of you have wanted to donate to me. I am super flattered by that. I created a [patreon page](https://www.patreon.com/stolksdorf). If you feel like helping out, head here :)
|
||||||
|
|
||||||
|
#### Under the Hood Stuff
|
||||||
|
- Setup a proper staging environment. Will be using this for tests, and hopefully getting the community to help me test future versions
|
||||||
|
- Server-side prerendering now much faster
|
||||||
|
- Regular weekly database back-ups. Your brews are safe!
|
||||||
|
- Database is now uniquely indexed on both editId and shareId, page loads/saving should be much faster
|
||||||
|
- Improved Admin console. This helps me answer people's questions about issues with their brews
|
||||||
|
- Added a whole querying/pagniation API that I can use for stats and answering questions
|
||||||
|
- Clearing out "Abandoned" brews (smaller than a tweet and haven't been viewed for a week). These account for nearly a third of all stored brews.
|
||||||
|
|
||||||
|
#### Interface
|
||||||
|
- Added in a whole new editor with syntax highlighting for markdown
|
||||||
|
- Built a splitpane! Remembers where you left the split in between sessions
|
||||||
|
- Re-organized the snippets into a hierarchical groups. Should be much easier to find what you need
|
||||||
|
- Partial page rendering is working. The Homebrewery will now only load the viewable pages, and any page with `<style>` tags on them. If you are working on a large brew you should notice *significant* performance improvements
|
||||||
|
- Edit page saving interface has been improved significantly. Auto-saves after 3 seconds on inactivity, now allows user to save at anytime. Will stop the tab from closing witha pop-up if there are unsaved changes.
|
||||||
|
- Navbar and overall style has been improved and spacing made more consistent
|
||||||
|
- Elements under the hood are way more organized and should behaviour much more reliably in many sizes.
|
||||||
|
- Source now opens to it's own route `/source/:sharedId` instead of just a window. Now easier to share, and won't be blocked by some browsers.
|
||||||
|
- Print page now auto-opens print dialog. If you want to share your print page link, just remove the `?dialog=true` parameter and it won't open the dialog.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
\page
|
||||||
|
|
||||||
|
### Wednesday, 20/04/2016
|
||||||
|
- A lot of admin improvements. Pagninated brew table
|
||||||
|
- Added a searching and removing abandoned brew api endpoints (turns out about 40% of brews are shorter that a tweet!).
|
||||||
|
- Fixed the require cache being cleared. Pages should render a bit faster now.
|
||||||
|
- Pulled in `kkragenbrink`s fix for nested lists, Thanks!
|
||||||
|
|
||||||
|
|
||||||
|
### Wednesday, 06/04/2016 - v1.4
|
||||||
|
* Pages will now partially render. This should greatly speed up *very* large homebrews. The Homebreery will figure out which page you should be looking at and render that page, the page before, and the page after.
|
||||||
|
* Zooming should be fixed. I've changed the font size units to be cm, which match the units of the page. Zooming in and out now look much better.
|
||||||
|
|
||||||
|
|
||||||
|
### Monday, 29/02/2016 - v1.3.1
|
||||||
|
* Removng the changelog button from the Share page
|
||||||
|
* Added a A4 page size snippet (thanks guppy42!)
|
||||||
|
* Added support for `<sup>` and `<sub>` tags (thanks crashinworld14!)
|
||||||
|
|
||||||
|
### Saturday, 20/02/2016
|
||||||
|
* Fixed h1 headers not going full width (thanks McToomin27)
|
||||||
|
* Added a github issue template
|
||||||
|
|
||||||
|
## v1.3.0
|
||||||
|
|
||||||
|
### Friday, 19/02/2016
|
||||||
|
* Improved the admin panel
|
||||||
|
* Added ability to clear away old empty brews
|
||||||
|
* Added delete button to the edit page
|
||||||
|
* Added a dynamically updating changelog page! Nifty!
|
||||||
|
* Added stlying for wide monster stat blocks and single column class tables
|
||||||
|
* Added snippets for wide monster stat blocks and single column class tables
|
||||||
|
|
||||||
|
### Tuesday, 16/02/2016
|
||||||
|
* Paragraphs right after tables now indent (thanks LikeAJi6!)
|
||||||
|
* Added a `@page` css rule to auto turn off margins when printing
|
||||||
|
* Added a `page-break` property on each `.phb` page to properly page the pages up when exporting (thanks Jokefury!)
|
||||||
|
* Improved first character rendering on Firefox
|
||||||
|
* Improved table spacing a bit
|
||||||
|
* Changed padding at page bottom for better fit and clipping of elements
|
||||||
|
* Improved spacing for bold text (thanks nickpunt!)
|
||||||
|
|
||||||
|
|
||||||
|
## v1.2.0
|
||||||
|
|
||||||
|
### Sunday, 17/01/2016
|
||||||
|
* Added a printer friendly snippet that injects some CSS to remove backbrounds and images
|
||||||
|
* Adjusted the styling specific to spell blocks to give it tighter spacing
|
||||||
|
* Added a changelog! How meta!
|
||||||
|
|
||||||
|
## v1.1.0
|
||||||
|
|
||||||
|
### Thursday, 14/01/2016
|
||||||
|
* Added view source to see the markdown that made the page
|
||||||
|
* Added print view
|
||||||
|
* Fixed API issues that were causing the server to crash
|
||||||
|
* Increased padding on table cells
|
||||||
|
* Raw html now shows in view source
|
||||||
|
|
||||||
|
|
||||||
|
## v1.0.0 - Release
|
||||||
|
|
||||||
|
### Wednesday, 3/01/2016
|
||||||
|
|
||||||
|
* Added `phb.standalone.css` plus a build system for creating it
|
||||||
|
* Added page numbers and footer text
|
||||||
|
* Page accent now flips each page
|
||||||
|
|
||||||
@@ -1,42 +1,41 @@
|
|||||||
const React = require('react');
|
var React = require('react');
|
||||||
const _ = require('lodash');
|
var _ = require('lodash');
|
||||||
|
var cx = require('classnames');
|
||||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
|
||||||
|
var HomebrewAdmin = require('./homebrewAdmin/homebrewAdmin.jsx');
|
||||||
const BrewLookup = require('./brewLookup/brewLookup.jsx');
|
|
||||||
const AdminSearch = require('./adminSearch/adminSearch.jsx');
|
var Admin = React.createClass({
|
||||||
const InvalidBrew = require('./invalidBrew/invalidBrew.jsx');
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
const Admin = React.createClass({
|
url : "",
|
||||||
getDefaultProps: function() {
|
admin_key : "",
|
||||||
return {
|
homebrews : [],
|
||||||
admin_key : '',
|
};
|
||||||
};
|
},
|
||||||
},
|
|
||||||
|
render : function(){
|
||||||
renderNavbar : function(){
|
var self = this;
|
||||||
return <Nav.base>
|
return(
|
||||||
<Nav.section>
|
<div className='admin'>
|
||||||
<Nav.item icon='fa-magic' className='homebreweryLogo'>
|
|
||||||
Homebrewery Admin
|
<header>
|
||||||
</Nav.item>
|
<div className='container'>
|
||||||
</Nav.section>
|
<i className='fa fa-rocket' />
|
||||||
</Nav.base>
|
naturalcrit admin
|
||||||
},
|
</div>
|
||||||
|
</header>
|
||||||
render : function(){
|
|
||||||
return <div className='admin'>
|
<div className='container'>
|
||||||
{this.renderNavbar()}
|
|
||||||
<main className='content'>
|
<a target="_blank" href='https://www.google.com/analytics/web/?hl=en#report/defaultid/a72212009w109843310p114529111/'>Link to Google Analytics</a>
|
||||||
<BrewLookup adminKey={this.props.admin_key} />
|
|
||||||
<AdminSearch adminKey={this.props.admin_key} />
|
<HomebrewAdmin homebrews={this.props.homebrews} admin_key={this.props.admin_key} />
|
||||||
|
</div>
|
||||||
<div className='dangerZone'>Danger Zone</div>
|
|
||||||
|
|
||||||
<InvalidBrew adminKey={this.props.admin_key} />
|
</div>
|
||||||
</main>
|
);
|
||||||
</div>
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
|
module.exports = Admin;
|
||||||
module.exports = Admin;
|
|
||||||
|
|||||||
@@ -1,53 +1,39 @@
|
|||||||
|
@import 'naturalcrit/styles/reset.less';
|
||||||
@import 'naturalcrit/styles/core.less';
|
@import 'naturalcrit/styles/elements.less';
|
||||||
html,body, #reactRoot{
|
@import 'naturalcrit/styles/animations.less';
|
||||||
min-height : 100%;
|
@import 'naturalcrit/styles/colors.less';
|
||||||
}
|
@import 'naturalcrit/styles/tooltip.less';
|
||||||
body{
|
|
||||||
height : 100%;
|
@import 'font-awesome/css/font-awesome.css';
|
||||||
margin : 0;
|
|
||||||
padding : 0;
|
html,body, #reactContainer, .naturalCrit{
|
||||||
background-color : #ddd;
|
min-height : 100%;
|
||||||
font-family : 'Open Sans', sans-serif;
|
}
|
||||||
font-weight : 100;
|
|
||||||
color : #4b5055;
|
@sidebarWidth : 250px;
|
||||||
text-rendering : optimizeLegibility;
|
|
||||||
}
|
body{
|
||||||
.admin {
|
background-color : #eee;
|
||||||
nav {
|
font-family : 'Open Sans', sans-serif;
|
||||||
background-color : @red;
|
color : #4b5055;
|
||||||
.navItem{
|
font-weight : 100;
|
||||||
background-color : @red;
|
text-rendering : optimizeLegibility;
|
||||||
}
|
margin : 0;
|
||||||
.homebreweryLogo{
|
padding : 0;
|
||||||
font-family : CodeBold;
|
height : 100%;
|
||||||
font-size : 12px;
|
}
|
||||||
color : white;
|
|
||||||
div{
|
.admin{
|
||||||
margin-top : 2px;
|
|
||||||
margin-bottom : -2px;
|
header{
|
||||||
}
|
background-color : @red;
|
||||||
}
|
font-size: 2em;
|
||||||
}
|
padding : 20px 0px;
|
||||||
h1{
|
color : white;
|
||||||
margin-bottom : 10px;
|
margin-bottom: 30px;
|
||||||
font-size : 2em;
|
i{
|
||||||
font-weight : 800;
|
margin-right: 30px;
|
||||||
border-bottom : 1px solid #ddd;
|
}
|
||||||
}
|
}
|
||||||
main.content{
|
|
||||||
width : 1000px;
|
|
||||||
margin : 0 auto;
|
|
||||||
padding : 50px 20px;
|
|
||||||
background-color : white;
|
|
||||||
.dangerZone{
|
|
||||||
margin : 30px 0px;
|
|
||||||
padding : 10px 20px;
|
|
||||||
background : repeating-linear-gradient(45deg, @yellow, @yellow 10px, darken(#333, 10%) 10px, darken(#333, 10%) 20px);
|
|
||||||
font-size : 1em;
|
|
||||||
font-weight : 800;
|
|
||||||
color : white;
|
|
||||||
text-transform : uppercase;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
|
|
||||||
const React = require('react');
|
|
||||||
const _ = require('lodash');
|
|
||||||
const cx = require('classnames');
|
|
||||||
const request = require('superagent');
|
|
||||||
|
|
||||||
const BrewTable = require('../brewTable/brewTable.jsx');
|
|
||||||
|
|
||||||
const LIMIT = 10;
|
|
||||||
|
|
||||||
const AdminSearch = React.createClass({
|
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
adminKey : '',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
getInitialState: function() {
|
|
||||||
return {
|
|
||||||
totalBrews : 1,
|
|
||||||
brews: [],
|
|
||||||
|
|
||||||
searching : false,
|
|
||||||
error : null,
|
|
||||||
|
|
||||||
page : 1,
|
|
||||||
searchTerms : ''
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
handleSearch : function(e){
|
|
||||||
this.setState({
|
|
||||||
searchTerms : e.target.value
|
|
||||||
});
|
|
||||||
},
|
|
||||||
handlePage : function(e){
|
|
||||||
this.setState({
|
|
||||||
page : e.target.value
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
search : function(){
|
|
||||||
this.setState({ searching : true, error : null });
|
|
||||||
|
|
||||||
request.get(`/api/brew`)
|
|
||||||
.query({
|
|
||||||
terms : this.state.searchTerms,
|
|
||||||
limit : LIMIT,
|
|
||||||
page : this.state.page - 1
|
|
||||||
})
|
|
||||||
.set('x-homebrew-admin', this.props.adminKey)
|
|
||||||
.end((err, res) => {
|
|
||||||
if(err){
|
|
||||||
this.setState({
|
|
||||||
searching : false,
|
|
||||||
error : err && err.toString()
|
|
||||||
});
|
|
||||||
}else{
|
|
||||||
this.setState({
|
|
||||||
brews : res.body.brews,
|
|
||||||
totalBrews : res.body.total
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
render: function(){
|
|
||||||
return <div className='adminSearch'>
|
|
||||||
<h1>Admin Search</h1>
|
|
||||||
<div className='controls'>
|
|
||||||
<input className='search' type='text' value={this.state.searchTerms} onChange={this.handleSearch} />
|
|
||||||
|
|
||||||
<button onClick={this.search}> <i className='fa fa-search' /> search </button>
|
|
||||||
|
|
||||||
|
|
||||||
<div className='page'>
|
|
||||||
page:
|
|
||||||
<input type='text' value={this.state.page} onChange={this.handlePage} />
|
|
||||||
/ {Math.ceil(this.state.totalBrews / LIMIT)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<BrewTable brews={this.state.brews} />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = AdminSearch;
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
|
|
||||||
.adminSearch{
|
|
||||||
.controls{
|
|
||||||
margin-bottom : 20px;
|
|
||||||
input.search{
|
|
||||||
height : 33px;
|
|
||||||
padding : 10px;
|
|
||||||
}
|
|
||||||
.page {
|
|
||||||
float : right;
|
|
||||||
font-weight : 800;
|
|
||||||
input{
|
|
||||||
width : 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
const React = require('react');
|
|
||||||
const _ = require('lodash');
|
|
||||||
const cx = require('classnames');
|
|
||||||
|
|
||||||
const request = require('superagent');
|
|
||||||
const BrewTable = require('../brewTable/brewTable.jsx');
|
|
||||||
|
|
||||||
const BrewLookup = React.createClass({
|
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
adminKey : '',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
getInitialState: function() {
|
|
||||||
return {
|
|
||||||
query:'',
|
|
||||||
resultBrew : null,
|
|
||||||
searching : false,
|
|
||||||
error : null
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
handleChange : function(e){
|
|
||||||
this.setState({
|
|
||||||
query : e.target.value
|
|
||||||
})
|
|
||||||
},
|
|
||||||
lookup : function(){
|
|
||||||
this.setState({ searching : true, error : null });
|
|
||||||
|
|
||||||
request.get(`/admin/lookup/${this.state.query}`)
|
|
||||||
.set('x-homebrew-admin', this.props.adminKey)
|
|
||||||
.end((err, res) => {
|
|
||||||
this.setState({
|
|
||||||
searching : false,
|
|
||||||
error : err && err.toString(),
|
|
||||||
resultBrew : (err ? null : res.body)
|
|
||||||
});
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
renderFoundBrew : function(){
|
|
||||||
if(this.state.searching) return <div className='searching'><i className='fa fa-spin fa-spinner' /></div>;
|
|
||||||
if(!this.state.resultBrew) return <div className='noBrew'>No brew found.</div>;
|
|
||||||
|
|
||||||
return <BrewTable brews={[this.state.resultBrew ]} />
|
|
||||||
|
|
||||||
/*
|
|
||||||
const brew = this.state.resultBrew;
|
|
||||||
return <div className='brewRow'>
|
|
||||||
<div>{brew.title}</div>
|
|
||||||
<div>{brew.authors.join(', ')}</div>
|
|
||||||
<div><a href={'/edit/' + brew.editId} target='_blank'>{brew.editId}</a></div>
|
|
||||||
<div><a href={'/share/' + brew.shareId} target='_blank'>{brew.shareId}</a></div>
|
|
||||||
<div>{Moment(brew.updatedAt).fromNow()}</div>
|
|
||||||
<div>{brew.views}</div>
|
|
||||||
<div>
|
|
||||||
<div className='deleteButton'>
|
|
||||||
<i className='fa fa-trash' />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
*/
|
|
||||||
},
|
|
||||||
|
|
||||||
renderError : function(){
|
|
||||||
if(!this.state.error) return;
|
|
||||||
|
|
||||||
return <div className='error'>
|
|
||||||
{this.state.error}
|
|
||||||
</div>
|
|
||||||
},
|
|
||||||
|
|
||||||
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()}
|
|
||||||
{this.renderError()}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = BrewLookup;
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
|
|
||||||
.brewLookup{
|
|
||||||
height : 200px;
|
|
||||||
input{
|
|
||||||
height : 33px;
|
|
||||||
margin-bottom : 20px;
|
|
||||||
padding : 0px 10px;
|
|
||||||
}
|
|
||||||
.error{
|
|
||||||
font-weight : 800;
|
|
||||||
color : @red;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
const React = require('react');
|
|
||||||
const _ = require('lodash');
|
|
||||||
const cx = require('classnames');
|
|
||||||
|
|
||||||
const Moment = require('moment');
|
|
||||||
|
|
||||||
//TODO: Add in delete
|
|
||||||
|
|
||||||
const BrewTable = React.createClass({
|
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
brews : []
|
|
||||||
};
|
|
||||||
},
|
|
||||||
renderRows : function(){
|
|
||||||
return _.map(this.props.brews, (brew) => {
|
|
||||||
let authors = 'None.';
|
|
||||||
if(brew.authors && brew.authors.length) authors = brew.authors.join(', ');
|
|
||||||
|
|
||||||
return <tr className={cx('brewRow', {'isEmpty' : brew.text == "false"})} key={brew.shareId || brew}>
|
|
||||||
<td>{brew.title}</td>
|
|
||||||
<td>{authors}</td>
|
|
||||||
<td><a href={'/edit/' + brew.editId} target='_blank'>{brew.editId}</a></td>
|
|
||||||
<td><a href={'/share/' + brew.shareId} target='_blank'>{brew.shareId}</a></td>
|
|
||||||
<td>{Moment(brew.updatedAt).fromNow()}</td>
|
|
||||||
<td>{brew.views}</td>
|
|
||||||
<td className='deleteButton'>
|
|
||||||
<i className='fa fa-trash' />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
});
|
|
||||||
},
|
|
||||||
render: function(){
|
|
||||||
return <table className='brewTable'>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Title</th>
|
|
||||||
<th>Authors</th>
|
|
||||||
<th>Edit Link</th>
|
|
||||||
<th>Share Link</th>
|
|
||||||
<th>Last Updated</th>
|
|
||||||
<th>Views</th>
|
|
||||||
<th>Remove</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{this.renderRows()}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = BrewTable;
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
|
|
||||||
table.brewTable{
|
|
||||||
th{
|
|
||||||
padding : 10px;
|
|
||||||
background-color : fade(@blue, 20%);
|
|
||||||
font-weight : 800;
|
|
||||||
}
|
|
||||||
tr:nth-child(even){
|
|
||||||
background-color : fade(@green, 10%);
|
|
||||||
}
|
|
||||||
tr.isEmpty{
|
|
||||||
background-color : fade(@red, 30%);
|
|
||||||
}
|
|
||||||
td{
|
|
||||||
min-width : 100px;
|
|
||||||
padding : 10px;
|
|
||||||
text-align : center;
|
|
||||||
|
|
||||||
/*
|
|
||||||
&.preview{
|
|
||||||
position : relative;
|
|
||||||
&:hover{
|
|
||||||
.content{
|
|
||||||
display : block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.content{
|
|
||||||
position : absolute;
|
|
||||||
display : none;
|
|
||||||
top : 100%;
|
|
||||||
left : 0px;
|
|
||||||
z-index : 1000;
|
|
||||||
max-height : 500px;
|
|
||||||
width : 300px;
|
|
||||||
padding : 30px;
|
|
||||||
background-color : white;
|
|
||||||
font-family : monospace;
|
|
||||||
text-align : left;
|
|
||||||
pointer-events : none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
153
client/admin/homebrewAdmin/homebrewAdmin.jsx
Normal file
153
client/admin/homebrewAdmin/homebrewAdmin.jsx
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
var React = require('react');
|
||||||
|
var _ = require('lodash');
|
||||||
|
var cx = require('classnames');
|
||||||
|
var request = require('superagent');
|
||||||
|
|
||||||
|
var Moment = require('moment');
|
||||||
|
|
||||||
|
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('/homebrew/api/search')
|
||||||
|
.query({
|
||||||
|
admin_key : this.props.admin_key,
|
||||||
|
count : this.state.count,
|
||||||
|
page : page
|
||||||
|
})
|
||||||
|
.end((err, res)=>{
|
||||||
|
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('/homebrew/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('/homebrew/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('/homebrew/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.sharedId}>
|
||||||
|
<td><a href={'/homebrew/edit/' + brew.editId} target='_blank'>{brew.editId}</a></td>
|
||||||
|
<td><a href={'/homebrew/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'>
|
||||||
|
<h2>
|
||||||
|
Homebrews - {this.state.total}
|
||||||
|
</h2>
|
||||||
|
{this.renderPagnination()}
|
||||||
|
{this.renderBrewTable()}
|
||||||
|
|
||||||
|
<button className='clearOldButton' onClick={this.clearInvalidBrews}>
|
||||||
|
Clear Old
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = HomebrewAdmin;
|
||||||
53
client/admin/homebrewAdmin/homebrewAdmin.less
Normal file
53
client/admin/homebrewAdmin/homebrewAdmin.less
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
|
||||||
|
.homebrewAdmin{
|
||||||
|
margin-bottom: 80px;
|
||||||
|
.brewTable{
|
||||||
|
table{
|
||||||
|
|
||||||
|
th{
|
||||||
|
padding : 10px;
|
||||||
|
font-weight : 800;
|
||||||
|
}
|
||||||
|
tr:nth-child(even){
|
||||||
|
background-color : fade(@green, 10%);
|
||||||
|
}
|
||||||
|
tr.isEmpty{
|
||||||
|
background-color : fade(@red, 30%);
|
||||||
|
}
|
||||||
|
td{
|
||||||
|
min-width : 100px;
|
||||||
|
padding : 10px;
|
||||||
|
text-align : center;
|
||||||
|
|
||||||
|
&.preview{
|
||||||
|
position : relative;
|
||||||
|
&:hover{
|
||||||
|
.content{
|
||||||
|
display : block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.content{
|
||||||
|
position : absolute;
|
||||||
|
display : none;
|
||||||
|
top : 100%;
|
||||||
|
left : 0px;
|
||||||
|
z-index : 1000;
|
||||||
|
max-height : 500px;
|
||||||
|
width : 300px;
|
||||||
|
padding : 30px;
|
||||||
|
background-color : white;
|
||||||
|
font-family : monospace;
|
||||||
|
text-align : left;
|
||||||
|
pointer-events : none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.deleteButton{
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
button.clearOldButton{
|
||||||
|
float : right;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
const React = require('react');
|
|
||||||
const _ = require('lodash');
|
|
||||||
const cx = require('classnames');
|
|
||||||
|
|
||||||
const request = require('superagent');
|
|
||||||
const BrewTable = require('../brewTable/brewTable.jsx');
|
|
||||||
|
|
||||||
|
|
||||||
const InvalidBrew = React.createClass({
|
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
adminKey : '',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
getInitialState: function() {
|
|
||||||
return {
|
|
||||||
brews: []
|
|
||||||
};
|
|
||||||
},
|
|
||||||
getInvalid : function(){
|
|
||||||
request.get(`/admin/invalid`)
|
|
||||||
.set('x-homebrew-admin', this.props.adminKey)
|
|
||||||
.end((err, res) => {
|
|
||||||
this.setState({
|
|
||||||
brews : res.body
|
|
||||||
});
|
|
||||||
})
|
|
||||||
},
|
|
||||||
removeInvalid : function(){
|
|
||||||
if(!this.state.brews.length) return;
|
|
||||||
if(!confirm(`Are you sure you want to remove ${this.state.brews.length} brews`)) return;
|
|
||||||
if(!confirm('Sure you are sure?')) return;
|
|
||||||
|
|
||||||
request.delete(`/admin/invalid`)
|
|
||||||
.set('x-homebrew-admin', this.props.adminKey)
|
|
||||||
.end((err, res) => {
|
|
||||||
console.log(err, res.body);
|
|
||||||
alert('Invalid brews removed!');
|
|
||||||
this.getInvalid();
|
|
||||||
})
|
|
||||||
},
|
|
||||||
render: function(){
|
|
||||||
return <div className='invalidBrew'>
|
|
||||||
<h1>Remove Invalid Brews</h1>
|
|
||||||
<div>This will removes all brews older than 3 days and shorter than a tweet.</div>
|
|
||||||
<button className='get' onClick={this.getInvalid}> Get Invalid Brews</button>
|
|
||||||
<button className='remove' disabled={this.state.brews.length == 0} onClick={this.removeInvalid}> Remove invalid Brews</button>
|
|
||||||
|
|
||||||
<BrewTable brews={this.state.brews} />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = InvalidBrew;
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
.invalidBrew{
|
|
||||||
button{
|
|
||||||
margin: 10px 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
93
client/homebrew/brewRenderer/brewRenderer.jsx
Normal file
93
client/homebrew/brewRenderer/brewRenderer.jsx
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
var React = require('react');
|
||||||
|
var _ = require('lodash');
|
||||||
|
var cx = require('classnames');
|
||||||
|
|
||||||
|
var Markdown = require('marked');
|
||||||
|
|
||||||
|
var PAGE_HEIGHT = 1056 + 30;
|
||||||
|
|
||||||
|
var BrewRenderer = React.createClass({
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
text : ''
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getInitialState: function() {
|
||||||
|
return {
|
||||||
|
viewablePageNumber: 0,
|
||||||
|
height : 0
|
||||||
|
};
|
||||||
|
},
|
||||||
|
totalPages : 0,
|
||||||
|
height : 0,
|
||||||
|
|
||||||
|
componentDidMount: function() {
|
||||||
|
this.setState({
|
||||||
|
height : this.refs.main.parentNode.clientHeight
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleScroll : function(e){
|
||||||
|
this.setState({
|
||||||
|
viewablePageNumber : Math.floor(e.target.scrollTop / PAGE_HEIGHT)
|
||||||
|
});
|
||||||
|
},
|
||||||
|
//Implement later
|
||||||
|
scrollToPage : function(pageNumber){
|
||||||
|
},
|
||||||
|
|
||||||
|
shouldRender : function(pageText, index){
|
||||||
|
var viewIndex = this.state.viewablePageNumber;
|
||||||
|
if(index == viewIndex - 1) return true;
|
||||||
|
if(index == viewIndex) return true;
|
||||||
|
if(index == viewIndex + 1) return true;
|
||||||
|
|
||||||
|
//Check for style tages
|
||||||
|
if(pageText.indexOf('<style>') !== -1) return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
renderPageInfo : function(){
|
||||||
|
return <div className='pageInfo'>
|
||||||
|
{this.state.viewablePageNumber + 1} / {this.totalPages}
|
||||||
|
</div>
|
||||||
|
},
|
||||||
|
|
||||||
|
renderDummyPage : function(key){
|
||||||
|
return <div className='phb' key={key}>
|
||||||
|
<i className='fa fa-spinner fa-spin' />
|
||||||
|
</div>
|
||||||
|
},
|
||||||
|
|
||||||
|
renderPage : function(pageText, index){
|
||||||
|
return <div className='phb' dangerouslySetInnerHTML={{__html:Markdown(pageText)}} key={index} />
|
||||||
|
},
|
||||||
|
|
||||||
|
renderPages : function(){
|
||||||
|
var pages = this.props.text.split('\\page');
|
||||||
|
this.totalPages = pages.length;
|
||||||
|
|
||||||
|
return _.map(pages, (page, index)=>{
|
||||||
|
if(this.shouldRender(page, index)){
|
||||||
|
return this.renderPage(page, index);
|
||||||
|
}else{
|
||||||
|
return this.renderDummyPage(index);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
render : function(){
|
||||||
|
return <div className='brewRenderer'
|
||||||
|
onScroll={this.handleScroll}
|
||||||
|
ref='main'
|
||||||
|
style={{height : this.state.height}}>
|
||||||
|
|
||||||
|
<div className='pages'>
|
||||||
|
{this.renderPages()}
|
||||||
|
</div>
|
||||||
|
{this.renderPageInfo()}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = BrewRenderer;
|
||||||
@@ -1,39 +1,28 @@
|
|||||||
|
|
||||||
@import (less) './shared/homebrewery/phb_style/phb.less';
|
@import (less) 'naturalcrit/phbStyle/phb.style.less';
|
||||||
.pane{
|
.pane{
|
||||||
position : relative;
|
position : relative;
|
||||||
}
|
}
|
||||||
.brewRenderer{
|
.brewRenderer{
|
||||||
overflow-y : scroll;
|
overflow-y : scroll;
|
||||||
.pageInfo{
|
.pageInfo{
|
||||||
position : absolute;
|
position : absolute;
|
||||||
right : 17px;
|
right : 17px;
|
||||||
bottom : 0;
|
bottom : 0;
|
||||||
z-index : 1000;
|
z-index : 1000;
|
||||||
padding : 8px 10px;
|
padding : 8px 10px;
|
||||||
background-color : #333;
|
background-color : #333;
|
||||||
font-size : 10px;
|
font-size : 10px;
|
||||||
font-weight : 800;
|
font-weight : 800;
|
||||||
color : white;
|
color : white;
|
||||||
}
|
}
|
||||||
.ppr_msg{
|
.pages{
|
||||||
position : absolute;
|
margin : 30px 0px;
|
||||||
left : 0px;
|
&>.phb{
|
||||||
bottom : 0;
|
margin-right : auto;
|
||||||
z-index : 1000;
|
margin-bottom : 30px;
|
||||||
padding : 8px 10px;
|
margin-left : auto;
|
||||||
background-color : #333;
|
box-shadow : 1px 4px 14px #000;
|
||||||
font-size : 10px;
|
}
|
||||||
font-weight : 800;
|
}
|
||||||
color : white;
|
|
||||||
}
|
|
||||||
.pages{
|
|
||||||
margin : 30px 0px;
|
|
||||||
&>.phb{
|
|
||||||
margin-right : auto;
|
|
||||||
margin-bottom : 30px;
|
|
||||||
margin-left : auto;
|
|
||||||
box-shadow : 1px 4px 14px #000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
129
client/homebrew/editor/editor.jsx
Normal file
129
client/homebrew/editor/editor.jsx
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
var React = require('react');
|
||||||
|
var _ = require('lodash');
|
||||||
|
var cx = require('classnames');
|
||||||
|
|
||||||
|
var CodeEditor = require('naturalcrit/codeEditor/codeEditor.jsx');
|
||||||
|
var Snippets = require('./snippets/snippets.js');
|
||||||
|
|
||||||
|
|
||||||
|
var splice = function(str, index, inject){
|
||||||
|
return str.slice(0, index) + inject + str.slice(index);
|
||||||
|
};
|
||||||
|
var execute = function(val){
|
||||||
|
if(_.isFunction(val)) return val();
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var Editor = React.createClass({
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
value : "",
|
||||||
|
onChange : function(){}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
cursorPosition : {
|
||||||
|
line : 0,
|
||||||
|
ch : 0
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidMount: function() {
|
||||||
|
var paneHeight = this.refs.main.parentNode.clientHeight;
|
||||||
|
paneHeight -= this.refs.snippetBar.clientHeight + 1;
|
||||||
|
this.refs.codeEditor.codeMirror.setSize(null, paneHeight);
|
||||||
|
},
|
||||||
|
|
||||||
|
handleTextChange : function(text){
|
||||||
|
this.props.onChange(text);
|
||||||
|
},
|
||||||
|
handleCursorActivty : function(curpos){
|
||||||
|
this.cursorPosition = curpos;
|
||||||
|
},
|
||||||
|
|
||||||
|
handleSnippetClick : function(injectText){
|
||||||
|
var 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);
|
||||||
|
},
|
||||||
|
|
||||||
|
//Called when there are changes to the editor's dimensions
|
||||||
|
update : function(){
|
||||||
|
this.refs.codeEditor.updateSize();
|
||||||
|
},
|
||||||
|
|
||||||
|
renderSnippetGroups : function(){
|
||||||
|
return _.map(Snippets, (snippetGroup)=>{
|
||||||
|
return <SnippetGroup
|
||||||
|
groupName={snippetGroup.groupName}
|
||||||
|
icon={snippetGroup.icon}
|
||||||
|
snippets={snippetGroup.snippets}
|
||||||
|
key={snippetGroup.groupName}
|
||||||
|
onSnippetClick={this.handleSnippetClick}
|
||||||
|
/>
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
render : function(){
|
||||||
|
return(
|
||||||
|
<div className='editor' ref='main'>
|
||||||
|
<div className='snippetBar' ref='snippetBar'>
|
||||||
|
{this.renderSnippetGroups()}
|
||||||
|
</div>
|
||||||
|
<CodeEditor
|
||||||
|
ref='codeEditor'
|
||||||
|
wrap={true}
|
||||||
|
language='gfm'
|
||||||
|
value={this.props.value}
|
||||||
|
onChange={this.handleTextChange}
|
||||||
|
onCursorActivity={this.handleCursorActivty} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = Editor;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var SnippetGroup = React.createClass({
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
groupName : '',
|
||||||
|
icon : 'fa-rocket',
|
||||||
|
snippets : [],
|
||||||
|
onSnippetClick : function(){},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
handleSnippetClick : function(snippet){
|
||||||
|
this.props.onSnippetClick(execute(snippet.gen));
|
||||||
|
},
|
||||||
|
renderSnippets : function(){
|
||||||
|
return _.map(this.props.snippets, (snippet)=>{
|
||||||
|
return <div className='snippet' key={snippet.name} onClick={this.handleSnippetClick.bind(this, snippet)}>
|
||||||
|
<i className={'fa fa-fw ' + snippet.icon} />
|
||||||
|
{snippet.name}
|
||||||
|
</div>
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
render : function(){
|
||||||
|
return <div className='snippetGroup'>
|
||||||
|
<div className='text'>
|
||||||
|
<i className={'fa fa-fw ' + this.props.icon} />
|
||||||
|
<span className='groupName'>{this.props.groupName}</span>
|
||||||
|
</div>
|
||||||
|
<div className='dropdown'>
|
||||||
|
{this.renderSnippets()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
},
|
||||||
|
|
||||||
|
});
|
||||||
56
client/homebrew/editor/editor.less
Normal file
56
client/homebrew/editor/editor.less
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
|
||||||
|
.editor{
|
||||||
|
position : relative;
|
||||||
|
width : 100%;
|
||||||
|
.snippetBar{
|
||||||
|
display : flex;
|
||||||
|
padding : 5px;
|
||||||
|
background-color : #ddd;
|
||||||
|
align-items : center;
|
||||||
|
.snippetGroup{
|
||||||
|
.animate(background-color);
|
||||||
|
margin : 0px 8px;
|
||||||
|
padding : 3px;
|
||||||
|
font-size : 13px;
|
||||||
|
border-radius : 5px;
|
||||||
|
&:hover, &.selected{
|
||||||
|
background-color : #999;
|
||||||
|
}
|
||||||
|
.text{
|
||||||
|
line-height : 20px;
|
||||||
|
.groupName{
|
||||||
|
margin-left : 6px;
|
||||||
|
font-size : 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:hover{
|
||||||
|
.dropdown{
|
||||||
|
visibility : visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.dropdown{
|
||||||
|
position : absolute;
|
||||||
|
visibility : hidden;
|
||||||
|
z-index : 1000;
|
||||||
|
padding : 5px;
|
||||||
|
background-color : #ddd;
|
||||||
|
.snippet{
|
||||||
|
.animate(background-color);
|
||||||
|
padding : 10px;
|
||||||
|
cursor : pointer;
|
||||||
|
font-size : 10px;
|
||||||
|
i{
|
||||||
|
margin-right: 8px;
|
||||||
|
font-size : 13px;
|
||||||
|
}
|
||||||
|
&:hover{
|
||||||
|
background-color : #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.codeEditor{
|
||||||
|
height : 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,42 +1,42 @@
|
|||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
|
|
||||||
module.exports = function(classname){
|
module.exports = function(classname){
|
||||||
|
|
||||||
classname = classname || _.sample(['archivist', 'fancyman', 'linguist', 'fletcher',
|
classname = classname || _.sample(['archivist', 'fancyman', 'linguist', 'fletcher',
|
||||||
'notary', 'berserker-typist', 'fishmongerer', 'manicurist', 'haberdasher', 'concierge'])
|
'notary', 'berserker-typist', 'fishmongerer', 'manicurist', 'haberdasher', 'concierge'])
|
||||||
|
|
||||||
classname = classname.toLowerCase();
|
classname = classname.toLowerCase();
|
||||||
|
|
||||||
var hitDie = _.sample([4, 6, 8, 10, 12]);
|
var hitDie = _.sample([4, 6, 8, 10, 12]);
|
||||||
|
|
||||||
var abilityList = ["Strength", "Dexerity", "Constitution", "Wisdom", "Charisma", "Intelligence"];
|
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"];
|
var skillList = ["Acrobatics ", "Animal Handling", "Arcana", "Athletics", "Deception", "History", "Insight", "Intimidation", "Investigation", "Medicine", "Nature", "Perception", "Performance", "Persuasion", "Religion", "Sleight of Hand", "Stealth", "Survival"];
|
||||||
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
"## Class Features",
|
"## Class Features",
|
||||||
"As a " + classname + ", you gain the following class features",
|
"As a " + classname + ", you gain the following class features",
|
||||||
"#### Hit Points",
|
"#### Hit Points",
|
||||||
"___",
|
"___",
|
||||||
"- **Hit Dice:** 1d" + hitDie + " per " + classname + " level",
|
"- **Hit Dice:** 1d" + hitDie + " per " + classname + " level",
|
||||||
"- **Hit Points at 1st Level:** " + hitDie + " + your Constitution modifier",
|
"- **Hit Points at 1st Level:** " + hitDie + " + your Constituion modifier",
|
||||||
"- **Hit Points at Higher Levels:** 1d" + hitDie + " (or " + (hitDie/2 + 1) + ") + your Constitution modifier per " + classname + " level after 1st",
|
"- **Hit Points at Higher Levels:** 1d" + hitDie + " (or " + (hitDie/2 + 1) + ") + your Constituion modifier per " + classname + " level after 1st",
|
||||||
"",
|
"",
|
||||||
"#### Proficiencies",
|
"#### Proficiencies",
|
||||||
"___",
|
"___",
|
||||||
"- **Armor:** " + (_.sampleSize(["Light armor", "Medium armor", "Heavy armor", "Shields"], _.random(0,3)).join(', ') || "None"),
|
"- **Armor:** " + (_.sampleSize(["Light armor", "Medium armor", "Heavy armor", "Shields"], _.random(0,3)).join(', ') || "None"),
|
||||||
"- **Weapons:** " + (_.sampleSize(["Squeegee", "Rubber Chicken", "Simple weapons", "Martial weapons"], _.random(0,2)).join(', ') || "None"),
|
"- **Weapons:** " + (_.sampleSize(["Squeegee", "Rubber Chicken", "Simple weapons", "Martial weapons"], _.random(0,2)).join(', ') || "None"),
|
||||||
"- **Tools:** " + (_.sampleSize(["Artian's tools", "one musical instrument", "Thieve's tools"], _.random(0,2)).join(', ') || "None"),
|
"- **Tools:** " + (_.sampleSize(["Artian's tools", "one musical instrument", "Thieve's tools"], _.random(0,2)).join(', ') || "None"),
|
||||||
"",
|
"",
|
||||||
"___",
|
"___",
|
||||||
"- **Saving Throws:** " + (_.sampleSize(abilityList, 2).join(', ')),
|
"- **Saving Throws:** " + (_.sampleSize(abilityList, 2).join(', ')),
|
||||||
"- **Skills:** Choose two from " + (_.sampleSize(skillList, _.random(4, 6)).join(', ')),
|
"- **Skills:** Choose two from " + (_.sampleSize(skillList, _.random(4, 6)).join(', ')),
|
||||||
"",
|
"",
|
||||||
"#### Equipment",
|
"#### Equipment",
|
||||||
"You start with the following equipment, in addition to the equipment granted by your background:",
|
"You start with the following equipment, in addition to the equipment granted by your background:",
|
||||||
"- *(a)* a martial weapon and a shield or *(b)* two martial weapons",
|
"- *(a)* a martial weapon and a shield or *(b)* two martial weapons",
|
||||||
"- *(a)* five javelins or *(b)* any simple melee weapon",
|
"- *(a)* five javelins or *(b)* any simple melee weapon",
|
||||||
"- " + (_.sample(["10 lint fluffs", "1 button", "a cherished lost sock"])),
|
"- " + (_.sample(["10 lint fluffs", "1 button", "a cherished lost sock"])),
|
||||||
"\n\n\n"
|
"\n\n\n"
|
||||||
].join('\n');
|
].join('\n');
|
||||||
}
|
}
|
||||||
@@ -1,114 +1,105 @@
|
|||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
|
|
||||||
var features = [
|
var features = [
|
||||||
"Astrological Botany",
|
"Astrological Botany",
|
||||||
"Astrological Chemistry",
|
"Astrological Chemistry",
|
||||||
"Biochemical Sorcery",
|
"Biochemical Sorcery",
|
||||||
"Civil Alchemy",
|
"Civil Alchemy",
|
||||||
"Consecrated Biochemistry",
|
"Consecrated Biochemistry",
|
||||||
"Demonic Anthropology",
|
"Demonic Anthropology",
|
||||||
"Divinatory Mineralogy",
|
"Divinatory Mineralogy",
|
||||||
"Genetic Banishing",
|
"Genetic Banishing",
|
||||||
"Hermetic Geography",
|
"Hermetic Geography",
|
||||||
"Immunological Incantations",
|
"Immunological Incantations",
|
||||||
"Nuclear Illusionism",
|
"Nuclear Illusionism",
|
||||||
"Ritual Astronomy",
|
"Ritual Astronomy",
|
||||||
"Seismological Divination",
|
"Seismological Divination",
|
||||||
"Spiritual Biochemistry",
|
"Spiritual Biochemistry",
|
||||||
"Statistical Occultism",
|
"Statistical Occultism",
|
||||||
"Police Necromancer",
|
"Police Necromancer",
|
||||||
"Sixgun Poisoner",
|
"Sixgun Poisoner",
|
||||||
"Pharmaceutical Gunslinger",
|
"Pharmaceutical Gunslinger",
|
||||||
"Infernal Banker",
|
"Infernal Banker",
|
||||||
"Spell Analyst",
|
"Spell Analyst",
|
||||||
"Gunslinger Corruptor",
|
"Gunslinger Corruptor",
|
||||||
"Torque Interfacer",
|
"Torque Interfacer",
|
||||||
"Exo Interfacer",
|
"Exo Interfacer",
|
||||||
"Gunpowder Torturer",
|
"Gunpowder Torturer",
|
||||||
"Orbital Gravedigger",
|
"Orbital Gravedigger",
|
||||||
"Phased Linguist",
|
"Phased Linguist",
|
||||||
"Mathematical Pharmacist",
|
"Mathematical Pharmacist",
|
||||||
"Plasma Outlaw",
|
"Plasma Outlaw",
|
||||||
"Malefic Chemist",
|
"Malefic Chemist",
|
||||||
"Police Cultist"
|
"Police Cultist"
|
||||||
];
|
];
|
||||||
|
|
||||||
var classnames = ['Archivist', 'Fancyman', 'Linguist', 'Fletcher',
|
var classnames = ['Archivist', 'Fancyman', 'Linguist', 'Fletcher',
|
||||||
'Notary', 'Berserker-Typist', 'Fishmongerer', 'Manicurist', 'Haberdasher', 'Concierge'];
|
'Notary', 'Berserker-Typist', 'Fishmongerer', 'Manicurist', 'Haberdasher', 'Concierge'];
|
||||||
|
|
||||||
var levels = ["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th", "11th", "12th", "13th", "14th", "15th", "16th", "17th", "18th", "19th", "20th"]
|
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];
|
|
||||||
|
module.exports = {
|
||||||
var getFeature = (level)=>{
|
full : function(classname){
|
||||||
var res = []
|
classname = classname || _.sample(classnames)
|
||||||
if(_.includes([4,6,8,12,14,16,19], level+1)){
|
|
||||||
res = ["Ability Score Improvement"]
|
var maxes = [4,3,3,3,3,2,2,1,1]
|
||||||
}
|
var drawSlots = function(Slots){
|
||||||
res = _.union(res, _.sampleSize(features, _.sample([0,1,1,1,1,1])));
|
var slots = Number(Slots);
|
||||||
if(!res.length) return "─";
|
return _.times(9, function(i){
|
||||||
return res.join(', ');
|
var max = maxes[i];
|
||||||
}
|
if(slots < 1) return "—";
|
||||||
|
var res = _.min([max, slots]);
|
||||||
module.exports = {
|
slots -= res;
|
||||||
full : function(){
|
return res;
|
||||||
var classname = _.sample(classnames)
|
}).join(' | ')
|
||||||
|
}
|
||||||
var maxes = [4,3,3,3,3,2,2,1,1]
|
|
||||||
var drawSlots = function(Slots){
|
|
||||||
var slots = Number(Slots);
|
var cantrips = 3;
|
||||||
return _.times(9, function(i){
|
var spells = 1;
|
||||||
var max = maxes[i];
|
var slots = 2;
|
||||||
if(slots < 1) return "—";
|
return "##### The " + classname + "\n" +
|
||||||
var res = _.min([max, slots]);
|
"___\n" +
|
||||||
slots -= res;
|
"| Level | Proficiency Bonus | Features | Cantrips Known | Spells Known | 1st | 2nd | 3rd | 4th | 5th | 6th | 7th | 8th | 9th |\n"+
|
||||||
return res;
|
"|:---:|:---:|:---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|\n" +
|
||||||
}).join(' | ')
|
_.map(levels, function(levelName, level){
|
||||||
}
|
var res = [
|
||||||
|
levelName,
|
||||||
|
"+" + Math.ceil(level/5 + 1),
|
||||||
var cantrips = 3;
|
_.sampleSize(features, _.sample([0,1,1])).join(', ') || "Ability Score Improvement",
|
||||||
var spells = 1;
|
cantrips,
|
||||||
var slots = 2;
|
spells,
|
||||||
return "<div class='classTable wide'>\n##### The " + classname + "\n" +
|
drawSlots(slots)
|
||||||
"| Level | Proficiency Bonus | Features | Cantrips Known | Spells Known | 1st | 2nd | 3rd | 4th | 5th | 6th | 7th | 8th | 9th |\n"+
|
].join(' | ');
|
||||||
"|:---:|:---:|:---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|\n" +
|
|
||||||
_.map(levels, function(levelName, level){
|
cantrips += _.random(0,1);
|
||||||
var res = [
|
spells += _.random(0,1);
|
||||||
levelName,
|
slots += _.random(0,2);
|
||||||
"+" + profBonus[level],
|
|
||||||
getFeature(level),
|
return "| " + res + " |";
|
||||||
cantrips,
|
}).join('\n') +'\n\n';
|
||||||
spells,
|
},
|
||||||
drawSlots(slots)
|
|
||||||
].join(' | ');
|
half : function(classname){
|
||||||
|
classname = classname || _.sample(classnames)
|
||||||
cantrips += _.random(0,1);
|
|
||||||
spells += _.random(0,1);
|
var featureScore = 1
|
||||||
slots += _.random(0,2);
|
return "##### The " + classname + "\n" +
|
||||||
|
"___\n" + "___\n" +
|
||||||
return "| " + res + " |";
|
"| Level | Proficiency Bonus | Features | " + _.sample(features) + "|\n" +
|
||||||
}).join('\n') +'\n</div>\n\n';
|
"|:---:|:---:|:---|:---:|\n" +
|
||||||
},
|
_.map(levels, function(levelName, level){
|
||||||
|
var res = [
|
||||||
half : function(){
|
levelName,
|
||||||
var classname = _.sample(classnames)
|
"+" + Math.ceil(level/5 + 1),
|
||||||
|
_.sampleSize(features, _.sample([0,1,1])).join(', ') || "Ability Score Improvement",
|
||||||
var featureScore = 1
|
"+" + featureScore
|
||||||
return "<div class='classTable'>\n##### The " + classname + "\n" +
|
].join(' | ');
|
||||||
"| Level | Proficiency Bonus | Features | " + _.sample(features) + "|\n" +
|
|
||||||
"|:---:|:---:|:---|:---:|\n" +
|
featureScore += _.random(0,1);
|
||||||
_.map(levels, function(levelName, level){
|
|
||||||
var res = [
|
return "| " + res + " |";
|
||||||
levelName,
|
}).join('\n') +'\n\n';
|
||||||
"+" + profBonus[level],
|
}
|
||||||
getFeature(level),
|
|
||||||
"+" + featureScore
|
|
||||||
].join(' | ');
|
|
||||||
|
|
||||||
featureScore += _.random(0,1);
|
|
||||||
|
|
||||||
return "| " + res + " |";
|
|
||||||
}).join('\n') +'\n</div>\n\n';
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
@@ -1,196 +1,196 @@
|
|||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
|
|
||||||
var genList = function(list, max){
|
var genList = function(list, max){
|
||||||
return _.sampleSize(list, _.random(0,max)).join(', ') || "None";
|
return _.sampleSize(list, _.random(0,max)).join(', ') || "None";
|
||||||
}
|
}
|
||||||
|
|
||||||
var getMonsterName = function(){
|
var getMonsterName = function(){
|
||||||
return _.sample([
|
return _.sample([
|
||||||
"All-devouring Baseball Imp",
|
"All-devouring Baseball Imp",
|
||||||
"All-devouring Gumdrop Wraith",
|
"All-devouring Gumdrop Wraith",
|
||||||
"Chocolate Hydra",
|
"Chocolate Hydra",
|
||||||
"Devouring Peacock",
|
"Devouring Peacock",
|
||||||
"Economy-sized Colossus of the Lemonade Stand",
|
"Economy-sized Colossus of the Lemonade Stand",
|
||||||
"Ghost Pigeon",
|
"Ghost Pigeon",
|
||||||
"Gibbering Duck",
|
"Gibbering Duck",
|
||||||
"Sparklemuffin Peacock Spider",
|
"Sparklemuffin Peacock Spider",
|
||||||
"Gum Elemental",
|
"Gum Elemental",
|
||||||
"Illiterate Construct of the Candy Store",
|
"Illiterate Construct of the Candy Store",
|
||||||
"Ineffable Chihuahua",
|
"Ineffable Chihuahua",
|
||||||
"Irritating Death Hamster",
|
"Irritating Death Hamster",
|
||||||
"Irritating Gold Mouse",
|
"Irritating Gold Mouse",
|
||||||
"Juggernaut Snail",
|
"Juggernaut Snail",
|
||||||
"Juggernaut of the Sock Drawer",
|
"Juggernaut of the Sock Drawer",
|
||||||
"Koala of the Cosmos",
|
"Koala of the Cosmos",
|
||||||
"Mad Koala of the West",
|
"Mad Koala of the West",
|
||||||
"Milk Djinni of the Lemonade Stand",
|
"Milk Djinni of the Lemonade Stand",
|
||||||
"Mind Ferret",
|
"Mind Ferret",
|
||||||
"Mystic Salt Spider",
|
"Mystic Salt Spider",
|
||||||
"Necrotic Halitosis Angel",
|
"Necrotic Halitosis Angel",
|
||||||
"Pinstriped Famine Sheep",
|
"Pinstriped Famine Sheep",
|
||||||
"Ritalin Leech",
|
"Ritalin Leech",
|
||||||
"Shocker Kangaroo",
|
"Shocker Kangaroo",
|
||||||
"Stellar Tennis Juggernaut",
|
"Stellar Tennis Juggernaut",
|
||||||
"Wailing Quail of the Sun",
|
"Wailing Quail of the Sun",
|
||||||
"Angel Pigeon",
|
"Angel Pigeon",
|
||||||
"Anime Sphinx",
|
"Anime Sphinx",
|
||||||
"Bored Avalanche Sheep of the Wasteland",
|
"Bored Avalanche Sheep of the Wasteland",
|
||||||
"Devouring Nougat Sphinx of the Sock Drawer",
|
"Devouring Nougat Sphinx of the Sock Drawer",
|
||||||
"Djinni of the Footlocker",
|
"Djinni of the Footlocker",
|
||||||
"Ectoplasmic Jazz Devil",
|
"Ectoplasmic Jazz Devil",
|
||||||
"Flatuent Angel",
|
"Flatuent Angel",
|
||||||
"Gelatinous Duck of the Dream-Lands",
|
"Gelatinous Duck of the Dream-Lands",
|
||||||
"Gelatinous Mouse",
|
"Gelatinous Mouse",
|
||||||
"Golem of the Footlocker",
|
"Golem of the Footlocker",
|
||||||
"Lich Wombat",
|
"Lich Wombat",
|
||||||
"Mechanical Sloth of the Past",
|
"Mechanical Sloth of the Past",
|
||||||
"Milkshake Succubus",
|
"Milkshake Succubus",
|
||||||
"Puffy Bone Peacock of the East",
|
"Puffy Bone Peacock of the East",
|
||||||
"Rainbow Manatee",
|
"Rainbow Manatee",
|
||||||
"Rune Parrot",
|
"Rune Parrot",
|
||||||
"Sand Cow",
|
"Sand Cow",
|
||||||
"Sinister Vanilla Dragon",
|
"Sinister Vanilla Dragon",
|
||||||
"Snail of the North",
|
"Snail of the North",
|
||||||
"Spider of the Sewer",
|
"Spider of the Sewer",
|
||||||
"Stellar Sawdust Leech",
|
"Stellar Sawdust Leech",
|
||||||
"Storm Anteater of Hell",
|
"Storm Anteater of Hell",
|
||||||
"Stupid Spirit of the Brewery",
|
"Stupid Spirit of the Brewery",
|
||||||
"Time Kangaroo",
|
"Time Kangaroo",
|
||||||
"Tomb Poodle",
|
"Tomb Poodle",
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
var getType = function(){
|
var getType = function(){
|
||||||
return _.sample(['Tiny', 'Small', 'Medium', 'Large', 'Gargantuan', 'Stupidly vast']) + " " + _.sample(['beast', 'fiend', 'annoyance', 'guy', 'cutie'])
|
return _.sample(['Tiny', 'Small', 'Medium', 'Large', 'Gargantuan', 'Stupidly vast']) + " " + _.sample(['beast', 'fiend', 'annoyance', 'guy', 'cutie'])
|
||||||
}
|
}
|
||||||
|
|
||||||
var getAlignment = function(){
|
var getAlignment = function(){
|
||||||
return _.sample([
|
return _.sample([
|
||||||
"annoying evil",
|
"annoying evil",
|
||||||
"chaotic gossipy",
|
"chaotic gossipy",
|
||||||
"chaotic sloppy",
|
"chaotic sloppy",
|
||||||
"depressed neutral",
|
"depressed neutral",
|
||||||
"lawful bogus",
|
"lawful bogus",
|
||||||
"lawful coy",
|
"lawful coy",
|
||||||
"manic-depressive evil",
|
"manic-depressive evil",
|
||||||
"narrow-minded neutral",
|
"narrow-minded neutral",
|
||||||
"neutral annoying",
|
"neutral annoying",
|
||||||
"neutral ignorant",
|
"neutral ignorant",
|
||||||
"oedpipal neutral",
|
"oedpipal neutral",
|
||||||
"silly neutral",
|
"silly neutral",
|
||||||
"unoriginal neutral",
|
"unoriginal neutral",
|
||||||
"weird neutral",
|
"weird neutral",
|
||||||
"wordy evil",
|
"wordy evil",
|
||||||
"unaligned"
|
"unaligned"
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
var getStats = function(){
|
var getStats = function(){
|
||||||
return '>|' + _.times(6, function(){
|
return '>|' + _.times(6, function(){
|
||||||
var num = _.random(1,20);
|
var num = _.random(1,20);
|
||||||
var mod = Math.ceil(num/2 - 5)
|
var mod = Math.ceil(num/2 - 5)
|
||||||
return num + " (" + (mod >= 0 ? '+'+mod : mod ) + ")"
|
return num + " (" + (mod >= 0 ? '+'+mod : mod ) + ")"
|
||||||
}).join('|') + '|';
|
}).join('|') + '|';
|
||||||
}
|
}
|
||||||
|
|
||||||
var genAbilities = function(){
|
var genAbilities = function(){
|
||||||
return _.sample([
|
return _.sample([
|
||||||
"> ***Pack Tactics.*** These guys work together. Like super well, you don't even know.",
|
"> ***Pack Tactics.*** These guys work together. Like super well, you don't even know.",
|
||||||
"> ***False Appearance. *** While the armor reamin motionless, it is indistinguishable from a normal suit of armor.",
|
"> ***False Appearance. *** While the armor reamin motionless, it is indistinguishable from a normal suit of armor.",
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
var genAction = function(){
|
var genAction = function(){
|
||||||
var name = _.sample([
|
var name = _.sample([
|
||||||
"Abdominal Drop",
|
"Abdominal Drop",
|
||||||
"Airplane Hammer",
|
"Airplane Hammer",
|
||||||
"Atomic Death Throw",
|
"Atomic Death Throw",
|
||||||
"Bulldog Rake",
|
"Bulldog Rake",
|
||||||
"Corkscrew Strike",
|
"Corkscrew Strike",
|
||||||
"Crossed Splash",
|
"Crossed Splash",
|
||||||
"Crossface Suplex",
|
"Crossface Suplex",
|
||||||
"DDT Powerbomb",
|
"DDT Powerbomb",
|
||||||
"Dual Cobra Wristlock",
|
"Dual Cobra Wristlock",
|
||||||
"Dual Throw",
|
"Dual Throw",
|
||||||
"Elbow Hold",
|
"Elbow Hold",
|
||||||
"Gory Body Sweep",
|
"Gory Body Sweep",
|
||||||
"Heel Jawbreaker",
|
"Heel Jawbreaker",
|
||||||
"Jumping Driver",
|
"Jumping Driver",
|
||||||
"Open Chin Choke",
|
"Open Chin Choke",
|
||||||
"Scorpion Flurry",
|
"Scorpion Flurry",
|
||||||
"Somersault Stump Fists",
|
"Somersault Stump Fists",
|
||||||
"Suffering Wringer",
|
"Suffering Wringer",
|
||||||
"Super Hip Submission",
|
"Super Hip Submission",
|
||||||
"Super Spin",
|
"Super Spin",
|
||||||
"Team Elbow",
|
"Team Elbow",
|
||||||
"Team Foot",
|
"Team Foot",
|
||||||
"Tilt-a-whirl Chin Sleeper",
|
"Tilt-a-whirl Chin Sleeper",
|
||||||
"Tilt-a-whirl Eye Takedown",
|
"Tilt-a-whirl Eye Takedown",
|
||||||
"Turnbuckle Roll"
|
"Turnbuckle Roll"
|
||||||
])
|
])
|
||||||
|
|
||||||
return "> ***" + name + ".*** *Melee Weapon Attack:* +4 to hit, reach 5ft., one target. *Hit* 5 (1d6 + 2) ";
|
return "> ***" + name + ".*** *Melee Weapon Attack:* +4 to hit, reach 5ft., one target. *Hit* 5 (1d6 + 2) ";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
||||||
full : function(){
|
full : function(){
|
||||||
return [
|
return [
|
||||||
"___",
|
"___",
|
||||||
"___",
|
"___",
|
||||||
"> ## " + getMonsterName(),
|
"> ## " + getMonsterName(),
|
||||||
">*" + getType() + ", " + getAlignment() + "*",
|
">*" + getType() + ", " + getAlignment() + "*",
|
||||||
"> ___",
|
"> ___",
|
||||||
"> - **Armor Class** " + _.random(10,20),
|
"> - **Armor Class** " + _.random(10,20),
|
||||||
"> - **Hit Points** " + _.random(1, 150) + "(1d4 + 5)",
|
"> - **Hit Points** " + _.random(1, 150) + "(1d4 + 5)",
|
||||||
"> - **Speed** " + _.random(0,50) + "ft.",
|
"> - **Speed** " + _.random(0,50) + "ft.",
|
||||||
">___",
|
">___",
|
||||||
">|STR|DEX|CON|INT|WIS|CHA|",
|
">|STR|DEX|CON|INT|WIS|CHA|",
|
||||||
">|:---:|:---:|:---:|:---:|:---:|:---:|",
|
">|:---:|:---:|:---:|:---:|:---:|:---:|",
|
||||||
getStats(),
|
getStats(),
|
||||||
">___",
|
">___",
|
||||||
"> - **Condition Immunities** " + genList(["groggy", "swagged", "weak-kneed", "buzzed", "groovy", "melancholy", "drunk"], 3),
|
"> - **Condition Immunities** " + genList(["groggy", "swagged", "weak-kneed", "buzzed", "groovy", "melancholy", "drunk"], 3),
|
||||||
"> - **Senses** passive Perception " + _.random(3, 20),
|
"> - **Senses** passive Perception " + _.random(3, 20),
|
||||||
"> - **Languages** " + genList(["Common", "Pottymouth", "Gibberish", "Latin", "Jive"], 2),
|
"> - **Languages** " + genList(["Common", "Pottymouth", "Gibberish", "Latin", "Jive"], 2),
|
||||||
"> - **Challenge** " + _.random(0, 15) + " (" + _.random(10,10000)+ " XP)",
|
"> - **Challenge** " + _.random(0, 15) + " (" + _.random(10,10000)+ " XP)",
|
||||||
"> ___",
|
"> ___",
|
||||||
_.times(_.random(3,6), function(){
|
_.times(_.random(3,6), function(){
|
||||||
return genAbilities()
|
return genAbilities()
|
||||||
}).join('\n>\n'),
|
}).join('\n>\n'),
|
||||||
"> ### Actions",
|
"> ### Actions",
|
||||||
_.times(_.random(4,6), function(){
|
_.times(_.random(4,6), function(){
|
||||||
return genAction()
|
return genAction()
|
||||||
}).join('\n>\n'),
|
}).join('\n>\n'),
|
||||||
].join('\n') + '\n\n\n';
|
].join('\n') + '\n\n\n';
|
||||||
},
|
},
|
||||||
|
|
||||||
half : function(){
|
half : function(){
|
||||||
return [
|
return [
|
||||||
"___",
|
"___",
|
||||||
"> ## " + getMonsterName(),
|
"> ## " + getMonsterName(),
|
||||||
">*" + getType() + ", " + getAlignment() + "*",
|
">*" + getType() + ", " + getAlignment() + "*",
|
||||||
"> ___",
|
"> ___",
|
||||||
"> - **Armor Class** " + _.random(10,20),
|
"> - **Armor Class** " + _.random(10,20),
|
||||||
"> - **Hit Points** " + _.random(1, 150) + "(1d4 + 5)",
|
"> - **Hit Points** " + _.random(1, 150) + "(1d4 + 5)",
|
||||||
"> - **Speed** " + _.random(0,50) + "ft.",
|
"> - **Speed** " + _.random(0,50) + "ft.",
|
||||||
">___",
|
">___",
|
||||||
">|STR|DEX|CON|INT|WIS|CHA|",
|
">|STR|DEX|CON|INT|WIS|CHA|",
|
||||||
">|:---:|:---:|:---:|:---:|:---:|:---:|",
|
">|:---:|:---:|:---:|:---:|:---:|:---:|",
|
||||||
getStats(),
|
getStats(),
|
||||||
">___",
|
">___",
|
||||||
"> - **Condition Immunities** " + genList(["groggy", "swagged", "weak-kneed", "buzzed", "groovy", "melancholy", "drunk"], 3),
|
"> - **Condition Immunities** " + genList(["groggy", "swagged", "weak-kneed", "buzzed", "groovy", "melancholy", "drunk"], 3),
|
||||||
"> - **Senses** passive Perception " + _.random(3, 20),
|
"> - **Senses** passive Perception " + _.random(3, 20),
|
||||||
"> - **Languages** " + genList(["Common", "Pottymouth", "Gibberish", "Latin", "Jive"], 2),
|
"> - **Languages** " + genList(["Common", "Pottymouth", "Gibberish", "Latin", "Jive"], 2),
|
||||||
"> - **Challenge** " + _.random(0, 15) + " (" + _.random(10,10000)+ " XP)",
|
"> - **Challenge** " + _.random(0, 15) + " (" + _.random(10,10000)+ " XP)",
|
||||||
"> ___",
|
"> ___",
|
||||||
_.times(_.random(0,2), function(){
|
_.times(_.random(0,2), function(){
|
||||||
return genAbilities()
|
return genAbilities()
|
||||||
}).join('\n>\n'),
|
}).join('\n>\n'),
|
||||||
"> ### Actions",
|
"> ### Actions",
|
||||||
_.times(_.random(1,2), function(){
|
_.times(_.random(1,2), function(){
|
||||||
return genAction()
|
return genAction()
|
||||||
}).join('\n>\n'),
|
}).join('\n>\n'),
|
||||||
].join('\n') + '\n\n\n';
|
].join('\n') + '\n\n\n';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
var MagicGen = require('./magic.gen.js');
|
var SpellGen = require('./spell.gen.js');
|
||||||
var ClassTableGen = require('./classtable.gen.js');
|
var ClassTableGen = require('./classtable.gen.js');
|
||||||
var MonsterBlockGen = require('./monsterblock.gen.js');
|
var MonsterBlockGen = require('./monsterblock.gen.js');
|
||||||
var ClassFeatureGen = require('./classfeature.gen.js');
|
var ClassFeatureGen = require('./classfeature.gen.js');
|
||||||
var FullClassGen = require('./fullclass.gen.js');
|
var FullClassGen = require('./fullclass.gen.js');
|
||||||
var CoverPageGen = require('./coverpage.gen.js');
|
|
||||||
var TableOfContentsGen = require('./tableOfContents.gen.js');
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = [
|
module.exports = [
|
||||||
@@ -28,11 +27,6 @@ module.exports = [
|
|||||||
icon : 'fa-arrows-v',
|
icon : 'fa-arrows-v',
|
||||||
gen : "<div style='margin-top:140px'></div>\n\n"
|
gen : "<div style='margin-top:140px'></div>\n\n"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name : "Wide Block",
|
|
||||||
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"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name : "Image",
|
name : "Image",
|
||||||
icon : 'fa-image',
|
icon : 'fa-image',
|
||||||
@@ -59,24 +53,6 @@ module.exports = [
|
|||||||
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",
|
|
||||||
icon : 'fa-sort-numeric-asc',
|
|
||||||
gen : "<div class='pageNumber auto'></div>\n"
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name : "Link to page",
|
|
||||||
icon : 'fa-link',
|
|
||||||
gen : "[Click here](#p3) to go to page 3\n"
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name : "Table of Contents",
|
|
||||||
icon : 'fa-book',
|
|
||||||
gen : TableOfContentsGen
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -91,12 +67,7 @@ module.exports = [
|
|||||||
{
|
{
|
||||||
name : 'Spell',
|
name : 'Spell',
|
||||||
icon : 'fa-magic',
|
icon : 'fa-magic',
|
||||||
gen : MagicGen.spell,
|
gen : SpellGen,
|
||||||
},
|
|
||||||
{
|
|
||||||
name : 'Spell List',
|
|
||||||
icon : 'fa-list',
|
|
||||||
gen : MagicGen.spellList,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : 'Class Feature',
|
name : 'Class Feature',
|
||||||
@@ -115,20 +86,6 @@ module.exports = [
|
|||||||
].join('\n');
|
].join('\n');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name : 'Descriptive Text Box',
|
|
||||||
icon : 'fa-sticky-note-o',
|
|
||||||
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>"
|
|
||||||
].join('\n');
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name : 'Monster Stat Block',
|
name : 'Monster Stat Block',
|
||||||
icon : 'fa-bug',
|
icon : 'fa-bug',
|
||||||
@@ -138,12 +95,7 @@ module.exports = [
|
|||||||
name : 'Wide Monster Stat Block',
|
name : 'Wide Monster Stat Block',
|
||||||
icon : 'fa-paw',
|
icon : 'fa-paw',
|
||||||
gen : MonsterBlockGen.full,
|
gen : MonsterBlockGen.full,
|
||||||
},
|
}
|
||||||
{
|
|
||||||
name : 'Cover Page',
|
|
||||||
icon : 'fa-file-word-o',
|
|
||||||
gen : CoverPageGen,
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -180,52 +132,6 @@ module.exports = [
|
|||||||
"| 17th | 4 or lower |\n\n",
|
"| 17th | 4 or lower |\n\n",
|
||||||
].join('\n');
|
].join('\n');
|
||||||
},
|
},
|
||||||
},
|
|
||||||
{
|
|
||||||
name : 'Wide Table',
|
|
||||||
icon : 'fa-list',
|
|
||||||
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"
|
|
||||||
].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",
|
|
||||||
].join('\n');
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -265,3 +171,5 @@ module.exports = [
|
|||||||
},
|
},
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
78
client/homebrew/editor/snippets/spell.gen.js
Normal file
78
client/homebrew/editor/snippets/spell.gen.js
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
var _ = require('lodash');
|
||||||
|
|
||||||
|
module.exports = function(){
|
||||||
|
|
||||||
|
|
||||||
|
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",
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
var level = ["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th"];
|
||||||
|
var 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(', ') + ")"
|
||||||
|
}
|
||||||
|
|
||||||
|
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"
|
||||||
|
].join('\n');
|
||||||
|
}
|
||||||
@@ -1,73 +1,56 @@
|
|||||||
const React = require('react');
|
var React = require('react');
|
||||||
const _ = require('lodash');
|
var _ = require('lodash');
|
||||||
const cx = require('classnames');
|
var cx = require('classnames');
|
||||||
|
|
||||||
const CreateRouter = require('pico-router').createRouter;
|
var CreateRouter = require('pico-router').createRouter;
|
||||||
const BrewActions = require('homebrewery/brew.actions.js');
|
|
||||||
const AccountActions = require('homebrewery/account.actions.js');
|
var HomePage = require('./pages/homePage/homePage.jsx');
|
||||||
|
var EditPage = require('./pages/editPage/editPage.jsx');
|
||||||
const HomePage = require('./pages/homePage/homePage.jsx');
|
var SharePage = require('./pages/sharePage/sharePage.jsx');
|
||||||
const EditPage = require('./pages/editPage/editPage.jsx');
|
var NewPage = require('./pages/newPage/newPage.jsx');
|
||||||
const UserPage = require('./pages/userPage/userPage.jsx');
|
|
||||||
const SharePage = require('./pages/sharePage/sharePage.jsx');
|
var Router;
|
||||||
const NewPage = require('./pages/newPage/newPage.jsx');
|
var Homebrew = React.createClass({
|
||||||
//const ErrorPage = require('./pages/errorPage/errorPage.jsx');
|
getDefaultProps: function() {
|
||||||
const PrintPage = require('./pages/printPage/printPage.jsx');
|
return {
|
||||||
|
url : "",
|
||||||
let Router;
|
welcomeText : "",
|
||||||
const Homebrew = React.createClass({
|
changelog : "",
|
||||||
getDefaultProps: function() {
|
brew : {
|
||||||
return {
|
title : '',
|
||||||
url : '',
|
text : '',
|
||||||
version : '0.0.0',
|
shareId : null,
|
||||||
loginPath : '',
|
editId : null,
|
||||||
|
createdAt : null,
|
||||||
user : undefined,
|
updatedAt : null,
|
||||||
brew : undefined,
|
}
|
||||||
brews : []
|
};
|
||||||
};
|
},
|
||||||
},
|
componentWillMount: function() {
|
||||||
componentWillMount: function() {
|
Router = CreateRouter({
|
||||||
BrewActions.init({
|
'/homebrew/edit/:id' : (args) => {
|
||||||
version : this.props.version,
|
return <EditPage id={args.id} brew={this.props.brew} />
|
||||||
brew : this.props.brew
|
},
|
||||||
});
|
|
||||||
AccountActions.init({
|
'/homebrew/share/:id' : (args) => {
|
||||||
user : this.props.user,
|
return <SharePage id={args.id} brew={this.props.brew} />
|
||||||
loginPath : this.props.loginPath
|
},
|
||||||
});
|
'/homebrew/changelog' : (args) => {
|
||||||
|
return <SharePage brew={{title : 'Changelog', text : this.props.changelog}} />
|
||||||
Router = CreateRouter({
|
},
|
||||||
'/edit/:id' : <EditPage />,
|
'/homebrew/new' : (args) => {
|
||||||
'/share/:id' : <SharePage />,
|
return <NewPage />
|
||||||
'/user/:username' : (args) => {
|
},
|
||||||
return <UserPage
|
'/homebrew*' : <HomePage welcomeText={this.props.welcomeText} />,
|
||||||
username={args.username}
|
});
|
||||||
brews={this.props.brews}
|
},
|
||||||
/>
|
render : function(){
|
||||||
},
|
return(
|
||||||
'/print/:id' : (args, query) => {
|
<div className='homebrew'>
|
||||||
return <PrintPage brew={this.props.brew} query={query}/>;
|
<Router initialUrl={this.props.url}/>
|
||||||
},
|
</div>
|
||||||
'/print' : (args, query) => {
|
);
|
||||||
return <PrintPage query={query}/>;
|
}
|
||||||
},
|
});
|
||||||
'/new' : <NewPage />,
|
|
||||||
|
module.exports = Homebrew;
|
||||||
|
|
||||||
'/changelog' : <SharePage />,
|
|
||||||
'/test' : <SharePage />,
|
|
||||||
'/test_old' : <SharePage />,
|
|
||||||
|
|
||||||
|
|
||||||
'*' : <HomePage />,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
render : function(){
|
|
||||||
return <div className='homebrew'>
|
|
||||||
<Router initialUrl={this.props.url}/>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = Homebrew;
|
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
|
@import 'naturalcrit/styles/core.less';
|
||||||
@import 'naturalcrit/styles/core.less';
|
.homebrew{
|
||||||
.homebrew{
|
height : 100%;
|
||||||
height : 100%;
|
|
||||||
.page{
|
//TODO: Consider making backgroudn color lighter
|
||||||
display : flex;
|
background-color : @steel;
|
||||||
height : 100%;
|
.page{
|
||||||
background-color : @steel;
|
display : flex;
|
||||||
flex-direction : column;
|
height : 100%;
|
||||||
.content{
|
flex-direction : column;
|
||||||
position : relative;
|
.content{
|
||||||
height : calc(~"100% - 29px"); //Navbar height
|
position : relative;
|
||||||
flex : auto;
|
height : calc(~"100% - 29px"); //Navbar height
|
||||||
}
|
flex : auto;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
const React = require('react');
|
|
||||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
|
||||||
|
|
||||||
const Store = require('homebrewery/account.store.js');
|
|
||||||
const Actions = require('homebrewery/account.actions.js');
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = function(props){
|
|
||||||
const user = Store.getUser();
|
|
||||||
if(user && user == props.userPage){
|
|
||||||
return <Nav.item onClick={Actions.logout} color='yellow' icon='fa-user-times'>
|
|
||||||
logout
|
|
||||||
</Nav.item>
|
|
||||||
}
|
|
||||||
if(user){
|
|
||||||
return <Nav.item href={`/user/${user}`} color='yellow' icon='fa-user'>
|
|
||||||
{user}
|
|
||||||
</Nav.item>
|
|
||||||
}
|
|
||||||
return <Nav.item onClick={Actions.login} color='teal' icon='fa-sign-in'>
|
|
||||||
login
|
|
||||||
</Nav.item>
|
|
||||||
};
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
const React = require('react');
|
|
||||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
|
||||||
const Store = require('homebrewery/brew.store.js');
|
|
||||||
|
|
||||||
module.exports = Store.createSmartComponent((props) => {
|
|
||||||
return <Nav.item className='brewTitle'>{props.title}</Nav.item>
|
|
||||||
}, (props) => {
|
|
||||||
return {title : Store.getMetaData().title};
|
|
||||||
})
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
const flux = require('pico-flux')
|
|
||||||
const React = require('react');
|
|
||||||
const _ = require('lodash');
|
|
||||||
const cx = require('classnames');
|
|
||||||
|
|
||||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
|
||||||
|
|
||||||
const Store = require('homebrewery/brew.store.js');
|
|
||||||
const Actions = require('homebrewery/brew.actions.js');
|
|
||||||
|
|
||||||
const onStoreChange = () => {
|
|
||||||
return {
|
|
||||||
status : Store.getStatus(),
|
|
||||||
errors : Store.getErrors()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const ContinousSave = React.createClass({
|
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
status : 'ready',
|
|
||||||
errors : undefined
|
|
||||||
};
|
|
||||||
},
|
|
||||||
componentDidMount: function() {
|
|
||||||
flux.actionEmitter.on('dispatch', this.actionHandler);
|
|
||||||
window.onbeforeunload = ()=>{
|
|
||||||
if(this.props.status !== 'ready') return 'You have unsaved changes!';
|
|
||||||
};
|
|
||||||
},
|
|
||||||
componentWillUnmount: function() {
|
|
||||||
flux.actionEmitter.removeListener('dispatch', this.actionHandler);
|
|
||||||
window.onbeforeunload = function(){};
|
|
||||||
},
|
|
||||||
actionHandler : function(actionType){
|
|
||||||
if(actionType == 'UPDATE_BREW_TEXT' || actionType == 'UPDATE_META'){
|
|
||||||
Actions.pendingSave();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleClick : function(){
|
|
||||||
Actions.save();
|
|
||||||
},
|
|
||||||
renderError : function(){
|
|
||||||
let errMsg = '';
|
|
||||||
try{
|
|
||||||
errMsg += this.state.errors.toString() + '\n\n';
|
|
||||||
errMsg += '```\n' + JSON.stringify(this.state.errors.response.error, null, ' ') + '\n```';
|
|
||||||
}catch(e){}
|
|
||||||
|
|
||||||
return <Nav.item className='continousSave error' icon="fa-warning">
|
|
||||||
Oops!
|
|
||||||
<div className='errorContainer'>
|
|
||||||
Looks like there was a problem saving. <br />
|
|
||||||
Report the issue <a target='_blank' href={'https://github.com/stolksdorf/naturalcrit/issues/new?body='+ encodeURIComponent(errMsg)}>
|
|
||||||
here
|
|
||||||
</a>.
|
|
||||||
</div>
|
|
||||||
</Nav.item>
|
|
||||||
},
|
|
||||||
render : function(){
|
|
||||||
if(this.props.status == 'error') return this.renderError();
|
|
||||||
|
|
||||||
if(this.props.status == 'saving'){
|
|
||||||
return <Nav.item className='continousSave' icon="fa-spinner fa-spin">saving...</Nav.item>
|
|
||||||
}
|
|
||||||
if(this.props.status == 'pending'){
|
|
||||||
return <Nav.item className='continousSave' onClick={this.handleClick} color='blue' icon='fa-save'>Save Now</Nav.item>
|
|
||||||
}
|
|
||||||
if(this.props.status == 'ready'){
|
|
||||||
return <Nav.item className='continousSave saved'>saved.</Nav.item>
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = Store.createSmartComponent(ContinousSave, onStoreChange);
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
const flux = require('pico-flux')
|
|
||||||
const React = require('react');
|
|
||||||
const _ = require('lodash');
|
|
||||||
const cx = require('classnames');
|
|
||||||
|
|
||||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
|
||||||
|
|
||||||
const Store = require('homebrewery/brew.store.js');
|
|
||||||
const Actions = require('homebrewery/brew.actions.js');
|
|
||||||
|
|
||||||
const onStoreChange = () => {
|
|
||||||
return {
|
|
||||||
status : Store.getStatus(),
|
|
||||||
errors : Store.getErrors()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const ContinousSave = React.createClass({
|
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
status : 'ready',
|
|
||||||
errors : undefined
|
|
||||||
};
|
|
||||||
},
|
|
||||||
componentDidMount: function() {
|
|
||||||
flux.actionEmitter.on('dispatch', this.actionHandler);
|
|
||||||
window.onbeforeunload = ()=>{
|
|
||||||
if(this.props.status !== 'ready') return 'You have unsaved changes!';
|
|
||||||
};
|
|
||||||
},
|
|
||||||
componentWillUnmount: function() {
|
|
||||||
flux.actionEmitter.removeListener('dispatch', this.actionHandler);
|
|
||||||
window.onbeforeunload = function(){};
|
|
||||||
},
|
|
||||||
actionHandler : function(actionType){
|
|
||||||
if(actionType == 'UPDATE_BREW_CODE' || actionType == 'UPDATE_META' || actionType == 'UPDATE_BREW_STYLE'){
|
|
||||||
Actions.pendingSave();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleClick : function(){
|
|
||||||
Actions.save();
|
|
||||||
},
|
|
||||||
renderError : function(){
|
|
||||||
let errMsg = '';
|
|
||||||
try{
|
|
||||||
errMsg += this.state.errors.toString() + '\n\n';
|
|
||||||
errMsg += '```\n' + JSON.stringify(this.state.errors.response.error, null, ' ') + '\n```';
|
|
||||||
}catch(e){}
|
|
||||||
|
|
||||||
return <Nav.item className='continousSave error' icon="fa-warning">
|
|
||||||
Oops!
|
|
||||||
<div className='errorContainer'>
|
|
||||||
Looks like there was a problem saving. <br />
|
|
||||||
Back up your brew in a text file, just in case.
|
|
||||||
<br /><br />
|
|
||||||
|
|
||||||
Report the issue <a target='_blank' href={'https://github.com/stolksdorf/naturalcrit/issues/new?body='+ encodeURIComponent(errMsg)}>
|
|
||||||
here
|
|
||||||
</a>.
|
|
||||||
</div>
|
|
||||||
</Nav.item>
|
|
||||||
},
|
|
||||||
render : function(){
|
|
||||||
if(this.props.status == 'error') return this.renderError();
|
|
||||||
|
|
||||||
if(this.props.status == 'saving'){
|
|
||||||
return <Nav.item className='continousSave' icon="fa-spinner fa-spin">saving...</Nav.item>
|
|
||||||
}
|
|
||||||
if(this.props.status == 'pending'){
|
|
||||||
return <Nav.item className='continousSave' onClick={this.handleClick} color='blue' icon='fa-save'>Save Now</Nav.item>
|
|
||||||
}
|
|
||||||
if(this.props.status == 'ready'){
|
|
||||||
return <Nav.item className='continousSave saved'>saved.</Nav.item>
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = Store.createSmartComponent(ContinousSave, onStoreChange);
|
|
||||||
@@ -1,33 +1,33 @@
|
|||||||
var React = require('react');
|
var React = require('react');
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
var cx = require('classnames');
|
var cx = require('classnames');
|
||||||
var Nav = require('naturalcrit/nav/nav.jsx');
|
var Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
|
|
||||||
const MAX_TITLE_LENGTH = 50;
|
const MAX_TITLE_LENGTH = 50;
|
||||||
|
|
||||||
|
|
||||||
var EditTitle = React.createClass({
|
var EditTitle = React.createClass({
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
return {
|
return {
|
||||||
title : '',
|
title : '',
|
||||||
onChange : function(){}
|
onChange : function(){}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
handleChange : function(e){
|
handleChange : function(e){
|
||||||
if(e.target.value.length > MAX_TITLE_LENGTH) return;
|
if(e.target.value.length > MAX_TITLE_LENGTH) return;
|
||||||
this.props.onChange(e.target.value);
|
this.props.onChange(e.target.value);
|
||||||
},
|
},
|
||||||
render : function(){
|
render : function(){
|
||||||
return <Nav.item className='editTitle'>
|
return <Nav.item className='editTitle'>
|
||||||
<input placeholder='Brew Title' type='text' value={this.props.title} onChange={this.handleChange} />
|
<input placeholder='Brew Title' type='text' value={this.props.title} onChange={this.handleChange} />
|
||||||
|
|
||||||
<div className={cx('charCount', {'max' : this.props.title.length >= MAX_TITLE_LENGTH})}>
|
<div className={cx('charCount', {'max' : this.props.title.length >= MAX_TITLE_LENGTH})}>
|
||||||
{this.props.title.length}/{MAX_TITLE_LENGTH}
|
{this.props.title.length}/{MAX_TITLE_LENGTH}
|
||||||
</div>
|
</div>
|
||||||
</Nav.item>
|
</Nav.item>
|
||||||
},
|
},
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = EditTitle;
|
module.exports = EditTitle;
|
||||||
@@ -1,13 +1,8 @@
|
|||||||
var React = require('react');
|
var React = require('react');
|
||||||
var Nav = require('naturalcrit/nav/nav.jsx');
|
var Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
|
|
||||||
module.exports = function(props){
|
module.exports = function(props){
|
||||||
return <Nav.item
|
return <Nav.item newTab={true} href='https://github.com/stolksdorf/naturalcrit/issues' color='red' icon='fa-bug'>
|
||||||
{...props}
|
report issue
|
||||||
newTab={true}
|
</Nav.item>
|
||||||
href='https://github.com/stolksdorf/homebrewery/issues'
|
|
||||||
color='red'
|
|
||||||
icon='fa-bug'>
|
|
||||||
report issue
|
|
||||||
</Nav.item>
|
|
||||||
};
|
};
|
||||||
@@ -1,22 +1,21 @@
|
|||||||
const React = require('react');
|
var React = require('react');
|
||||||
const _ = require('lodash');
|
var _ = require('lodash');
|
||||||
|
|
||||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
var Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
const Store = require('homebrewery/brew.store.js');
|
|
||||||
|
var Navbar = React.createClass({
|
||||||
const Navbar = React.createClass({
|
render : function(){
|
||||||
render : function(){
|
return <Nav.base>
|
||||||
return <Nav.base>
|
<Nav.section>
|
||||||
<Nav.section>
|
<Nav.logo />
|
||||||
<Nav.logo />
|
<Nav.item href='/homebrew' className='homebrewLogo'>
|
||||||
<Nav.item href='/' className='homebrewLogo'>
|
<div>The Homebrewery</div>
|
||||||
<div>The Homebrewery</div>
|
</Nav.item>
|
||||||
</Nav.item>
|
<Nav.item>v2.0.0</Nav.item>
|
||||||
<Nav.item>{`v${Store.getVersion()}`}</Nav.item>
|
</Nav.section>
|
||||||
</Nav.section>
|
{this.props.children}
|
||||||
{this.props.children}
|
</Nav.base>
|
||||||
</Nav.base>
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
|
module.exports = Navbar;
|
||||||
module.exports = Navbar;
|
|
||||||
|
|||||||
@@ -1,132 +1,58 @@
|
|||||||
@navbarHeight : 28px;
|
|
||||||
.homebrew nav{
|
.homebrew nav{
|
||||||
.homebrewLogo{
|
.homebrewLogo{
|
||||||
.animate(color);
|
.animate(color);
|
||||||
font-family : CodeBold;
|
font-family : CodeBold;
|
||||||
font-size : 12px;
|
font-size : 12px;
|
||||||
color : white;
|
color : white;
|
||||||
div{
|
div{
|
||||||
margin-top : 2px;
|
margin-top : 2px;
|
||||||
margin-bottom : -2px;
|
margin-bottom : -2px;
|
||||||
}
|
}
|
||||||
&:hover{
|
&:hover{
|
||||||
color : @blue;
|
color : @blue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.brewTitle.navItem{
|
.editTitle.navItem{
|
||||||
font-size : 12px;
|
padding : 2px 12px;
|
||||||
font-weight : 800;
|
input{
|
||||||
color : white;
|
margin : 0;
|
||||||
text-align : center;
|
padding : 2px;
|
||||||
text-transform : initial;
|
width : 250px;
|
||||||
}
|
background-color : #444;
|
||||||
.patreon.navItem{
|
font-family : 'Open Sans', sans-serif;
|
||||||
i{
|
font-size : 12px;
|
||||||
.animate(color);
|
font-weight : 800;
|
||||||
&:hover{
|
color : white;
|
||||||
color : @red;
|
text-align : center;
|
||||||
}
|
border : 1px solid @blue;
|
||||||
}
|
outline : none;
|
||||||
}
|
}
|
||||||
.recent.navItem{
|
.charCount{
|
||||||
position : relative;
|
display : inline-block;
|
||||||
.dropdown{
|
vertical-align : bottom;
|
||||||
position : absolute;
|
margin-left : 8px;
|
||||||
top : 28px;
|
text-align : right;
|
||||||
left : 0px;
|
color : #666;
|
||||||
z-index : 10000;
|
&.max{
|
||||||
width : 100%;
|
color : @red;
|
||||||
h4{
|
}
|
||||||
display : block;
|
}
|
||||||
box-sizing : border-box;
|
}
|
||||||
padding : 5px 0px;
|
.brewTitle.navItem{
|
||||||
background-color : #333;
|
font-size : 12px;
|
||||||
font-size : 0.8em;
|
font-weight : 800;
|
||||||
color : #bbb;
|
color : white;
|
||||||
text-align : center;
|
text-align : center;
|
||||||
border-top : 1px solid #888;
|
text-transform: initial;
|
||||||
&:nth-of-type(1){ background-color: darken(@teal, 20%); }
|
}
|
||||||
&:nth-of-type(2){ background-color: darken(@purple, 30%); }
|
.patreon.navItem{
|
||||||
}
|
i{
|
||||||
.item{
|
.animate(color);
|
||||||
.animate(background-color);
|
&:hover{
|
||||||
position : relative;
|
color : @red;
|
||||||
display : block;
|
}
|
||||||
box-sizing : border-box;
|
}
|
||||||
padding : 13px 5px;
|
}
|
||||||
background-color : #333;
|
|
||||||
color : white;
|
|
||||||
text-decoration : none;
|
|
||||||
border-top : 1px solid #888;
|
|
||||||
&:hover{
|
|
||||||
background-color : @blue;
|
|
||||||
}
|
|
||||||
.title{
|
|
||||||
display : inline-block;
|
|
||||||
overflow : hidden;
|
|
||||||
width : 100%;
|
|
||||||
text-overflow : ellipsis;
|
|
||||||
white-space : nowrap;
|
|
||||||
}
|
|
||||||
.time{
|
|
||||||
position : absolute;
|
|
||||||
right : 2px;
|
|
||||||
bottom : 2px;
|
|
||||||
font-size : 0.7em;
|
|
||||||
color : #888;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.warning.navItem{
|
|
||||||
position : relative;
|
|
||||||
background-color : @orange;
|
|
||||||
color : white;
|
|
||||||
&:hover>.dropdown{
|
|
||||||
visibility : visible;
|
|
||||||
}
|
|
||||||
.dropdown{
|
|
||||||
position : absolute;
|
|
||||||
display : block;
|
|
||||||
top : 28px;
|
|
||||||
left : 0px;
|
|
||||||
visibility : hidden;
|
|
||||||
z-index : 10000;
|
|
||||||
box-sizing : border-box;
|
|
||||||
width : 100%;
|
|
||||||
padding : 13px 5px;
|
|
||||||
background-color : #333;
|
|
||||||
text-align : center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.staticSave.navItem{
|
|
||||||
background-color : @orange;
|
|
||||||
&:hover{
|
|
||||||
background-color : @green;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.continousSave.navItem{
|
|
||||||
width : 105px;
|
|
||||||
text-align : center;
|
|
||||||
&.saved{
|
|
||||||
cursor : initial;
|
|
||||||
color : #666;
|
|
||||||
}
|
|
||||||
&.error{
|
|
||||||
position : relative;
|
|
||||||
background-color : @red;
|
|
||||||
.errorContainer{
|
|
||||||
position : absolute;
|
|
||||||
top : 29px;
|
|
||||||
left : -20px;
|
|
||||||
z-index : 1000;
|
|
||||||
width : 170px;
|
|
||||||
padding : 8px;
|
|
||||||
background-color : #333;
|
|
||||||
a{
|
|
||||||
color : @teal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
Account : require('./account.navitem.jsx'),
|
|
||||||
BrewTitle : require('./brewTitle.navitem.jsx'),
|
|
||||||
ContinousSave : require('./continousSave.navitem.jsx'),
|
|
||||||
Issue : require('./issue.navitem.jsx'),
|
|
||||||
Patreon : require('./patreon.navitem.jsx'),
|
|
||||||
Print : require('./print.navitem.jsx'),
|
|
||||||
Recent : require('./recent.navitem.jsx'),
|
|
||||||
StaticSave : require('./staticSave.navitem.jsx'),
|
|
||||||
};
|
|
||||||
@@ -1,14 +1,13 @@
|
|||||||
var React = require('react');
|
var React = require('react');
|
||||||
var Nav = require('naturalcrit/nav/nav.jsx');
|
var Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
|
|
||||||
module.exports = function(props){
|
module.exports = function(props){
|
||||||
return <Nav.item
|
return <Nav.item
|
||||||
{...props}
|
className='patreon'
|
||||||
className='patreon'
|
newTab={true}
|
||||||
newTab={true}
|
href='https://www.patreon.com/stolksdorf'
|
||||||
href='https://www.patreon.com/stolksdorf'
|
color='green'
|
||||||
color='green'
|
icon='fa-heart'>
|
||||||
icon='fa-heart'>
|
help out
|
||||||
help out
|
</Nav.item>
|
||||||
</Nav.item>
|
|
||||||
};
|
};
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
var React = require('react');
|
var React = require('react');
|
||||||
var Nav = require('naturalcrit/nav/nav.jsx');
|
var Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
|
|
||||||
module.exports = function(props){
|
module.exports = function(props){
|
||||||
return <Nav.item newTab={true} href={'/print/' + props.shareId +'?dialog=true'} color='purple' icon='fa-file-pdf-o'>
|
return <Nav.item newTab={true} href={'/homebrew/print/' + props.shareId +'?dialog=true'} color='purple' icon='fa-print'>
|
||||||
get PDF
|
print
|
||||||
</Nav.item>
|
</Nav.item>
|
||||||
};
|
};
|
||||||
@@ -1,204 +0,0 @@
|
|||||||
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';
|
|
||||||
|
|
||||||
//DEPRICATED
|
|
||||||
|
|
||||||
|
|
||||||
var BaseItem = React.createClass({
|
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
storageKey : '',
|
|
||||||
text : '',
|
|
||||||
currentBrew:{
|
|
||||||
title : '',
|
|
||||||
id : '',
|
|
||||||
url : ''
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
getInitialState: function() {
|
|
||||||
return {
|
|
||||||
showDropdown: false,
|
|
||||||
brews : []
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
componentDidMount: function() {
|
|
||||||
console.log('Recent nav item is depricated');
|
|
||||||
|
|
||||||
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>
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
}
|
|
||||||
51
client/homebrew/navbar/reddit.navitem.jsx
Normal file
51
client/homebrew/navbar/reddit.navitem.jsx
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
var React = require('react');
|
||||||
|
var _ = require('lodash');
|
||||||
|
var cx = require('classnames');
|
||||||
|
|
||||||
|
//var striptags = require('striptags');
|
||||||
|
|
||||||
|
var Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
|
|
||||||
|
const MAX_URL_SIZE = 2083;
|
||||||
|
const MAIN_URL = "https://www.reddit.com/r/UnearthedArcana/submit?selftext=true"
|
||||||
|
|
||||||
|
|
||||||
|
var RedditShare = React.createClass({
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
brew : {
|
||||||
|
title : '',
|
||||||
|
sharedId : '',
|
||||||
|
text : ''
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
getText : function(){
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
handleClick : function(){
|
||||||
|
var url = [
|
||||||
|
MAIN_URL,
|
||||||
|
'title=' + encodeURIComponent(this.props.brew.title ? this.props.brew.title : 'Check out my brew!'),
|
||||||
|
|
||||||
|
'text=' + encodeURIComponent(this.props.brew.text)
|
||||||
|
|
||||||
|
|
||||||
|
].join('&');
|
||||||
|
|
||||||
|
window.open(url, '_blank');
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
render : function(){
|
||||||
|
return <Nav.item icon='fa-reddit-alien' color='red' onClick={this.handleClick}>
|
||||||
|
share on reddit
|
||||||
|
</Nav.item>
|
||||||
|
},
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = RedditShare;
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
const React = require('react');
|
|
||||||
const _ = require('lodash');
|
|
||||||
const cx = require('classnames');
|
|
||||||
|
|
||||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
|
||||||
const Store = require('homebrewery/brew.store.js');
|
|
||||||
const Actions = require('homebrewery/brew.actions.js');
|
|
||||||
|
|
||||||
const StaticSave = React.createClass({
|
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
status : 'ready'
|
|
||||||
};
|
|
||||||
},
|
|
||||||
handleClick : function(){
|
|
||||||
Actions.create();
|
|
||||||
},
|
|
||||||
render : function(){
|
|
||||||
if(this.props.status === 'saving'){
|
|
||||||
return <Nav.item icon='fa-spinner fa-spin' className='staticSave'>
|
|
||||||
save...
|
|
||||||
</Nav.item>
|
|
||||||
}
|
|
||||||
if(this.props.status === 'ready'){
|
|
||||||
return <Nav.item icon='fa-save' className='staticSave' onClick={this.handleClick}>
|
|
||||||
save
|
|
||||||
</Nav.item>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = Store.createSmartComponent(StaticSave, ()=>{
|
|
||||||
return {
|
|
||||||
status : Store.getStatus()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1,63 +1,172 @@
|
|||||||
const React = require('react');
|
var React = require('react');
|
||||||
const _ = require('lodash');
|
var _ = require('lodash');
|
||||||
const cx = require('classnames');
|
var cx = require('classnames');
|
||||||
|
var request = require("superagent");
|
||||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
|
||||||
const Navbar = require('../../navbar/navbar.jsx');
|
var Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
const Items = require('../../navbar/navitems.js');
|
var Navbar = require('../../navbar/navbar.jsx');
|
||||||
|
|
||||||
const BrewInterface = require('homebrewery/brewInterface/brewInterface.jsx');
|
var EditTitle = require('../../navbar/editTitle.navitem.jsx');
|
||||||
const Utils = require('homebrewery/utils.js');
|
var ReportIssue = require('../../navbar/issue.navitem.jsx');
|
||||||
|
var PrintLink = require('../../navbar/print.navitem.jsx');
|
||||||
const Store = require('homebrewery/brew.store.js');
|
|
||||||
const Actions = require('homebrewery/brew.actions.js');
|
|
||||||
|
var SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
|
||||||
|
var Editor = require('../../editor/editor.jsx');
|
||||||
const EditPage = React.createClass({
|
var BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
||||||
componentDidMount: function(){
|
|
||||||
document.addEventListener('keydown', this.handleControlKeys);
|
|
||||||
},
|
|
||||||
componentWillUnmount: function() {
|
const SAVE_TIMEOUT = 3000;
|
||||||
document.removeEventListener('keydown', this.handleControlKeys);
|
|
||||||
},
|
|
||||||
handleControlKeys : Utils.controlKeys({
|
|
||||||
s : Actions.save,
|
var EditPage = React.createClass({
|
||||||
p : Actions.print
|
getDefaultProps: function() {
|
||||||
}),
|
return {
|
||||||
render : function(){
|
id : null,
|
||||||
return <div className='editPage page'>
|
brew : {
|
||||||
<SmartNav />
|
title : '',
|
||||||
<div className='content'>
|
text : '',
|
||||||
<BrewInterface />
|
shareId : null,
|
||||||
</div>
|
editId : null,
|
||||||
</div>
|
createdAt : null,
|
||||||
}
|
updatedAt : null,
|
||||||
});
|
}
|
||||||
|
};
|
||||||
const SmartNav = Store.createSmartComponent(React.createClass({
|
},
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
getInitialState: function() {
|
||||||
brew : {}
|
return {
|
||||||
};
|
title : this.props.brew.title,
|
||||||
},
|
text: this.props.brew.text,
|
||||||
render : function(){
|
isSaving : false,
|
||||||
return <Navbar>
|
isPending : false,
|
||||||
<Nav.section>
|
errors : null,
|
||||||
<Items.BrewTitle />
|
lastUpdated : this.props.brew.updatedAt
|
||||||
</Nav.section>
|
};
|
||||||
<Nav.section>
|
},
|
||||||
<Items.ContinousSave />
|
savedBrew : null,
|
||||||
<Items.Issue />
|
|
||||||
<Nav.item newTab={true} href={'/share/' + Store.getBrew().shareId} color='teal' icon='fa-share-alt'>
|
componentDidMount: function(){
|
||||||
Share
|
this.debounceSave = _.debounce(this.save, SAVE_TIMEOUT);
|
||||||
</Nav.item>
|
window.onbeforeunload = ()=>{
|
||||||
<Items.Print />
|
if(this.state.isSaving || this.state.isPending){
|
||||||
<Items.Account />
|
return 'You have unsaved changes!';
|
||||||
</Nav.section>
|
}
|
||||||
</Navbar>
|
}
|
||||||
}
|
},
|
||||||
}), ()=>{
|
componentWillUnmount: function() {
|
||||||
return {brew : Store.getBrew()}
|
window.onbeforeunload = function(){};
|
||||||
});
|
},
|
||||||
|
|
||||||
module.exports = EditPage;
|
handleSplitMove : function(){
|
||||||
|
this.refs.editor.update();
|
||||||
|
},
|
||||||
|
|
||||||
|
handleTitleChange : function(title){
|
||||||
|
this.setState({
|
||||||
|
title : title,
|
||||||
|
isPending : true
|
||||||
|
});
|
||||||
|
|
||||||
|
(this.hasChanges() ? this.debounceSave() : this.debounceSave.cancel());
|
||||||
|
},
|
||||||
|
|
||||||
|
handleTextChange : function(text){
|
||||||
|
this.setState({
|
||||||
|
text : text,
|
||||||
|
isPending : true
|
||||||
|
});
|
||||||
|
|
||||||
|
(this.hasChanges() ? this.debounceSave() : this.debounceSave.cancel());
|
||||||
|
},
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
request.get('/homebrew/api/remove/' + this.props.brew.editId)
|
||||||
|
.send()
|
||||||
|
.end(function(err, res){
|
||||||
|
window.location.href = '/homebrew';
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
hasChanges : function(){
|
||||||
|
if(this.savedBrew){
|
||||||
|
if(this.state.text !== this.savedBrew.text) return true;
|
||||||
|
if(this.state.title !== this.savedBrew.title) return true;
|
||||||
|
}else{
|
||||||
|
if(this.state.text !== this.props.brew.text) return true;
|
||||||
|
if(this.state.title !== this.props.brew.title) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
save : function(){
|
||||||
|
this.debounceSave.cancel();
|
||||||
|
this.setState({
|
||||||
|
isSaving : true
|
||||||
|
});
|
||||||
|
|
||||||
|
request
|
||||||
|
.put('/homebrew/api/update/' + this.props.brew.editId)
|
||||||
|
.send({
|
||||||
|
text : this.state.text,
|
||||||
|
title : this.state.title
|
||||||
|
})
|
||||||
|
.end((err, res) => {
|
||||||
|
this.savedBrew = res.body;
|
||||||
|
this.setState({
|
||||||
|
isPending : false,
|
||||||
|
isSaving : false,
|
||||||
|
lastUpdated : res.body.updatedAt
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
renderSaveButton : function(){
|
||||||
|
if(this.state.isSaving){
|
||||||
|
return <Nav.item className='save' icon="fa-spinner fa-spin">saving...</Nav.item>
|
||||||
|
}
|
||||||
|
if(!this.state.isPending && !this.state.isSaving){
|
||||||
|
return <Nav.item className='save saved'>saved.</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>
|
||||||
|
}
|
||||||
|
},
|
||||||
|
renderNavbar : function(){
|
||||||
|
return <Navbar>
|
||||||
|
<Nav.section>
|
||||||
|
<EditTitle title={this.state.title} onChange={this.handleTitleChange} />
|
||||||
|
</Nav.section>
|
||||||
|
<Nav.section>
|
||||||
|
{this.renderSaveButton()}
|
||||||
|
<Nav.item newTab={true} href={'/homebrew/share/' + this.props.brew.shareId} color='teal' icon='fa-share-alt'>
|
||||||
|
Share
|
||||||
|
</Nav.item>
|
||||||
|
<PrintLink shareId={this.props.brew.shareId} />
|
||||||
|
<Nav.item color='red' icon='fa-trash' onClick={this.handleDelete}>
|
||||||
|
Delete
|
||||||
|
</Nav.item>
|
||||||
|
</Nav.section>
|
||||||
|
</Navbar>
|
||||||
|
},
|
||||||
|
|
||||||
|
render : function(){
|
||||||
|
return <div className='editPage 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>
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = EditPage;
|
||||||
|
|||||||
@@ -1,4 +1,12 @@
|
|||||||
|
.editPage{
|
||||||
.editPage{
|
|
||||||
|
.navItem.save{
|
||||||
|
width : 75px;
|
||||||
|
text-align: center;
|
||||||
|
&.saved{
|
||||||
|
color : #666;
|
||||||
|
cursor : initial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
var React = require('react');
|
|
||||||
var _ = require('lodash');
|
|
||||||
var 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');
|
|
||||||
|
|
||||||
var BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
|
||||||
|
|
||||||
var ErrorPage = React.createClass({
|
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
ver : '0.0.0',
|
|
||||||
errorId: ''
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
text : '# Oops \n We could not find a brew with that id. **Sorry!**',
|
|
||||||
|
|
||||||
render : function(){
|
|
||||||
return <div className='errorPage page'>
|
|
||||||
<Navbar ver={this.props.ver}>
|
|
||||||
<Nav.section>
|
|
||||||
<Nav.item className='errorTitle'>
|
|
||||||
Crit Fail!
|
|
||||||
</Nav.item>
|
|
||||||
</Nav.section>
|
|
||||||
|
|
||||||
<Nav.section>
|
|
||||||
<PatreonNavItem />
|
|
||||||
<IssueNavItem />
|
|
||||||
<RecentNavItem.both errorId={this.props.errorId} />
|
|
||||||
</Nav.section>
|
|
||||||
</Navbar>
|
|
||||||
|
|
||||||
<div className='content'>
|
|
||||||
<BrewRenderer text={this.text} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = ErrorPage;
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
.errorPage{
|
|
||||||
.errorTitle{
|
|
||||||
background-color: @orange;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,63 +1,70 @@
|
|||||||
const React = require('react');
|
var React = require('react');
|
||||||
const _ = require('lodash');
|
var _ = require('lodash');
|
||||||
const cx = require('classnames');
|
var cx = require('classnames');
|
||||||
|
|
||||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
var Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
const Navbar = require('../../navbar/navbar.jsx');
|
var Navbar = require('../../navbar/navbar.jsx');
|
||||||
const PatreonNavItem = require('../../navbar/patreon.navitem.jsx');
|
var 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');
|
var SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
|
||||||
|
var Editor = require('../../editor/editor.jsx');
|
||||||
|
var BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
||||||
const BrewInterface = require('homebrewery/brewInterface/brewInterface.jsx');
|
|
||||||
|
|
||||||
|
|
||||||
const Actions = require('homebrewery/brew.actions.js');
|
var HomePage = React.createClass({
|
||||||
//
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
const HomePage = React.createClass({
|
welcomeText : ""
|
||||||
handleSave : function(){
|
};
|
||||||
Actions.saveNew();
|
},
|
||||||
},
|
getInitialState: function() {
|
||||||
|
return {
|
||||||
renderNavbar : function(){
|
text: this.props.welcomeText
|
||||||
return <Navbar>
|
};
|
||||||
<Nav.section>
|
},
|
||||||
<PatreonNavItem collaspe={true} />
|
handleSplitMove : function(){
|
||||||
<IssueNavItem collaspe={true} />
|
this.refs.editor.update();
|
||||||
<Nav.item newTab={true} href='/changelog' color='purple' icon='fa-star' collaspe={true}>
|
},
|
||||||
What's new
|
handleTextChange : function(text){
|
||||||
</Nav.item>
|
this.setState({
|
||||||
<RecentNavItem.both />
|
text : text
|
||||||
<AccountNavItem />
|
});
|
||||||
{/*}
|
},
|
||||||
<Nav.item href='/new' color='green' icon='fa-external-link'>
|
renderNavbar : function(){
|
||||||
New Brew
|
return <Navbar>
|
||||||
</Nav.item>
|
<Nav.section>
|
||||||
*/}
|
<PatreonNavItem />
|
||||||
</Nav.section>
|
<Nav.item newTab={true} href='https://github.com/stolksdorf/naturalcrit/issues' color='red' icon='fa-bug'>
|
||||||
</Navbar>
|
report issue
|
||||||
},
|
</Nav.item>
|
||||||
|
<Nav.item newTab={true} href='/homebrew/changelog' color='purple' icon='fa-file-text-o'>
|
||||||
render : function(){
|
Changelog
|
||||||
return <div className='homePage page'>
|
</Nav.item>
|
||||||
{this.renderNavbar()}
|
<Nav.item href='/homebrew/new' color='green' icon='fa-external-link'>
|
||||||
<div className='content'>
|
New Brew
|
||||||
<BrewInterface />
|
</Nav.item>
|
||||||
</div>
|
</Nav.section>
|
||||||
|
</Navbar>
|
||||||
<div className={cx('floatingSaveButton', {
|
},
|
||||||
//show : Store.getBrewText() !== this.props.welcomeText
|
|
||||||
})} onClick={this.handleSave}>
|
render : function(){
|
||||||
Save current <i className='fa fa-save' />
|
return <div className='homePage page'>
|
||||||
</div>
|
{this.renderNavbar()}
|
||||||
|
|
||||||
<a href='/new' className='floatingNewButton'>
|
<div className='content'>
|
||||||
Create your own <i className='fa fa-magic' />
|
<SplitPane onDragFinish={this.handleSplitMove} ref='pane'>
|
||||||
</a>
|
<Editor value={this.state.text} onChange={this.handleTextChange} ref='editor'/>
|
||||||
</div>
|
<BrewRenderer text={this.state.text} />
|
||||||
}
|
</SplitPane>
|
||||||
});
|
</div>
|
||||||
|
|
||||||
module.exports = HomePage;
|
<a href='/homebrew/new' className='floatingNewButton'>
|
||||||
|
Create your own <i className='fa fa-magic' />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = HomePage;
|
||||||
|
|||||||
@@ -1,43 +1,23 @@
|
|||||||
.homePage{
|
|
||||||
position : relative;
|
.homePage{
|
||||||
a.floatingNewButton{
|
|
||||||
.animate(background-color);
|
|
||||||
position : absolute;
|
position : relative;
|
||||||
display : block;
|
a.floatingNewButton{
|
||||||
right : 70px;
|
.animate(background-color);
|
||||||
bottom : 70px;
|
position : absolute;
|
||||||
z-index : 100;
|
display : block;
|
||||||
z-index : 5001;
|
right : 70px;
|
||||||
padding : 1em;
|
bottom : 70px;
|
||||||
background-color : @orange;
|
z-index : 100;
|
||||||
font-size : 1.5em;
|
padding : 1em;
|
||||||
color : white;
|
background-color : @orange;
|
||||||
text-decoration : none;
|
font-size : 1.5em;
|
||||||
box-shadow : 3px 3px 15px black;
|
color : white;
|
||||||
&:hover{
|
text-decoration : none;
|
||||||
background-color : darken(@orange, 20%);
|
box-shadow : 3px 3px 15px black;
|
||||||
}
|
&:hover{
|
||||||
}
|
background-color : darken(@orange, 20%);
|
||||||
.floatingSaveButton{
|
}
|
||||||
.animateAll();
|
}
|
||||||
position : absolute;
|
|
||||||
display : block;
|
|
||||||
right : 200px;
|
|
||||||
bottom : 90px;
|
|
||||||
z-index : 100;
|
|
||||||
z-index : 5000;
|
|
||||||
padding : 0.8em;
|
|
||||||
cursor : pointer;
|
|
||||||
background-color : @blue;
|
|
||||||
font-size : 0.8em;
|
|
||||||
color : white;
|
|
||||||
text-decoration : none;
|
|
||||||
box-shadow : 3px 3px 15px black;
|
|
||||||
&:hover{
|
|
||||||
background-color : darken(@blue, 20%);
|
|
||||||
}
|
|
||||||
&.show{
|
|
||||||
right : 350px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,100 +1,107 @@
|
|||||||
# The Homebrewery
|
# The Homebrewery
|
||||||
Welcome traveler from an antique land. Please sit and tell us of what you have seen. The unheard of monsters, who slither and bite. Tell us of the wondrous items and and artifacts you have found, their mysteries yet to be unlocked. Of the vexing vocations and surprising skills you have seen.
|
Welcome traveler from an antique land. Please sit and tell us of what you have seen. The unheard of monsters, who slither and bite. Tell us of the wondrous items and and artifacts you have found, their mysteries yet to be unlocked. Of the vexing vocations and surprising skills you have seen.
|
||||||
|
|
||||||
### Homebrew D&D made easy
|
### Homebrew D&D made easy
|
||||||
The Homebrewery makes the creation and sharing of authentic looking Fifth-Edition homebrews easy. It uses [Markdown](https://help.github.com/articles/markdown-basics/) with a little CSS magic to make your brews come to life.
|
The Homebrewery makes the creation and sharing of authentic looking Fifth-Edition homebrews easy. It uses [Markdown](https://help.github.com/articles/markdown-basics/) with a little CSS magic to make your brews come to life.
|
||||||
|
|
||||||
**Try it! **Simply edit the text on the left and watch it *update live* on the right.
|
**Try it! **Simply edit the text on the left and watch it *update live* on the right.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Editing and Sharing
|
|
||||||
When you create your own homebrew you will be given a *edit url* and a *share url*. Any changes you make will be automatically saved to the database within a few seconds. Anyone with the edit url will be able to make edits to your homebrew. So be careful about who you share it with.
|
#### Features
|
||||||
|
* Monster Stat Blocks
|
||||||
Anyone with the *share url* will be able to access a read-only version of your homebrew.
|
* Full class tables
|
||||||
|
* Notes and Tables
|
||||||
## Helping out
|
* Images
|
||||||
Like this tool? Want to buy me a beer? [Head here](https://www.patreon.com/stolksdorf) to help me keep the servers running.
|
* Page numbering and footers
|
||||||
|
* Vertical spacing, column breaks, and multiple pages
|
||||||
This tool will **always** be free, never have ads, and I will never offer any "premium" features or whatever.
|
|
||||||
|
|
||||||
|
|
||||||
|
### Editing and Sharing
|
||||||
>##### PDF Exporting
|
When you create your own homebrew you will be given a *edit url* and a *share url*. Any changes you make will be automatically saved to the database within a few seconds. Anyone with the edit url will be able to make edits to your homebrew. So be careful about who you share it with.
|
||||||
> PDF Printing works best in Chrome. If you are having quality/consistency issues, try using Chrome to print instead.
|
|
||||||
>
|
Anyone with the *share url* will be able to access a read-only version of your homebrew.
|
||||||
> After clicking the "Print" item in the navbar a new page will open and a print dialog will pop-up.
|
|
||||||
> * Set the **Destination** to "Save as PDF"
|
## Helping out
|
||||||
> * Set **Paper Size** to "Letter"
|
Like this tool? Want to buy me a beer? [Head here](https://www.patreon.com/stolksdorf) to help me keep the servers running.
|
||||||
> * If you are printing on A4 paper, make sure to have the "A4 page size snippet" in your brew
|
|
||||||
> * In **Options** make sure "Background Images" is selected.
|
This tool will **always** be free, never have ads, and I will never offer any "premium" features or whatever.
|
||||||
> * Hit print and enjoy! You're done!
|
|
||||||
>
|
### Bugs, Issues, Suggestions?
|
||||||
> If you want to save ink or have a monochrome printer, add the **Ink Friendly** snippet to your brew before you print
|
Have an idea of how to make The Homebrewery better? Or did you find something that wasn't quite right? Head [here](https://github.com/stolksdorf/NaturalCrit/issues/new) and let me know!.
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
```
|
```
|
||||||
|
|
||||||
## Big things coming in v3.0.0
|
## New Things in v2.0.0!
|
||||||
With the next major release of Homebrewery, v3.0.0, this tool *will no longer support raw HTML input for brew code*. All brews made previous to the release of v3.0.0 will still render normally.
|
What's new in the latest update? Check out the full changelog [here](/homebrew/changelog)
|
||||||
|
|
||||||
## New Things All The Time!
|
* **A whole new look** The site has been re-built from the ground up!
|
||||||
What's new in the latest update? Check out the full changelog [here](/changelog)
|
* **Better editor and Split Pane** Syntax highlighting will make writing your brews even easier, and now you can customize how large your editor is.
|
||||||
|
* **More reliable rendering** Lots of work has been put into making the rendering more reliable, not just for web, but also for PDFs
|
||||||
### Bugs, Issues, Suggestions?
|
* **PDF Printing on Chrome** You don't need to use Chrome Canary anymore!
|
||||||
Have an idea of how to make The Homebrewery better? Or did you find something that wasn't quite right? Head [here](https://github.com/stolksdorf/homebrewery/issues/new) and let me know!.
|
* ** Performance Improvements** The site should load faster, save faster, and render large brews *much* faster.
|
||||||
|
* **Patreon page** If you like this tool and want to show some thanks you can [head here](https://www.patreon.com/stolksdorf).
|
||||||
### Legal Junk
|
|
||||||
The Homebrewery is licensed using the [MIT License](https://github.com/stolksdorf/homebrewery/blob/master/license). Which means you are free to use The Homebrewery is any way that you want, except for claiming that you made it yourself.
|
>##### PDF Exporting
|
||||||
|
> After clicking the "Print" item in the navbar a new page will open and a print dialog will pop-up
|
||||||
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.
|
> * Set the **Destination** to "Save as PDF"
|
||||||
|
> * Set **Paper Size** to "Letter"
|
||||||
### More Resources
|
> * If you are printing on A4 paper, make sure to have the "A4 page size snippet" in your brew
|
||||||
If you are looking for more 5e Homebrew resources check out [r/UnearthedArcana](https://www.reddit.com/r/UnearthedArcana/) and their list of useful resources [here](https://www.reddit.com/r/UnearthedArcana/comments/3uwxx9/resources_open_to_the_community/).
|
> * In **Options** make sure "Background Images" is selected.
|
||||||
|
> * Hit print and enjoy! You're done!
|
||||||
|
>
|
||||||
|
> If you want to save ink or have a monochrome printer, add the **Ink Friendly** snippet to your brew before you print
|
||||||
<img src='http://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>
|
|
||||||
|
|
||||||
|
|
||||||
|
<img src='http://i.imgur.com/hMna6G0.png' style='position:absolute;bottom:50px;right:30px;width:280px' />
|
||||||
|
|
||||||
\page
|
<div class='pageNumber'>1</div>
|
||||||
|
<div class='footnote'>PART 1 | FANCINESS</div>
|
||||||
# Appendix
|
|
||||||
|
|
||||||
### Not quite Markdown
|
|
||||||
Although the Homebrewery uses Markdown, to get all the styling features from the PHB, we had to get a little creative. Some base HTML elements are not used as expected and I've had to include a few new keywords.
|
|
||||||
|
\page
|
||||||
___
|
|
||||||
* **Horizontal Rules** are generally used to *modify* existing elements into a different style. For example, a horizontal rule before a blockquote will give it the style of a Monster Stat Block instead of a note.
|
# Appendix
|
||||||
* **New Pages** are controlled by the author. It's impossible for the site to detect when the end of a page is reached, so indicate you'd like to start a new page, use the new page snippet to get the syntax.
|
|
||||||
* **Code Blocks** are used only to indicate column breaks. Since they don't allow for styling within them, they weren't that useful to use.
|
### Not quite Markdown
|
||||||
* **HTML** can be used to get *just* the right look for your homebrew. I've included some examples in the snippet icons above the editor.
|
Although the Homebrewery uses Markdown, to get all the styling features from the PHB, we had to get a little creative. Some base HTML elements are not used as expected and I've had to include a few new keywords.
|
||||||
|
|
||||||
|
___
|
||||||
|
* **Horizontal Rules** are generally used to *modify* existing elements into a different style. For example, a horizontal rule before a blockquote will give it the style of a Monster Stat Block instead of a note.
|
||||||
```
|
* **New Pages** are controlled by the author. It's impossible for the site to detect when the end of a page is reached, so indicate you'd like to start a new page, use the new page snippet to get the syntax.
|
||||||
```
|
* **Code Blocks** are used only to indicate column breaks. Since they don't allow for styling within them, they weren't that useful to use.
|
||||||
|
* **HTML** can be used to get *just* the right look for your homebrew. I've included some examples in the snippet icons above the editor.
|
||||||
|
|
||||||
### Images
|
|
||||||
Images must be hosted online somewhere, like imgur. You use the address to that image to reference it in your brew. Images can be included 'inline' with the text using Markdown-style images. However for background images more control is needed.
|
|
||||||
|
### Images
|
||||||
Background images should be included as HTML-style img tags. Using inline CSS you can precisely position your image where you'd like it to be. I have added both a inflow image snippet and a background image snippet to give you exmaples of how to do it.
|
Images can be included 'inline' with the text using Markdown-style images. However for background images more control is needed.
|
||||||
|
|
||||||
|
Background images should be included as HTML-style img tags. Using inline CSS you can precisely position your image where you'd like it to be. I have added both a inflow image snippet and a background image snippet to give you exmaples of how to do it.
|
||||||
|
|
||||||
### Crediting Me
|
```
|
||||||
If you'd like to credit The Homebrewery in your brew, I'd be flattered! Just reference that you made it with The Homebrewery.
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Legal Junk
|
||||||
<div class='pageNumber'>2</div>
|
You are free to use The Homebrewery is 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.
|
||||||
<div class='footnote'>PART 2 | BORING STUFF</div>
|
|
||||||
|
### Crediting Me
|
||||||
|
If you'd like to credit The Homebrewery in your brew, I'd be flattered! Just reference that you made it with The Homebrewery.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class='pageNumber'>2</div>
|
||||||
|
<div class='footnote'>PART 2 | BORING STUFF</div>
|
||||||
|
|
||||||
|
|
||||||
@@ -1,62 +1,130 @@
|
|||||||
const React = require('react');
|
var React = require('react');
|
||||||
const _ = require('lodash');
|
var _ = require('lodash');
|
||||||
|
var cx = require('classnames');
|
||||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
var request = require("superagent");
|
||||||
const Navbar = require('../../navbar/navbar.jsx');
|
|
||||||
const Items = require('../../navbar/navitems.js');
|
var Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
|
var Navbar = require('../../navbar/navbar.jsx');
|
||||||
const Store = require('homebrewery/brew.store.js');
|
var EditTitle = require('../../navbar/editTitle.navitem.jsx');
|
||||||
const Actions = require('homebrewery/brew.actions.js');
|
|
||||||
|
|
||||||
const BrewInterface = require('homebrewery/brewInterface/brewInterface.jsx');
|
var SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
|
||||||
const Utils = require('homebrewery/utils.js');
|
var Editor = require('../../editor/editor.jsx');
|
||||||
|
var BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
||||||
const KEY = 'homebrewery-new';
|
|
||||||
|
|
||||||
const NewPage = React.createClass({
|
const KEY = 'naturalCrit-homebrew-new';
|
||||||
componentDidMount: function() {
|
|
||||||
try{
|
var NewPage = React.createClass({
|
||||||
const storedBrew = JSON.parse(localStorage.getItem(KEY));
|
getInitialState: function() {
|
||||||
if(storedBrew && storedBrew.text) Actions.setBrew(storedBrew);
|
return {
|
||||||
}catch(e){}
|
title : 'My Awesome Brew v99',
|
||||||
Store.updateEmitter.on('change', this.saveToLocal);
|
text: '',
|
||||||
document.addEventListener('keydown', this.handleControlKeys);
|
isSaving : false
|
||||||
},
|
};
|
||||||
componentWillUnmount: function() {
|
},
|
||||||
Store.updateEmitter.removeListener('change', this.saveToLocal);
|
|
||||||
document.removeEventListener('keydown', this.handleControlKeys);
|
|
||||||
},
|
componentDidMount: function() {
|
||||||
|
var storage = localStorage.getItem(KEY);
|
||||||
saveToLocal : function(){
|
if(storage){
|
||||||
localStorage.setItem(KEY, JSON.stringify(Store.getBrew()));
|
this.setState({
|
||||||
},
|
text : storage
|
||||||
handleControlKeys : Utils.controlKeys({
|
})
|
||||||
s : Actions.saveNew,
|
}
|
||||||
p : Actions.localPrint
|
window.onbeforeunload = (e)=>{
|
||||||
}),
|
if(this.state.text == '') return;
|
||||||
|
return "Your homebrew isn't saved. Are you sure you want to leave?";
|
||||||
render : function(){
|
};
|
||||||
return <div className='newPage page'>
|
},
|
||||||
<Navbar>
|
|
||||||
<Nav.section>
|
|
||||||
<Items.BrewTitle />
|
componentWillUnmount: function() {
|
||||||
</Nav.section>
|
window.onbeforeunload = function(){};
|
||||||
|
},
|
||||||
<Nav.section>
|
|
||||||
<Items.StaticSave />
|
handleSplitMove : function(){
|
||||||
<Nav.item color='purple' icon='fa-file-pdf-o' onClick={Actions.localPrint}>
|
this.refs.editor.update();
|
||||||
get PDF
|
},
|
||||||
</Nav.item>
|
|
||||||
<Items.Issue collaspe={true} />
|
handleTitleChange : function(title){
|
||||||
<Items.Account />
|
this.setState({
|
||||||
</Nav.section>
|
title : title
|
||||||
</Navbar>
|
});
|
||||||
|
},
|
||||||
<div className='content'>
|
|
||||||
<BrewInterface />
|
handleTextChange : function(text){
|
||||||
</div>
|
this.setState({
|
||||||
</div>
|
text : text
|
||||||
}
|
});
|
||||||
});
|
localStorage.setItem(KEY, text);
|
||||||
|
},
|
||||||
module.exports = NewPage;
|
|
||||||
|
handleSave : function(){
|
||||||
|
this.setState({
|
||||||
|
isSaving : true
|
||||||
|
});
|
||||||
|
request.post('/homebrew/api')
|
||||||
|
.send({
|
||||||
|
title : this.state.title,
|
||||||
|
text : this.state.text
|
||||||
|
})
|
||||||
|
.end((err, res)=>{
|
||||||
|
|
||||||
|
if(err){
|
||||||
|
this.setState({
|
||||||
|
isSaving : false
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.onbeforeunload = function(){};
|
||||||
|
var brew = res.body;
|
||||||
|
localStorage.removeItem(KEY);
|
||||||
|
window.location = '/homebrew/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.handleSave}>
|
||||||
|
save
|
||||||
|
</Nav.item>
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
renderNavbar : function(){
|
||||||
|
return <Navbar>
|
||||||
|
<Nav.section>
|
||||||
|
<EditTitle title={this.state.title} onChange={this.handleTitleChange} />
|
||||||
|
</Nav.section>
|
||||||
|
|
||||||
|
<Nav.section>
|
||||||
|
{this.renderSaveButton()}
|
||||||
|
<Nav.item newTab={true} href='https://github.com/stolksdorf/naturalcrit/issues' color='red' icon='fa-bug'>
|
||||||
|
report issue
|
||||||
|
</Nav.item>
|
||||||
|
</Nav.section>
|
||||||
|
</Navbar>
|
||||||
|
},
|
||||||
|
|
||||||
|
render : function(){
|
||||||
|
return <div className='newPage 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>
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = NewPage;
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
.newPage{
|
.newPage{
|
||||||
|
|
||||||
|
.saveButton{
|
||||||
|
background-color: @orange;
|
||||||
|
&:hover{
|
||||||
|
background-color: @green;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
const React = require('react');
|
|
||||||
const _ = require('lodash');
|
|
||||||
const cx = require('classnames');
|
|
||||||
const Markdown = require('homebrewery/markdown.js');
|
|
||||||
|
|
||||||
const Headtags = require('vitreum/headtags');
|
|
||||||
|
|
||||||
const PrintPage = React.createClass({
|
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
query : {},
|
|
||||||
brew : {
|
|
||||||
text : '',
|
|
||||||
style : ''
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
getInitialState: function() {
|
|
||||||
return {
|
|
||||||
brew: this.props.brew
|
|
||||||
};
|
|
||||||
},
|
|
||||||
componentDidMount: function() {
|
|
||||||
if(this.props.query.local){
|
|
||||||
try{
|
|
||||||
this.setState({
|
|
||||||
brew : JSON.parse(
|
|
||||||
localStorage.getItem(this.props.query.local)
|
|
||||||
)
|
|
||||||
});
|
|
||||||
}catch(e){}
|
|
||||||
}
|
|
||||||
if(this.props.query.dialog) window.print();
|
|
||||||
},
|
|
||||||
//TODO: Print page shouldn't replicate functionality in brew renderer
|
|
||||||
renderStyle : function(){
|
|
||||||
if(!this.state.brew.style) return;
|
|
||||||
return <style>{this.state.brew.style.replace(/;/g, ' !important;')}</style>
|
|
||||||
},
|
|
||||||
renderPages : function(){
|
|
||||||
return _.map(this.state.brew.text.split('\\page'), (page, index) => {
|
|
||||||
return <div
|
|
||||||
className='phb v2'
|
|
||||||
id={`p${index + 1}`}
|
|
||||||
dangerouslySetInnerHTML={{__html:Markdown.render(page)}}
|
|
||||||
key={index} />;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
renderPrintInstructions : function(){
|
|
||||||
return <div className='printInstructions'>
|
|
||||||
Hey, I'm really cool instructions!!!!!
|
|
||||||
|
|
||||||
</div>
|
|
||||||
},
|
|
||||||
|
|
||||||
render : function(){
|
|
||||||
return <div className='printPage'>
|
|
||||||
<Headtags.title>{this.state.brew.title}</Headtags.title>
|
|
||||||
{this.renderPrintInstructions()}
|
|
||||||
{this.renderStyle()}
|
|
||||||
{this.renderPages()}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = PrintPage;
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
|
|
||||||
.printPage{
|
|
||||||
position : relative;
|
|
||||||
@media print{
|
|
||||||
.printInstructions{
|
|
||||||
display : none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.printInstructions{
|
|
||||||
position : absolute;
|
|
||||||
top : 0px;
|
|
||||||
right : 0px;
|
|
||||||
z-index : 100000;
|
|
||||||
padding : 30px;
|
|
||||||
background-color : @blue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,74 +1,48 @@
|
|||||||
const React = require('react');
|
var React = require('react');
|
||||||
const _ = require('lodash');
|
var _ = require('lodash');
|
||||||
const cx = require('classnames');
|
var cx = require('classnames');
|
||||||
|
|
||||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
var Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
const Navbar = require('../../navbar/navbar.jsx');
|
var Navbar = require('../../navbar/navbar.jsx');
|
||||||
const PrintLink = require('../../navbar/print.navitem.jsx');
|
|
||||||
const ReportIssue = require('../../navbar/issue.navitem.jsx');
|
var PrintLink = require('../../navbar/print.navitem.jsx');
|
||||||
//const RecentlyViewed = require('../../navbar/recent.navitem.jsx').viewed;
|
|
||||||
const Account = require('../../navbar/account.navitem.jsx');
|
var BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
||||||
|
|
||||||
const BrewRenderer = require('homebrewery/brewRenderer/brewRenderer.jsx');
|
var SharePage = React.createClass({
|
||||||
const Utils = require('homebrewery/utils.js');
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
const Actions = require('homebrewery/brew.actions.js');
|
brew : {
|
||||||
const Store = require('homebrewery/brew.store.js');
|
title : '',
|
||||||
|
text : '',
|
||||||
const Headtags = require('vitreum/headtags');
|
shareId : null,
|
||||||
|
createdAt : null,
|
||||||
const SharePage = React.createClass({
|
updatedAt : null,
|
||||||
componentDidMount: function() {
|
views : 0
|
||||||
document.addEventListener('keydown', this.handleControlKeys);
|
}
|
||||||
},
|
};
|
||||||
componentWillUnmount: function() {
|
},
|
||||||
document.removeEventListener('keydown', this.handleControlKeys);
|
|
||||||
},
|
render : function(){
|
||||||
handleControlKeys : Utils.controlKeys({
|
return <div className='sharePage page'>
|
||||||
p : Actions.print
|
<Navbar>
|
||||||
}),
|
<Nav.section>
|
||||||
|
<Nav.item className='brewTitle'>{this.props.brew.title}</Nav.item>
|
||||||
renderMetatags : function(brew){
|
</Nav.section>
|
||||||
let metatags = [
|
|
||||||
<Headtags.meta key='site_name' property='og:site_name' content='Homebrewery'/>,
|
<Nav.section>
|
||||||
<Headtags.meta key='type' property='og:type' content='article' />
|
<PrintLink shareId={this.props.brew.shareId} />
|
||||||
];
|
<Nav.item href={'/homebrew/source/' + this.props.brew.shareId} color='teal' icon='fa-code'>
|
||||||
if(brew.title){
|
source
|
||||||
metatags.push(<Headtags.meta key='title' property='og:title' content={brew.title} />);
|
</Nav.item>
|
||||||
}
|
</Nav.section>
|
||||||
if(brew.description){
|
</Navbar>
|
||||||
metatags.push(<Headtags.meta key='description' name='description' content={brew.description} />);
|
|
||||||
}
|
<div className='content'>
|
||||||
if(brew.thumbnail){
|
<BrewRenderer text={this.props.brew.text} />
|
||||||
metatags.push(<Headtags.meta key='image' property='og:image' content={brew.thumbnail} />);
|
</div>
|
||||||
}
|
</div>
|
||||||
return metatags;
|
}
|
||||||
},
|
});
|
||||||
|
|
||||||
render : function(){
|
module.exports = SharePage;
|
||||||
const brew = Store.getBrew();
|
|
||||||
return <div className='sharePage page'>
|
|
||||||
{this.renderMetatags(brew)}
|
|
||||||
|
|
||||||
<Navbar>
|
|
||||||
<Nav.section>
|
|
||||||
<Nav.item className='brewTitle'>{brew.title}</Nav.item>
|
|
||||||
</Nav.section>
|
|
||||||
|
|
||||||
<Nav.section>
|
|
||||||
<ReportIssue />
|
|
||||||
<PrintLink shareId={brew.shareId} />
|
|
||||||
<Nav.item href={'/source/' + brew.shareId} color='teal' icon='fa-code'>
|
|
||||||
source
|
|
||||||
</Nav.item>
|
|
||||||
<Account />
|
|
||||||
</Nav.section>
|
|
||||||
</Navbar>
|
|
||||||
<div className='content'>
|
|
||||||
<BrewRenderer brew={brew} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = SharePage;
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
.sharePage{
|
.sharePage{
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
const React = require('react');
|
|
||||||
const _ = require('lodash');
|
|
||||||
const cx = require('classnames');
|
|
||||||
const moment = require('moment');
|
|
||||||
|
|
||||||
const BrewItem = React.createClass({
|
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
brew : {
|
|
||||||
title : '',
|
|
||||||
description : '',
|
|
||||||
|
|
||||||
authors : []
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
renderEditLink: function(){
|
|
||||||
if(!this.props.brew.editId) return;
|
|
||||||
|
|
||||||
return <a href={`/edit/${this.props.brew.editId}`} target='_blank'>
|
|
||||||
<i className='fa fa-pencil' />
|
|
||||||
</a>
|
|
||||||
},
|
|
||||||
|
|
||||||
render : function(){
|
|
||||||
const brew = this.props.brew;
|
|
||||||
return <div className='brewItem'>
|
|
||||||
<h2>{brew.title}</h2>
|
|
||||||
<p className='description' >{brew.description}</p>
|
|
||||||
<hr />
|
|
||||||
|
|
||||||
<div className='info'>
|
|
||||||
<span>
|
|
||||||
<i className='fa fa-user' /> {brew.authors.join(', ')}
|
|
||||||
</span>
|
|
||||||
<span>
|
|
||||||
<i className='fa fa-eye' /> {brew.views}
|
|
||||||
</span>
|
|
||||||
<span>
|
|
||||||
<i className='fa fa-refresh' /> {moment(brew.updatedAt).fromNow()}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='links'>
|
|
||||||
<a href={`/share/${brew.shareId}`} target='_blank'>
|
|
||||||
<i className='fa fa-share-alt' />
|
|
||||||
</a>
|
|
||||||
{this.renderEditLink()}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = BrewItem;
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
|
|
||||||
.brewItem{
|
|
||||||
position : relative;
|
|
||||||
display : inline-block;
|
|
||||||
vertical-align : top;
|
|
||||||
box-sizing : border-box;
|
|
||||||
box-sizing : border-box;
|
|
||||||
overflow : hidden;
|
|
||||||
width : 48%;
|
|
||||||
margin-right : 15px;
|
|
||||||
margin-bottom : 15px;
|
|
||||||
padding : 5px 15px 5px 8px;
|
|
||||||
padding-right : 15px;
|
|
||||||
border : 1px solid #c9ad6a;
|
|
||||||
border-radius : 5px;
|
|
||||||
-webkit-column-break-inside : avoid;
|
|
||||||
page-break-inside : avoid;
|
|
||||||
break-inside : avoid;
|
|
||||||
h4{
|
|
||||||
margin-bottom : 5px;
|
|
||||||
font-size : 2.2em;
|
|
||||||
}
|
|
||||||
.info{
|
|
||||||
font-family : ScalySans;
|
|
||||||
font-size : 1.2em;
|
|
||||||
&>span{
|
|
||||||
margin-right : 15px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&:hover{
|
|
||||||
.links{
|
|
||||||
opacity : 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&:nth-child(2n + 1){
|
|
||||||
margin-right : 0px;
|
|
||||||
}
|
|
||||||
.links{
|
|
||||||
.animate(opacity);
|
|
||||||
position : absolute;
|
|
||||||
top : 0px;
|
|
||||||
right : 0px;
|
|
||||||
height : 100%;
|
|
||||||
width : 2em;
|
|
||||||
opacity : 0;
|
|
||||||
background-color : fade(black, 60%);
|
|
||||||
text-align : center;
|
|
||||||
a{
|
|
||||||
.animate(opacity);
|
|
||||||
display : block;
|
|
||||||
margin : 8px 0px;
|
|
||||||
opacity : 0.6;
|
|
||||||
font-size : 1.3em;
|
|
||||||
color : white;
|
|
||||||
&:hover{
|
|
||||||
opacity : 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
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 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 BREWS = _.times(25, ()=>{ return brew});
|
|
||||||
|
|
||||||
|
|
||||||
const UserPage = React.createClass({
|
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
username : '',
|
|
||||||
brews : []
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
renderBrews : function(brews){
|
|
||||||
if(!brews || !brews.length) return <div className='noBrews'>No Brews.</div>;
|
|
||||||
|
|
||||||
const sortedBrews = _.sortBy(brews, (brew)=>{ return brew.title; });
|
|
||||||
|
|
||||||
return _.map(sortedBrews, (brew, idx) => {
|
|
||||||
return <BrewItem brew={brew} key={idx}/>
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
getSortedBrews : function(){
|
|
||||||
return _.groupBy(this.props.brews, (brew)=>{
|
|
||||||
return (brew.published ? 'published' : 'private')
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
renderPrivateBrews : function(privateBrews){
|
|
||||||
if(!privateBrews || !privateBrews.length) return;
|
|
||||||
|
|
||||||
return [
|
|
||||||
<h1>{this.props.username}'s unpublished brews</h1>,
|
|
||||||
this.renderBrews(privateBrews)
|
|
||||||
];
|
|
||||||
},
|
|
||||||
|
|
||||||
render : function(){
|
|
||||||
const brews = this.getSortedBrews();
|
|
||||||
console.log('user brews', brews);
|
|
||||||
|
|
||||||
return <div className='userPage page'>
|
|
||||||
<Navbar>
|
|
||||||
<Nav.section>
|
|
||||||
<RecentNavItem.both />
|
|
||||||
<Account userPage={this.props.username} />
|
|
||||||
</Nav.section>
|
|
||||||
</Navbar>
|
|
||||||
|
|
||||||
<div className='content'>
|
|
||||||
<div className='phb'>
|
|
||||||
<h1>{this.props.username}'s brews</h1>
|
|
||||||
{this.renderBrews(brews.published)}
|
|
||||||
{this.renderPrivateBrews(brews.private)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = UserPage;
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
|
|
||||||
.noColumns(){
|
|
||||||
column-count : auto;
|
|
||||||
column-fill : auto;
|
|
||||||
column-gap : auto;
|
|
||||||
column-width : auto;
|
|
||||||
-webkit-column-count : auto;
|
|
||||||
-moz-column-count : auto;
|
|
||||||
-webkit-column-width : auto;
|
|
||||||
-moz-column-width : auto;
|
|
||||||
-webkit-column-gap : auto;
|
|
||||||
-moz-column-gap : auto;
|
|
||||||
}
|
|
||||||
.userPage{
|
|
||||||
.content{
|
|
||||||
overflow-y : scroll;
|
|
||||||
.phb{
|
|
||||||
.noColumns();
|
|
||||||
height : auto;
|
|
||||||
min-height : 279.4mm;
|
|
||||||
margin : 20px auto;
|
|
||||||
&::after{
|
|
||||||
display : none;
|
|
||||||
}
|
|
||||||
.noBrews{
|
|
||||||
margin : 10px 0px;
|
|
||||||
font-size : 1.3em;
|
|
||||||
font-style : italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
86
client/main/main.jsx
Normal file
86
client/main/main.jsx
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
var React = require('react');
|
||||||
|
var _ = require('lodash');
|
||||||
|
var cx = require('classnames');
|
||||||
|
|
||||||
|
var Router = require('pico-router');
|
||||||
|
|
||||||
|
var NaturalCritIcon = require('naturalcrit/svg/naturalcrit.svg.jsx');
|
||||||
|
var HomebrewIcon = require('naturalcrit/svg/homebrew.svg.jsx');
|
||||||
|
|
||||||
|
var Main = React.createClass({
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
tools : [
|
||||||
|
{
|
||||||
|
id : 'homebrew',
|
||||||
|
path : '/homebrew',
|
||||||
|
name : 'The Homebrewery',
|
||||||
|
icon : <HomebrewIcon />,
|
||||||
|
desc : 'Make authentic-looking 5e homebrews using Markdown',
|
||||||
|
|
||||||
|
show : true,
|
||||||
|
beta : false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id : 'spellsort',
|
||||||
|
path : '/spellsort',
|
||||||
|
name : 'Spellsort',
|
||||||
|
icon : <HomebrewIcon />,
|
||||||
|
desc : 'Sort and search through spells',
|
||||||
|
|
||||||
|
show : true,
|
||||||
|
beta : true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id : 'homebrewfg2',
|
||||||
|
path : '/homebrew',
|
||||||
|
name : 'The Homebrewery',
|
||||||
|
icon : <HomebrewIcon />,
|
||||||
|
desc : 'Make authentic-looking 5e homebrews using Markdown',
|
||||||
|
|
||||||
|
show : false,
|
||||||
|
beta : false
|
||||||
|
}
|
||||||
|
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
renderTool : function(tool){
|
||||||
|
if(!tool.show) return null;
|
||||||
|
|
||||||
|
return <a href={tool.path} className={cx('tool', tool.id, {beta : tool.beta})} key={tool.id}>
|
||||||
|
<div className='content'>
|
||||||
|
{tool.icon}
|
||||||
|
<h2>{tool.name}</h2>
|
||||||
|
<p>{tool.desc}</p>
|
||||||
|
</div>
|
||||||
|
</a>;
|
||||||
|
},
|
||||||
|
|
||||||
|
renderTools : function(){
|
||||||
|
return _.map(this.props.tools, (tool)=>{
|
||||||
|
return this.renderTool(tool);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
render : function(){
|
||||||
|
return <div className='main'>
|
||||||
|
<div className='top'>
|
||||||
|
<div className='logo'>
|
||||||
|
<NaturalCritIcon />
|
||||||
|
<span className='name'>
|
||||||
|
Natural
|
||||||
|
<span className='crit'>Crit</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p>Top-tier tools for the discerning DM</p>
|
||||||
|
</div>
|
||||||
|
<div className='tools'>
|
||||||
|
{this.renderTools()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = Main;
|
||||||
136
client/main/main.less
Normal file
136
client/main/main.less
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
@import 'naturalcrit/styles/core.less';
|
||||||
|
.main{
|
||||||
|
height : 100vh;
|
||||||
|
background-color : white;
|
||||||
|
.top{
|
||||||
|
.fadeInTop(1s);
|
||||||
|
.delay(0.5);
|
||||||
|
margin-bottom : 100px;
|
||||||
|
padding-top : 100px;
|
||||||
|
text-align : center;
|
||||||
|
.logo{
|
||||||
|
font-size : 4em;
|
||||||
|
color : black;
|
||||||
|
svg{
|
||||||
|
height : .9em;
|
||||||
|
margin-right : .2em;
|
||||||
|
cursor : pointer;
|
||||||
|
fill : black;
|
||||||
|
}
|
||||||
|
.name{
|
||||||
|
font-family : 'CodeLight';
|
||||||
|
.crit{
|
||||||
|
font-family : 'CodeBold';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p{
|
||||||
|
margin-top : 10px;
|
||||||
|
font-size : 1.3em;
|
||||||
|
font-style : italic;
|
||||||
|
color : @grey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.tools{
|
||||||
|
width : 100%;
|
||||||
|
text-align : center;
|
||||||
|
.tool{
|
||||||
|
.sequentialDelay(0.5s, 1s);
|
||||||
|
.fadeInDown(1s);
|
||||||
|
.keep();
|
||||||
|
display : inline-block;
|
||||||
|
cursor : pointer;
|
||||||
|
opacity : 0;
|
||||||
|
color : black;
|
||||||
|
text-align : center;
|
||||||
|
text-decoration : none;
|
||||||
|
&+.tool{
|
||||||
|
border-left : 1px solid #666;
|
||||||
|
}
|
||||||
|
.content{
|
||||||
|
.addSketch(360px);
|
||||||
|
.animateAll(0.5s);
|
||||||
|
position : relative;
|
||||||
|
width : 320px;
|
||||||
|
padding : 35px;
|
||||||
|
&:hover{
|
||||||
|
svg, h2{
|
||||||
|
.transform(scale(1.3));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h2{
|
||||||
|
.animateAll(0.5s);
|
||||||
|
font-family : 'CodeBold';
|
||||||
|
font-size : 2em;
|
||||||
|
}
|
||||||
|
p{
|
||||||
|
max-width : 300px;
|
||||||
|
margin : 20px auto;
|
||||||
|
line-height : 1.5em;
|
||||||
|
}
|
||||||
|
svg{
|
||||||
|
.animateAll(0.5s);
|
||||||
|
height : 10em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.content:hover{
|
||||||
|
background-color : fade(@teal, 20%);
|
||||||
|
}
|
||||||
|
//Beta styles
|
||||||
|
&.beta{
|
||||||
|
cursor : initial;
|
||||||
|
.content{
|
||||||
|
&:hover{
|
||||||
|
svg, h2{
|
||||||
|
.transform(scale(1.0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
svg, h2{
|
||||||
|
opacity : 0.3;
|
||||||
|
}
|
||||||
|
&:after{
|
||||||
|
.animateAll();
|
||||||
|
content : "beta!";
|
||||||
|
position : absolute;
|
||||||
|
display : block;
|
||||||
|
top : 120px;
|
||||||
|
left : 0px;
|
||||||
|
width : 100%;
|
||||||
|
padding : 10px 0px;
|
||||||
|
//opacity : 0;
|
||||||
|
background-color : fade(@grey, 50%);
|
||||||
|
font-size : 2em;
|
||||||
|
font-weight : 800;
|
||||||
|
text-align : center;
|
||||||
|
text-transform : uppercase;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.addSketch(@length, @color : black){
|
||||||
|
path, line, polyline, circle, rect, polygon {
|
||||||
|
.sketch(@length, @color, 4s);
|
||||||
|
stroke-dasharray : @length;
|
||||||
|
stroke-dashoffset : 0px;
|
||||||
|
stroke : @color;
|
||||||
|
stroke-width : 0.5px;
|
||||||
|
fill : @color;
|
||||||
|
//.animateAll(3s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sketch(@length, @color : black, @duration : 3s, @easing : @defaultEasing){
|
||||||
|
.createAnimation(sketch, @duration, @easing);
|
||||||
|
.sketchKeyFrames(){
|
||||||
|
0% { stroke-dashoffset : @length; fill: transparent;}
|
||||||
|
50% { stroke-dashoffset : @length; fill: transparent;}
|
||||||
|
80% { stroke-dashoffset : 0px; fill: transparent;}
|
||||||
|
100% { stroke-dashoffset : 0px; fill:@color;}
|
||||||
|
}
|
||||||
|
@-webkit-keyframes sketch {.sketchKeyFrames();}
|
||||||
|
@-moz-keyframes sketch {.sketchKeyFrames();}
|
||||||
|
@-ms-keyframes sketch {.sketchKeyFrames();}
|
||||||
|
@-o-keyframes sketch {.sketchKeyFrames();}
|
||||||
|
@keyframes sketch {.sketchKeyFrames();}
|
||||||
|
}
|
||||||
21
client/spellsort/navbar/navbar.jsx
Normal file
21
client/spellsort/navbar/navbar.jsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
var React = require('react');
|
||||||
|
var _ = require('lodash');
|
||||||
|
|
||||||
|
var Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
|
|
||||||
|
var Navbar = React.createClass({
|
||||||
|
render : function(){
|
||||||
|
return <Nav.base>
|
||||||
|
<Nav.section>
|
||||||
|
<Nav.logo />
|
||||||
|
<Nav.item href='/spellsort' className='spellsortLogo'>
|
||||||
|
<div>Spellsort</div>
|
||||||
|
</Nav.item>
|
||||||
|
<Nav.item>v0.0.0</Nav.item>
|
||||||
|
</Nav.section>
|
||||||
|
{this.props.children}
|
||||||
|
</Nav.base>
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = Navbar;
|
||||||
16
client/spellsort/navbar/navbar.less
Normal file
16
client/spellsort/navbar/navbar.less
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
|
||||||
|
.spellsort nav{
|
||||||
|
.spellsortLogo{
|
||||||
|
.animate(color);
|
||||||
|
font-family : CodeBold;
|
||||||
|
font-size : 12px;
|
||||||
|
color : white;
|
||||||
|
div{
|
||||||
|
margin-top : 2px;
|
||||||
|
margin-bottom : -2px;
|
||||||
|
}
|
||||||
|
&:hover{
|
||||||
|
color : @teal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
32
client/spellsort/sorter/sorter.jsx
Normal file
32
client/spellsort/sorter/sorter.jsx
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
var React = require('react');
|
||||||
|
var _ = require('lodash');
|
||||||
|
var cx = require('classnames');
|
||||||
|
|
||||||
|
var Sorter = React.createClass({
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
spells : []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
renderSpell : function(spell){
|
||||||
|
return <div className='spell' key={spell.id}>
|
||||||
|
{spell.name}
|
||||||
|
</div>
|
||||||
|
},
|
||||||
|
|
||||||
|
renderSpells : function(){
|
||||||
|
return _.map(this.props.spells, (spell)=>{
|
||||||
|
return this.renderSpell(spell)
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
render : function(){
|
||||||
|
return <div className='sorter'>
|
||||||
|
{this.renderSpells()}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = Sorter;
|
||||||
3
client/spellsort/sorter/sorter.less
Normal file
3
client/spellsort/sorter/sorter.less
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
.COM{
|
||||||
|
|
||||||
|
}
|
||||||
97
client/spellsort/spellRenderer/spellRenderer.jsx
Normal file
97
client/spellsort/spellRenderer/spellRenderer.jsx
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
var React = require('react');
|
||||||
|
var _ = require('lodash');
|
||||||
|
var cx = require('classnames');
|
||||||
|
|
||||||
|
var Markdown = require('marked');
|
||||||
|
|
||||||
|
var SpellRenderer = React.createClass({
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
spells : []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
//TODO: Add in ritual tag
|
||||||
|
getSubtitle : function(spell){
|
||||||
|
if(spell.level == 0) return <p><em>{spell.school} cantrip</em></p>;
|
||||||
|
if(spell.level == 1) return <p><em>{spell.level}st-level {spell.school}</em></p>;
|
||||||
|
if(spell.level == 2) return <p><em>{spell.level}nd-level {spell.school}</em></p>;
|
||||||
|
if(spell.level == 3) return <p><em>{spell.level}rd-level {spell.school}</em></p>;
|
||||||
|
return <p><em>{spell.level}th-level {spell.school}</em></p>;
|
||||||
|
},
|
||||||
|
|
||||||
|
getComponents : function(spell){
|
||||||
|
var result = [];
|
||||||
|
if(spell.components.v) result.push('V');
|
||||||
|
if(spell.components.s) result.push('S');
|
||||||
|
if(spell.components.m) result.push('M ' + spell.components.m);
|
||||||
|
return result.join(', ');
|
||||||
|
},
|
||||||
|
|
||||||
|
getHigherLevels : function(spell){
|
||||||
|
if(!spell.scales) return null;
|
||||||
|
return <p>
|
||||||
|
<strong><em>At Higher Levels. </em></strong>
|
||||||
|
<span dangerouslySetInnerHTML={{__html: Markdown(spell.scales)}} />
|
||||||
|
</p>;
|
||||||
|
},
|
||||||
|
|
||||||
|
getClasses : function(spell){
|
||||||
|
if(!spell.classes || !spell.classes.length) return null;
|
||||||
|
|
||||||
|
var classes = _.map(spell.classes, (cls)=>{
|
||||||
|
return _.capitalize(cls);
|
||||||
|
}).join(', ');
|
||||||
|
|
||||||
|
return <li>
|
||||||
|
<strong>Classes:</strong> {classes}
|
||||||
|
</li>
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
renderSpell : function(spell){
|
||||||
|
console.log('rendering', spell);
|
||||||
|
return <div className='spell' key={spell.id}>
|
||||||
|
|
||||||
|
<h4>{spell.name}</h4>
|
||||||
|
{this.getSubtitle(spell)}
|
||||||
|
<hr />
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<strong>Casting Time:</strong> {spell.casting_time}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Range:</strong> {spell.range}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Components:</strong> {this.getComponents(spell)}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Duration:</strong> {spell.duration}
|
||||||
|
</li>
|
||||||
|
{this.getClasses(spell)}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<span dangerouslySetInnerHTML={{__html: Markdown(spell.description)}} />
|
||||||
|
{this.getHigherLevels(spell)}
|
||||||
|
</div>
|
||||||
|
},
|
||||||
|
|
||||||
|
renderSpells : function(){
|
||||||
|
return _.map(this.props.spells, (spell)=>{
|
||||||
|
return this.renderSpell(spell);
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
render : function(){
|
||||||
|
return <div className='spellRenderer'>
|
||||||
|
|
||||||
|
<div className='phb'>
|
||||||
|
{this.renderSpells()}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = SpellRenderer;
|
||||||
40
client/spellsort/spellRenderer/spellRenderer.less
Normal file
40
client/spellsort/spellRenderer/spellRenderer.less
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
@import (less) 'naturalcrit/phbStyle/phb.style.less';
|
||||||
|
.pane{
|
||||||
|
position : relative;
|
||||||
|
}
|
||||||
|
.spellRenderer{
|
||||||
|
overflow-y : scroll;
|
||||||
|
|
||||||
|
&>.phb{
|
||||||
|
margin-right : auto;
|
||||||
|
margin-bottom : 30px;
|
||||||
|
margin-left : auto;
|
||||||
|
box-shadow : 1px 4px 14px #000;
|
||||||
|
|
||||||
|
height : auto;
|
||||||
|
width : 100%;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
column-count : initial;
|
||||||
|
column-fill : initial;
|
||||||
|
column-gap : initial;
|
||||||
|
column-width : initial;
|
||||||
|
-webkit-column-count : initial;
|
||||||
|
-moz-column-count : initial;
|
||||||
|
-webkit-column-width : initial;
|
||||||
|
-moz-column-width : initial;
|
||||||
|
-webkit-column-gap : initial;
|
||||||
|
-moz-column-gap : initial;
|
||||||
|
|
||||||
|
.spell{
|
||||||
|
display: inline;
|
||||||
|
width : 8cm;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
45
client/spellsort/spellsort.jsx
Normal file
45
client/spellsort/spellsort.jsx
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
var React = require('react');
|
||||||
|
var _ = require('lodash');
|
||||||
|
var cx = require('classnames');
|
||||||
|
|
||||||
|
var Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
|
var Navbar = require('./navbar/navbar.jsx');
|
||||||
|
|
||||||
|
var SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
|
||||||
|
var SpellRenderer = require('./spellRenderer/spellRenderer.jsx');
|
||||||
|
var Sorter = require('./sorter/sorter.jsx');
|
||||||
|
|
||||||
|
var SpellSort = React.createClass({
|
||||||
|
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
spells : []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
handleSplitMove : function(){
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
render : function(){
|
||||||
|
console.log(this.props.spells);
|
||||||
|
|
||||||
|
return <div className='spellsort page'>
|
||||||
|
<Navbar>
|
||||||
|
<Nav.section>
|
||||||
|
yo
|
||||||
|
</Nav.section>
|
||||||
|
</Navbar>
|
||||||
|
<div className='content'>
|
||||||
|
<SplitPane onDragFinish={this.handleSplitMove} ref='pane'>
|
||||||
|
|
||||||
|
<Sorter spells={this.props.spells} />
|
||||||
|
<SpellRenderer spells={this.props.spells} />
|
||||||
|
|
||||||
|
</SplitPane>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = SpellSort;
|
||||||
4
client/spellsort/spellsort.less
Normal file
4
client/spellsort/spellsort.less
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
@import 'naturalcrit/styles/core.less';
|
||||||
|
.spellsort{
|
||||||
|
|
||||||
|
}
|
||||||
30
client/template.dot
Normal file
30
client/template.dot
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<script>global=window</script>
|
||||||
|
<link href="//netdna.bootstrapcdn.com/font-awesome/4.6.2/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/main/favicon.ico" type="image/x-icon" />
|
||||||
|
{{=vitreum.css}}
|
||||||
|
{{=vitreum.globals}}
|
||||||
|
<title>Natural Crit - D&D Tools</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="reactContainer">{{=vitreum.component}}</div>
|
||||||
|
</body>
|
||||||
|
{{=vitreum.libs}}
|
||||||
|
{{=vitreum.js}}
|
||||||
|
{{=vitreum.reactRender}}
|
||||||
|
|
||||||
|
{{? vitreum.inProduction}}
|
||||||
|
<script>
|
||||||
|
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||||
|
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||||
|
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||||
|
})(window,document,'script','http://www.google-analytics.com/analytics.js','ga');
|
||||||
|
|
||||||
|
ga('create', 'UA-72212009-1', 'auto');
|
||||||
|
ga('send', 'pageview');
|
||||||
|
</script>
|
||||||
|
{{?}}
|
||||||
|
</html>
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
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>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"log_level" : "info",
|
|
||||||
"login_path" : "/dev/login",
|
|
||||||
"jwt_secret" : "secretsecret",
|
|
||||||
"admin" : {
|
|
||||||
"user" : "admin",
|
|
||||||
"pass" : "password",
|
|
||||||
"key" : "adminadminadmin"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"login_path" : "http://naturalcrit.com/login",
|
|
||||||
"log_level" : "warn"
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"login_path" : "http://staging.naturalcrit.com/login",
|
|
||||||
"log_level" : "trace"
|
|
||||||
}
|
|
||||||
52
gulpfile.js
Normal file
52
gulpfile.js
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
var vitreumTasks = require("vitreum/tasks");
|
||||||
|
var gulp = require("gulp");
|
||||||
|
|
||||||
|
|
||||||
|
var gulp = vitreumTasks(gulp, {
|
||||||
|
entryPoints: [
|
||||||
|
'./client/main',
|
||||||
|
'./client/homebrew',
|
||||||
|
'./client/spellsort',
|
||||||
|
'./client/admin'
|
||||||
|
],
|
||||||
|
|
||||||
|
DEV: true,
|
||||||
|
buildPath: "./build/",
|
||||||
|
pageTemplate: "./client/template.dot",
|
||||||
|
projectModules: ["./shared/naturalcrit","./shared/codemirror"],
|
||||||
|
additionalRequirePaths : ['./shared', './node_modules'],
|
||||||
|
assetExts: ["*.svg", "*.png", "*.jpg", "*.pdf", "*.eot", "*.otf", "*.woff", "*.woff2", "*.ico", "*.ttf"],
|
||||||
|
serverWatchPaths: ["server"],
|
||||||
|
serverScript: "server.js",
|
||||||
|
libs: [
|
||||||
|
"react",
|
||||||
|
"react-dom",
|
||||||
|
"lodash",
|
||||||
|
"classnames",
|
||||||
|
|
||||||
|
//From ./shared
|
||||||
|
"codemirror",
|
||||||
|
"codemirror/mode/gfm/gfm.js",
|
||||||
|
'codemirror/mode/javascript/javascript.js',
|
||||||
|
|
||||||
|
"moment",
|
||||||
|
"superagent",
|
||||||
|
"marked",
|
||||||
|
"pico-router",
|
||||||
|
"pico-flux"
|
||||||
|
],
|
||||||
|
clientLibs: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
var rename = require('gulp-rename');
|
||||||
|
var less = require('gulp-less');
|
||||||
|
gulp.task('phb', function(){
|
||||||
|
gulp.src('./shared/naturalcrit/phbStyle/phb.style.less')
|
||||||
|
.pipe(less())
|
||||||
|
.pipe(rename('phb.standalone.css'))
|
||||||
|
.pipe(gulp.dest('./'));
|
||||||
|
})
|
||||||
|
|
||||||
21
license
21
license
@@ -1,21 +0,0 @@
|
|||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2016 Scott Tolksdorf
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
50
package.json
50
package.json
@@ -1,55 +1,31 @@
|
|||||||
{
|
{
|
||||||
"name": "homebrewery",
|
"name": "naturalcrit",
|
||||||
"description": "Create authentic looking D&D homebrews using only markdown",
|
"description": "D&D Tools for the discerning DM",
|
||||||
"version": "3.0.0",
|
"version": "2.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "node scripts/dev.js",
|
"postinstall": "gulp prod",
|
||||||
"quick": "node scripts/quick.js",
|
"start": "node server.js"
|
||||||
"build": "node scripts/build.js",
|
|
||||||
"populate": "node scripts/populate.js",
|
|
||||||
"prod": "set NODE_ENV=production&& npm run build",
|
|
||||||
"postinstall": "npm run build",
|
|
||||||
"start": "node server.js",
|
|
||||||
"snippet": "nodemon scripts/snippet.test.js",
|
|
||||||
"todo": "./node_modules/.bin/fixme -i node_modules/** -i build/**",
|
|
||||||
"test": "mocha tests",
|
|
||||||
"test:dev": "nodemon -x mocha tests || exit 0",
|
|
||||||
"test:markdown": "nodemon -x mocha tests/markdown.test.js || exit 0"
|
|
||||||
},
|
},
|
||||||
"author": "stolksdorf",
|
"author": "stolksdorf",
|
||||||
"license": "MIT",
|
"license": "BSD-2-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"app-module-path": "^1.0.4",
|
||||||
"basic-auth": "^1.0.3",
|
"basic-auth": "^1.0.3",
|
||||||
"body-parser": "^1.14.2",
|
"body-parser": "^1.14.2",
|
||||||
"classnames": "^2.2.0",
|
"classnames": "^2.2.0",
|
||||||
"codemirror": "^5.22.0",
|
|
||||||
"cookie-parser": "^1.4.3",
|
|
||||||
"egads": "^1.0.1",
|
|
||||||
"express": "^4.13.3",
|
"express": "^4.13.3",
|
||||||
"jwt-simple": "^0.5.1",
|
"gulp": "^3.9.0",
|
||||||
"lodash": "^4.17.3",
|
"lodash": "^4.11.2",
|
||||||
"loglevel": "^1.4.1",
|
|
||||||
"marked": "^0.3.5",
|
"marked": "^0.3.5",
|
||||||
"moment": "^2.11.0",
|
"moment": "^2.11.0",
|
||||||
"mongoose": "^4.3.3",
|
"mongoose": "^4.3.3",
|
||||||
"nconf": "^0.8.4",
|
"pico-flux": "^1.1.0",
|
||||||
"pico-flux": "^2.1.2",
|
|
||||||
"pico-router": "^1.1.0",
|
"pico-router": "^1.1.0",
|
||||||
"react": "^15.4.1",
|
"react": "^15.0.2",
|
||||||
"react-dom": "^15.4.1",
|
"react-dom": "^15.0.2",
|
||||||
"shortid": "^2.2.4",
|
"shortid": "^2.2.4",
|
||||||
"striptags": "^2.1.1",
|
"striptags": "^2.1.1",
|
||||||
"superagent": "^1.6.1",
|
"superagent": "^1.6.1",
|
||||||
"vitreum": "^4.0.12"
|
"vitreum": "^3.2.1"
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"app-module-path": "^2.1.0",
|
|
||||||
"chai": "^3.5.0",
|
|
||||||
"chai-as-promised": "^6.0.0",
|
|
||||||
"chai-subset": "^1.4.0",
|
|
||||||
"fixme": "^0.4.3",
|
|
||||||
"mocha": "^3.2.0",
|
|
||||||
"supertest": "^2.0.1",
|
|
||||||
"supertest-as-promised": "^4.0.2"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
526
phb.standalone.css
Normal file
526
phb.standalone.css
Normal file
File diff suppressed because one or more lines are too long
@@ -1,21 +0,0 @@
|
|||||||
const label = 'build';
|
|
||||||
console.time(label);
|
|
||||||
|
|
||||||
const clean = require('vitreum/steps/clean.js');
|
|
||||||
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');
|
|
||||||
|
|
||||||
Promise.resolve()
|
|
||||||
.then(()=>clean())
|
|
||||||
.then(()=>lib(Proj.libs))
|
|
||||||
.then(()=>jsx('homebrew', './client/homebrew/homebrew.jsx', Proj.libs, ['./shared']))
|
|
||||||
.then((deps)=>less('homebrew', ['./shared'], deps))
|
|
||||||
.then(()=>jsx('admin', './client/admin/admin.jsx', Proj.libs, ['./shared']))
|
|
||||||
.then((deps)=>less('admin', ['./shared'], deps))
|
|
||||||
.then(()=>asset(Proj.assets, ['./shared', './client']))
|
|
||||||
.then(()=>console.timeEnd.bind(console, label))
|
|
||||||
.catch(console.error);
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
const label = 'dev';
|
|
||||||
console.time(label);
|
|
||||||
|
|
||||||
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((deps)=>less('homebrew', './shared', deps))
|
|
||||||
.then(()=>jsx('admin', './client/admin/admin.jsx', Proj.libs, './shared'))
|
|
||||||
.then((deps)=>less('admin', './shared', deps))
|
|
||||||
.then(()=>assets(Proj.assets, ['./shared', './client']))
|
|
||||||
.then(()=>livereload())
|
|
||||||
.then(()=>server('./server.js', ['server']))
|
|
||||||
.then(()=>console.timeEnd.bind(console, label))
|
|
||||||
.catch(console.error)
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
|
|
||||||
require('fixme')({
|
|
||||||
path: process.cwd(),
|
|
||||||
ignored_directories: ['node_modules/**', '.git/**', 'build/**'],
|
|
||||||
file_patterns: ['**/*.js', '**/*.jsx', '**/*.less'],
|
|
||||||
file_encoding: 'utf8',
|
|
||||||
line_length_limit: 200
|
|
||||||
});
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
//Populates the DB with a bunch of brews for UI testing
|
|
||||||
const _ = require('lodash');
|
|
||||||
|
|
||||||
const DB = require('../server/db.js');
|
|
||||||
const BrewData = require('../server/brew.data.js');
|
|
||||||
const BrewGen = require('../test/brew.gen.js');
|
|
||||||
|
|
||||||
return Promise.resolve()
|
|
||||||
.then(DB.connect)
|
|
||||||
.then(BrewData.removeAll)
|
|
||||||
.then(() => {
|
|
||||||
console.log('Adding random brews...');
|
|
||||||
return BrewGen.populateDB(BrewGen.random(50));
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
console.log('Adding specific brews...');
|
|
||||||
return BrewGen.populateDB(BrewGen.static());
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
return DB.close();
|
|
||||||
})
|
|
||||||
.catch(console.error);
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
{
|
|
||||||
"assets": ["*.png","*.otf", "*.ico", "*.jpg"],
|
|
||||||
"shared":["./shared"],
|
|
||||||
"libs" : [
|
|
||||||
"react",
|
|
||||||
"react-dom",
|
|
||||||
"lodash",
|
|
||||||
"classnames",
|
|
||||||
"codemirror",
|
|
||||||
"codemirror/mode/gfm/gfm.js",
|
|
||||||
"codemirror/mode/javascript/javascript.js",
|
|
||||||
"codemirror/mode/css/css.js",
|
|
||||||
"moment",
|
|
||||||
"superagent",
|
|
||||||
"marked",
|
|
||||||
"pico-router",
|
|
||||||
"pico-flux"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
const label = 'quick';
|
|
||||||
console.time(label);
|
|
||||||
|
|
||||||
const jsx = require('vitreum/steps/jsx.js').partial;
|
|
||||||
const less = require('vitreum/steps/less.js').partial;
|
|
||||||
const server = require('vitreum/steps/server.watch.js').partial;
|
|
||||||
|
|
||||||
const Proj = require('./project.json');
|
|
||||||
|
|
||||||
Promise.resolve()
|
|
||||||
.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(server('./server.js', ['server']))
|
|
||||||
.then(console.timeEnd.bind(console, label))
|
|
||||||
.catch(console.error);
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
const snippets = require('../shared/homebrewery/snippets');
|
|
||||||
|
|
||||||
console.log(snippets);
|
|
||||||
|
|
||||||
//console.log(snippets.brew.spell());
|
|
||||||
//console.log(snippets.brew.table());
|
|
||||||
|
|
||||||
console.log(snippets.brew.noncasterTable());
|
|
||||||
106
server.js
106
server.js
@@ -1,36 +1,70 @@
|
|||||||
const config = require('nconf')
|
'use strict';
|
||||||
.argv()
|
var _ = require('lodash');
|
||||||
.env({ lowerCase: true })
|
require('app-module-path').addPath('./shared');
|
||||||
.file('environment', { file: `config/${process.env.NODE_ENV}.json` })
|
var vitreumRender = require('vitreum/render');
|
||||||
.file('defaults', { file: 'config/default.json' });
|
var bodyParser = require('body-parser')
|
||||||
|
var express = require("express");
|
||||||
const log = require('loglevel');
|
var app = express();
|
||||||
log.setLevel(config.get('log_level'));
|
app.use(express.static(__dirname + '/build'));
|
||||||
|
app.use(bodyParser.json());
|
||||||
//Server
|
|
||||||
const app = require('./server/app.js');
|
//Mongoose
|
||||||
|
var mongoose = require('mongoose');
|
||||||
/*
|
var mongoUri = process.env.MONGOLAB_URI || process.env.MONGOHQ_URL || 'mongodb://localhost/naturalcrit';
|
||||||
app.use((req, res, next) => {
|
mongoose.connect(mongoUri);
|
||||||
log.debug('---------------------------');
|
mongoose.connection.on('error', function(){
|
||||||
log.debug(req.method, req.path);
|
console.log(">>>ERROR: Run Mongodb.exe ya goof!");
|
||||||
|
});
|
||||||
if (req.params) {
|
|
||||||
log.debug('req params', req.params);
|
//Admin route
|
||||||
}
|
process.env.ADMIN_USER = process.env.ADMIN_USER || 'admin';
|
||||||
if (req.query) {
|
process.env.ADMIN_PASS = process.env.ADMIN_PASS || 'password';
|
||||||
log.debug('req query', req.query);
|
process.env.ADMIN_KEY = process.env.ADMIN_KEY || 'admin_key';
|
||||||
}
|
var auth = require('basic-auth');
|
||||||
|
app.get('/admin', function(req, res){
|
||||||
next();
|
var 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')
|
||||||
require('./server/db.js').connect()
|
}
|
||||||
.then(()=>{
|
vitreumRender({
|
||||||
const PORT = process.env.PORT || 8000;
|
page: './build/admin/bundle.dot',
|
||||||
const httpServer = app.listen(PORT, () => {
|
prerenderWith : './client/admin/admin.jsx',
|
||||||
log.info(`server on port:${PORT}`);
|
clearRequireCache : !process.env.PRODUCTION,
|
||||||
});
|
initialProps: {
|
||||||
})
|
url: req.originalUrl,
|
||||||
.catch((err)=>console.error(err))
|
admin_key : process.env.ADMIN_KEY,
|
||||||
|
},
|
||||||
|
}, function (err, page) {
|
||||||
|
return res.send(page)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
//Populate homebrew routes
|
||||||
|
app = require('./server/homebrew.api.js')(app);
|
||||||
|
app = require('./server/homebrew.server.js')(app);
|
||||||
|
|
||||||
|
|
||||||
|
//Populate Spellsort routes
|
||||||
|
app = require('./server/spellsort.server.js')(app);
|
||||||
|
|
||||||
|
|
||||||
|
app.get('*', function (req, res) {
|
||||||
|
vitreumRender({
|
||||||
|
page: './build/main/bundle.dot',
|
||||||
|
globals:{},
|
||||||
|
prerenderWith : './client/main/main.jsx',
|
||||||
|
initialProps: {
|
||||||
|
url: req.originalUrl
|
||||||
|
},
|
||||||
|
clearRequireCache : !process.env.PRODUCTION,
|
||||||
|
}, function (err, page) {
|
||||||
|
return res.send(page)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
var port = process.env.PORT || 8000;
|
||||||
|
app.listen(port);
|
||||||
|
console.log('Listening on localhost:' + port);
|
||||||
164
server/5espells.js
Normal file
164
server/5espells.js
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
module.exports = {
|
||||||
|
"Abi-Dalzim's Horrid Wilting": {
|
||||||
|
"athigherlevel": "",
|
||||||
|
"description": "(a bit of sponge)\r\nYou draw the moisture from every creature in a 30-foot cube centered on a point you choose within range. Each creature in that area must make a Constitution saving throw. Constructs and undead aren't affected, and plants and water elementals make this saving throw with disadvantage. A creature takes 10d8 necrotic damage on a failed save, or half as much damage on a successful one.",
|
||||||
|
"source": "Elemental Evil",
|
||||||
|
"level": "8",
|
||||||
|
"range": "150 feet",
|
||||||
|
"casting_time": "1 Action",
|
||||||
|
"id": "406",
|
||||||
|
"slug": "abi-dalzims-horrid-wilting",
|
||||||
|
"scag": "0",
|
||||||
|
"page": "15",
|
||||||
|
"components": "V, S, M",
|
||||||
|
"ritual": "No",
|
||||||
|
"name": "Abi-Dalzim's Horrid Wilting",
|
||||||
|
"school": "Necromancy",
|
||||||
|
"ee": "1",
|
||||||
|
"classes": [
|
||||||
|
"Sorcerer",
|
||||||
|
"Wizard"
|
||||||
|
],
|
||||||
|
"duration": "Instantaneous",
|
||||||
|
"concentration": "No"
|
||||||
|
},
|
||||||
|
"Absorb Elements ": {
|
||||||
|
"athigherlevel": " When you cast this spell using a spell slot of 2nd level or higher, the extra damage increases by 1d6 for each slot level above 1st.",
|
||||||
|
"description": "1 Reaction, which you take when you take acid, cold, fire, lightning, or thunder damage\n\nThe spell captures some of the incoming energy, lessening its effect on you and storing it for your next melee attack. You have resistance to the triggering damage type until the start of your next turn. Also, the first time you hit with a melee attack on your next turn, the target takes an extra 1d6 damage of the triggering type, and the spell ends.\r\n",
|
||||||
|
"source": "Elemental Evil",
|
||||||
|
"level": "1",
|
||||||
|
"range": "Self",
|
||||||
|
"casting_time": "Special",
|
||||||
|
"id": "377",
|
||||||
|
"slug": "absorb-elements",
|
||||||
|
"scag": "0",
|
||||||
|
"page": "15",
|
||||||
|
"components": "S",
|
||||||
|
"ritual": "No",
|
||||||
|
"name": "Absorb Elements ",
|
||||||
|
"school": "Abjuration",
|
||||||
|
"ee": "1",
|
||||||
|
"classes": [
|
||||||
|
"Druid",
|
||||||
|
"Ranger",
|
||||||
|
"Wizard"
|
||||||
|
],
|
||||||
|
"duration": "1 round",
|
||||||
|
"concentration": "No"
|
||||||
|
},
|
||||||
|
"Acid Splash": {
|
||||||
|
"athigherlevel": "This spell's damage increases by 1d6 when you reach 5th level (2d6), 11th level (3d6), and 17th level (4d6).",
|
||||||
|
"description": "You hurl a bubble of acid. \nChoose one creature within range, or choose two creatures within range that are within 5 feet of each other. A target must succeed on a Dexterity saving throw or take 1d6 acid damage. \n\n",
|
||||||
|
"source": "Player's Handbook",
|
||||||
|
"level": "0",
|
||||||
|
"range": "60 feet",
|
||||||
|
"casting_time": "1 Action",
|
||||||
|
"id": "1",
|
||||||
|
"slug": "acid-splash",
|
||||||
|
"scag": "0",
|
||||||
|
"page": "211",
|
||||||
|
"components": "V, S",
|
||||||
|
"ritual": "No",
|
||||||
|
"name": "Acid Splash",
|
||||||
|
"school": "Conjuration",
|
||||||
|
"ee": "0",
|
||||||
|
"classes": [
|
||||||
|
"Sorcerer",
|
||||||
|
"Wizard"
|
||||||
|
],
|
||||||
|
"duration": "Instantaneous",
|
||||||
|
"concentration": "No"
|
||||||
|
},
|
||||||
|
"Aganazzar's Scorcher": {
|
||||||
|
"athigherlevel": " When you cast this spell using a spell slot of 3rd level or higher, the damage increases by 1d8 for each slot level above 2nd.",
|
||||||
|
"description": "A line of roaring flame 30 feet long and 5 feet wide emanates from you in a direction you choose. Each creature in the line must make a Dexterity saving throw. A creature takes 3d8 fire damage on a failed save, or half as much damage on a successful one.\r\n",
|
||||||
|
"source": "Elemental Evil",
|
||||||
|
"level": "2",
|
||||||
|
"range": "30 feet",
|
||||||
|
"casting_time": "1 Action",
|
||||||
|
"id": "399",
|
||||||
|
"slug": "aganazzars-scorcher",
|
||||||
|
"scag": "0",
|
||||||
|
"page": "15",
|
||||||
|
"components": "V, S, M",
|
||||||
|
"ritual": "No",
|
||||||
|
"name": "Aganazzar's Scorcher",
|
||||||
|
"school": "Evocation",
|
||||||
|
"ee": "1",
|
||||||
|
"classes": [
|
||||||
|
"Sorcerer",
|
||||||
|
"Wizard"
|
||||||
|
],
|
||||||
|
"duration": "Instantaneous",
|
||||||
|
"concentration": "No"
|
||||||
|
},
|
||||||
|
"Aid": {
|
||||||
|
"athigherlevel": "When you cast this spell using a spell slot of 3rd level or higher, a target's hit points increase by an additional 5 for each slot level above 2nd.",
|
||||||
|
"description": "Your spell bolsters your allies with toughness and resolve. \nChoose up to three creatures within range. Each target's hit point maximum and current hit points increase by 5 for the duration.",
|
||||||
|
"source": "Player's Handbook",
|
||||||
|
"level": "2",
|
||||||
|
"range": "30 feet",
|
||||||
|
"casting_time": "1 Action",
|
||||||
|
"id": "2",
|
||||||
|
"slug": "aid",
|
||||||
|
"scag": "0",
|
||||||
|
"page": "211",
|
||||||
|
"components": "V, S, M (a tiny strip of white cloth)",
|
||||||
|
"ritual": "No",
|
||||||
|
"name": "Aid",
|
||||||
|
"school": "Abjuration",
|
||||||
|
"ee": "0",
|
||||||
|
"classes": [
|
||||||
|
"Cleric",
|
||||||
|
"Paladin"
|
||||||
|
],
|
||||||
|
"duration": "8 hours",
|
||||||
|
"concentration": "No"
|
||||||
|
},
|
||||||
|
"Alarm (Ritual)": {
|
||||||
|
"athigherlevel": "",
|
||||||
|
"description": "You set an alarm against unwanted intrusion. \r\nChoose a door, a window, or an area within range that is no larger than a 20-foot cube. Until the spell ends, an alarm alerts you whenever a tiny or larger creature touches or enters the warded area. When you cast the spell, you can designate creatures that won't set off the alarm. You also choose whether the alarm is mental or audible. \n\nA mental alarm alerts you with a ping in your mind if you are within 1 mile of the warded area. This ping awakens you if you are sleeping. \r\nAn audible alarm produces the sound of a hand bell for 10 seconds within 60 feet.",
|
||||||
|
"source": "Player's Handbook",
|
||||||
|
"level": "1",
|
||||||
|
"range": "30 feet",
|
||||||
|
"casting_time": "1 Minute",
|
||||||
|
"id": "3",
|
||||||
|
"slug": "alarm-ritual",
|
||||||
|
"scag": "0",
|
||||||
|
"page": "211",
|
||||||
|
"components": "V, S, M (a tiny bell and a piece of fine silver wire)",
|
||||||
|
"ritual": "Yes",
|
||||||
|
"name": "Alarm (Ritual)",
|
||||||
|
"school": "Abjuration",
|
||||||
|
"ee": "0",
|
||||||
|
"classes": [
|
||||||
|
"Ranger",
|
||||||
|
"Wizard"
|
||||||
|
],
|
||||||
|
"duration": "8 hours",
|
||||||
|
"concentration": "No"
|
||||||
|
},
|
||||||
|
"Alter Self": {
|
||||||
|
"athigherlevel": "",
|
||||||
|
"description": "You assume a different form.\r\nWhen you cast the spell, choose one of the following options, the effects of which last for the duration of the spell. While the spell lasts, you can end one option as an action to gain the benefits of a different one.\r\n\r\nAquatic Adaptation. You adapt your body to an aquatic environment, sprouting gills, and growing webbing between your fingers. You can breathe underwater and gain a swimming speed equal to your walking speed.\r\n\r\nChange Appearance. You transform your appearnce. You decide what you look like, including your height, weight, facial features, sound of your voice, hair length, coloration, and distinguishing characteristics, if any. \r\nYou can make yourself appear as a member of another race, though none of your statistics change. You also don't appear as a creature of a different size than you, and your basic shape stays the same; if you're bipedal, you can't use this spell to become quadrupedal, for instance. At any time for the duration of the spell, you can use your action to change your appearance in this way again.\r\n\n Natural Weapons. You grow claws, fangs, spines, horns, or a different natural weapon of your choice. Your unarmed strikes deal 1d6 bludgeoning, piercing, or slashing damage, as appropriate to the natural weapon you chose, and you are proficient with your unarmed strikes. Finally, the natural weapon is magic and you have a +1 bonus to the attack and damage rolls you make using it.",
|
||||||
|
"source": "Player's Handbook",
|
||||||
|
"level": "2",
|
||||||
|
"range": "Self",
|
||||||
|
"casting_time": "1 Action",
|
||||||
|
"id": "4",
|
||||||
|
"slug": "alter-self",
|
||||||
|
"scag": "0",
|
||||||
|
"page": "211",
|
||||||
|
"components": "V, S",
|
||||||
|
"ritual": "No",
|
||||||
|
"name": "Alter Self",
|
||||||
|
"school": "Transmutation",
|
||||||
|
"ee": "0",
|
||||||
|
"classes": [
|
||||||
|
"Sorcerer",
|
||||||
|
"Wizard"
|
||||||
|
],
|
||||||
|
"duration": "Concentration, up to 1 hour",
|
||||||
|
"concentration": "Yes"
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
const _ = require('lodash');
|
|
||||||
const router = require('express').Router();
|
|
||||||
const vitreumRender = require('vitreum/steps/render');
|
|
||||||
const templateFn = require('../client/template.js');
|
|
||||||
const config = require('nconf');
|
|
||||||
const Moment = require('moment');
|
|
||||||
|
|
||||||
const mw = require('./middleware.js');
|
|
||||||
const BrewData = require('./brew.data.js');
|
|
||||||
|
|
||||||
|
|
||||||
const getInvalidBrewQuery = ()=>{
|
|
||||||
return BrewData.model.find({
|
|
||||||
'$where' : "this.text.length < 140",
|
|
||||||
createdAt: {
|
|
||||||
$lt: Moment().subtract(3, 'days').toDate()
|
|
||||||
}
|
|
||||||
}).select({ text : false });
|
|
||||||
}
|
|
||||||
|
|
||||||
router.get('/admin', mw.adminLogin, (req, res, next) => {
|
|
||||||
return vitreumRender('admin', templateFn, {
|
|
||||||
url : req.originalUrl,
|
|
||||||
admin_key : config.get('admin:key')
|
|
||||||
})
|
|
||||||
.then((page) => {
|
|
||||||
return res.send(page)
|
|
||||||
})
|
|
||||||
.catch(next);
|
|
||||||
});
|
|
||||||
|
|
||||||
//Removes all empty brews that are older than 3 days and that are shorter than a tweet
|
|
||||||
router.get('/admin/invalid', mw.adminOnly, (req, res, next)=>{
|
|
||||||
getInvalidBrewQuery().exec()
|
|
||||||
.then((brews) => {
|
|
||||||
return res.json(brews);
|
|
||||||
})
|
|
||||||
.catch(next);
|
|
||||||
});
|
|
||||||
router.delete('/admin/invalid', mw.adminOnly, (req, res, next)=>{
|
|
||||||
getInvalidBrewQuery().remove()
|
|
||||||
.then(()=>{
|
|
||||||
return res.status(200).send();
|
|
||||||
})
|
|
||||||
.catch(next);
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get('/admin/lookup/:search', mw.adminOnly, (req, res, next) => {
|
|
||||||
BrewData.get({ $or:[
|
|
||||||
//Searches for partial matches on either edit or share
|
|
||||||
{editId : { "$regex": req.params.search, "$options": "i" }},
|
|
||||||
{shareId : { "$regex": req.params.search, "$options": "i" }},
|
|
||||||
]})
|
|
||||||
.then((brews) => {
|
|
||||||
return res.json(brews);
|
|
||||||
})
|
|
||||||
.catch(next);
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = router;
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
const config = require('nconf');
|
|
||||||
const express = require("express");
|
|
||||||
const app = express();
|
|
||||||
|
|
||||||
app.use(express.static(__dirname + '/../build'));
|
|
||||||
app.use(require('body-parser').json({limit: '25mb'}));
|
|
||||||
app.use(require('cookie-parser')());
|
|
||||||
|
|
||||||
|
|
||||||
//Middleware
|
|
||||||
const mw = require('./middleware.js');
|
|
||||||
app.use(mw.account);
|
|
||||||
app.use(mw.admin);
|
|
||||||
|
|
||||||
|
|
||||||
//Routes
|
|
||||||
app.use(require('./brew.api.js'));
|
|
||||||
app.use(require('./interface.routes.js'));
|
|
||||||
app.use(require('./admin.routes.js'));
|
|
||||||
|
|
||||||
if(config.get('NODE_ENV') !== 'staging' && config.get('NODE_ENV') !== 'production'){
|
|
||||||
app.use(require('./dev.routes.js'));
|
|
||||||
}
|
|
||||||
|
|
||||||
//Error Handler
|
|
||||||
app.use(require('./error.js').expressHandler);
|
|
||||||
|
|
||||||
module.exports = app;
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
const _ = require('lodash');
|
|
||||||
const router = require('express').Router();
|
|
||||||
|
|
||||||
const BrewData = require('./brew.data.js');
|
|
||||||
const mw = require('./middleware.js');
|
|
||||||
|
|
||||||
//Search
|
|
||||||
router.get('/api/brew', (req, res, next) => {
|
|
||||||
const opts = _.pick(req.query, ['limit', 'sort', 'page']);
|
|
||||||
|
|
||||||
BrewData.termSearch(req.query.terms, opts, req.admin)
|
|
||||||
.then((result) => {
|
|
||||||
return res.status(200).json(result);
|
|
||||||
})
|
|
||||||
.catch(next);
|
|
||||||
});
|
|
||||||
|
|
||||||
//User
|
|
||||||
router.get('/api/user/:username', (req, res, next) => {
|
|
||||||
const fullAccess = req.admin ||
|
|
||||||
!!(req.account && req.params.username == req.account.username);
|
|
||||||
|
|
||||||
BrewData.userSearch(req.params.username, fullAccess)
|
|
||||||
.then((result) => {
|
|
||||||
return res.status(200).json(result);
|
|
||||||
})
|
|
||||||
.catch(next);
|
|
||||||
});
|
|
||||||
|
|
||||||
//Get
|
|
||||||
router.get('/api/brew/:shareId', mw.viewBrew, (req, res, next) => {
|
|
||||||
return res.json(req.brew.toJSON());
|
|
||||||
});
|
|
||||||
|
|
||||||
//Create
|
|
||||||
router.post('/api/brew', (req, res, next)=>{
|
|
||||||
const brew = req.body;
|
|
||||||
if(req.account) brew.authors = [req.account.username];
|
|
||||||
BrewData.create(brew)
|
|
||||||
.then((brew) => {
|
|
||||||
return res.json(brew.toJSON());
|
|
||||||
})
|
|
||||||
.catch(next)
|
|
||||||
});
|
|
||||||
|
|
||||||
//Update
|
|
||||||
router.put('/api/brew/:editId', mw.loadBrew, (req, res, next)=>{
|
|
||||||
const brew = req.body || {};
|
|
||||||
if(req.account){
|
|
||||||
brew.authors = _.uniq(_.concat(brew.authors, req.account.username));
|
|
||||||
}
|
|
||||||
BrewData.update(req.params.editId, brew)
|
|
||||||
.then((brew) => {
|
|
||||||
return res.json(brew.toJSON());
|
|
||||||
})
|
|
||||||
.catch(next);
|
|
||||||
});
|
|
||||||
|
|
||||||
//Delete
|
|
||||||
router.delete('/api/brew/:editId', mw.loadBrew, (req, res, next) => {
|
|
||||||
BrewData.remove(req.params.editId)
|
|
||||||
.then(()=>{
|
|
||||||
return res.sendStatus(200);
|
|
||||||
})
|
|
||||||
.catch(next);
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = router;
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
const _ = require('lodash');
|
|
||||||
const shortid = require('shortid');
|
|
||||||
const mongoose = require('./db.js').instance;
|
|
||||||
|
|
||||||
const Error = require('./error.js');
|
|
||||||
const utils = require('./utils.js');
|
|
||||||
|
|
||||||
const BrewSchema = mongoose.Schema({
|
|
||||||
shareId : {type : String, default: shortid.generate, index: { unique: true }},
|
|
||||||
editId : {type : String, default: shortid.generate, index: { unique: true }},
|
|
||||||
|
|
||||||
text : {type : String, default : ""},
|
|
||||||
style : {type : String, default : ""},
|
|
||||||
|
|
||||||
title : {type : String, default : ""},
|
|
||||||
description : {type : String, default : ""},
|
|
||||||
tags : {type : String, default : ""},
|
|
||||||
thumbnail : {type : String, default : ""},
|
|
||||||
systems : [String],
|
|
||||||
authors : [String],
|
|
||||||
published : {type : Boolean, default : false},
|
|
||||||
|
|
||||||
createdAt : { type: Date, default: Date.now },
|
|
||||||
updatedAt : { type: Date, default: Date.now},
|
|
||||||
lastViewed : { type: Date, default: Date.now},
|
|
||||||
views : {type:Number, default:0},
|
|
||||||
version : {type: Number, default:2}
|
|
||||||
}, {
|
|
||||||
versionKey: false,
|
|
||||||
toJSON : {
|
|
||||||
transform: (doc, ret, options) => {
|
|
||||||
delete ret._id;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//Index these fields for fast text searching
|
|
||||||
BrewSchema.index({ title: "text", description: "text" });
|
|
||||||
|
|
||||||
BrewSchema.methods.increaseView = function(){
|
|
||||||
this.views = this.views + 1;
|
|
||||||
return this.save();
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
const Brew = mongoose.model('Brew', BrewSchema);
|
|
||||||
const BrewData = {
|
|
||||||
schema : BrewSchema,
|
|
||||||
model : Brew,
|
|
||||||
|
|
||||||
get : (query) => {
|
|
||||||
return Brew.findOne(query).exec()
|
|
||||||
.then((brew) => {
|
|
||||||
if(!brew) throw Error.noBrew();
|
|
||||||
return brew;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
create : (brew) => {
|
|
||||||
delete brew.shareId;
|
|
||||||
delete brew.editId;
|
|
||||||
if(!brew.title) brew.title = utils.getGoodBrewTitle(brew.text);
|
|
||||||
return (new Brew(brew)).save();
|
|
||||||
},
|
|
||||||
update : (editId, newBrew) => {
|
|
||||||
return BrewData.get({ editId })
|
|
||||||
.then((brew) => {
|
|
||||||
delete newBrew.shareId;
|
|
||||||
delete newBrew.editId;
|
|
||||||
brew = _.merge(brew, newBrew, { updatedAt : Date.now() });
|
|
||||||
|
|
||||||
brew.markModified('authors');
|
|
||||||
brew.markModified('systems');
|
|
||||||
return brew.save();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
remove : (editId) => {
|
|
||||||
return Brew.find({ editId }).remove().exec();
|
|
||||||
},
|
|
||||||
removeAll : ()=>{
|
|
||||||
return Brew.find({}).remove().exec();
|
|
||||||
},
|
|
||||||
|
|
||||||
//////// Special
|
|
||||||
|
|
||||||
getByShare : (shareId) => {
|
|
||||||
return BrewData.get({ shareId : shareId})
|
|
||||||
.then((brew) => {
|
|
||||||
brew.increaseView();
|
|
||||||
const brewJSON = brew.toJSON();
|
|
||||||
delete brewJSON.editId;
|
|
||||||
return brewJSON;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
getByEdit : (editId) => {
|
|
||||||
return BrewData.get({ editId });
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const BrewSearch = require('./brew.search.js')(Brew);
|
|
||||||
|
|
||||||
module.exports = _.merge(BrewData, BrewSearch);
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
const _ = require('lodash');
|
|
||||||
|
|
||||||
module.exports = (Brew) => {
|
|
||||||
const cmds = {
|
|
||||||
termSearch : (terms='', opts, fullAccess) => {
|
|
||||||
let query = {};
|
|
||||||
if(terms){
|
|
||||||
query = {$text: {
|
|
||||||
//Wrap terms in quotes to perform an AND operation
|
|
||||||
$search: _.map(terms.split(' '), (term)=>{
|
|
||||||
return `\"${term}\"`;
|
|
||||||
}).join(' '),
|
|
||||||
$caseSensitive : false
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
return cmds.search(query, opts, fullAccess);
|
|
||||||
},
|
|
||||||
|
|
||||||
userSearch : (username, fullAccess) => {
|
|
||||||
const query = {
|
|
||||||
authors : username
|
|
||||||
};
|
|
||||||
|
|
||||||
return cmds.search(query, {}, fullAccess);
|
|
||||||
},
|
|
||||||
|
|
||||||
search : (queryObj={}, options={}, fullAccess = true) => {
|
|
||||||
const opts = _.merge({
|
|
||||||
limit : 25,
|
|
||||||
page : 0,
|
|
||||||
sort : {}
|
|
||||||
}, options);
|
|
||||||
opts.limit = _.toNumber(opts.limit);
|
|
||||||
opts.page = _.toNumber(opts.page);
|
|
||||||
|
|
||||||
let filter = {
|
|
||||||
text : 0
|
|
||||||
};
|
|
||||||
|
|
||||||
if(!fullAccess){
|
|
||||||
filter.editId = 0;
|
|
||||||
queryObj.published = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const searchQuery = Brew
|
|
||||||
.find(queryObj)
|
|
||||||
.sort(opts.sort)
|
|
||||||
.select(filter)
|
|
||||||
.limit(opts.limit)
|
|
||||||
.skip(opts.page * opts.limit)
|
|
||||||
.lean()
|
|
||||||
.exec();
|
|
||||||
|
|
||||||
const countQuery = Brew.count(queryObj).exec();
|
|
||||||
|
|
||||||
return Promise.all([searchQuery, countQuery])
|
|
||||||
.then((result) => {
|
|
||||||
return {
|
|
||||||
brews : result[0],
|
|
||||||
total : result[1]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return cmds;
|
|
||||||
};
|
|
||||||
36
server/db.js
36
server/db.js
@@ -1,36 +0,0 @@
|
|||||||
const log = require('loglevel');
|
|
||||||
const mongoose = require('mongoose');
|
|
||||||
mongoose.Promise = Promise;
|
|
||||||
|
|
||||||
const dbPath = process.env.MONGODB_URI || process.env.MONGOLAB_URI || 'mongodb://localhost/homebrewery';
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
connect : ()=>{
|
|
||||||
return new Promise((resolve, reject)=>{
|
|
||||||
if(mongoose.connection.readyState !== 0){
|
|
||||||
log.warn('DB already connected');
|
|
||||||
return resolve();
|
|
||||||
}
|
|
||||||
mongoose.connect(dbPath,
|
|
||||||
(err) => {
|
|
||||||
if(err){
|
|
||||||
log.error('Error : Could not connect to a Mongo Database.');
|
|
||||||
log.error(' If you are running locally, make sure mongodb.exe is running.');
|
|
||||||
return reject(err);
|
|
||||||
}
|
|
||||||
log.info('DB connected.');
|
|
||||||
return resolve();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
close : ()=>{
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
mongoose.connection.close(()=>{
|
|
||||||
log.info('DB connection closed.');
|
|
||||||
return resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
instance : mongoose
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
const router = require('express').Router();
|
|
||||||
const jwt = require('jwt-simple');
|
|
||||||
const auth = require('basic-auth');
|
|
||||||
const config = require('nconf');
|
|
||||||
|
|
||||||
if(process.env.NODE_ENV == 'production') throw 'Loading dev routes in prod. ABORT ABORT';
|
|
||||||
|
|
||||||
|
|
||||||
router.get('/dev/login', (req, res, next) => {
|
|
||||||
const user = req.query.user;
|
|
||||||
if(!user){
|
|
||||||
return res.send(`
|
|
||||||
<html>
|
|
||||||
<body>dev login</body>
|
|
||||||
<script>
|
|
||||||
var user = prompt('enter username');
|
|
||||||
if(user) window.location = '/dev/login?user=' + encodeURIComponent(user);
|
|
||||||
</script></html>
|
|
||||||
`);
|
|
||||||
}
|
|
||||||
res.cookie('nc_session', jwt.encode({username : req.query.user}, config.get('jwt_secret')));
|
|
||||||
return res.redirect('/');
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = router;
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
|
|
||||||
const Error = require('egads').extend('Server Error', 500, 'Generic Server Error');
|
|
||||||
|
|
||||||
Error.noBrew = Error.extend('Can not find a brew with that id', 404, 'No Brew Found');
|
|
||||||
Error.noAuth = Error.extend('You can not access this route', 401, 'Unauthorized');
|
|
||||||
Error.noAdmin = Error.extend('You need admin credentials to access this route', 401, 'Unauthorized');
|
|
||||||
|
|
||||||
|
|
||||||
Error.expressHandler = (err, req, res, next) => {
|
|
||||||
if(err instanceof Error){
|
|
||||||
return res.status(err.status).send({
|
|
||||||
type : err.name,
|
|
||||||
message : err.message
|
|
||||||
});
|
|
||||||
}
|
|
||||||
//If server error, print the whole stack for debugging
|
|
||||||
return res.status(500).send({
|
|
||||||
message : err.message,
|
|
||||||
stack : err.stack
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = Error;
|
|
||||||
122
server/homebrew.api.js
Normal file
122
server/homebrew.api.js
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
var _ = require('lodash');
|
||||||
|
var Moment = require('moment');
|
||||||
|
var HomebrewModel = require('./homebrew.model.js').model;
|
||||||
|
|
||||||
|
var homebrewTotal = 0;
|
||||||
|
var refreshCount = function(){
|
||||||
|
HomebrewModel.count({}, function(err, total){
|
||||||
|
homebrewTotal = total;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
refreshCount()
|
||||||
|
|
||||||
|
var mw = {
|
||||||
|
adminOnly : function(req, res, next){
|
||||||
|
if(req.query && req.query.admin_key == process.env.ADMIN_KEY){
|
||||||
|
next();
|
||||||
|
}else{
|
||||||
|
return res.status(401).send('Access denied');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
var getTopBrews = function(cb){
|
||||||
|
HomebrewModel.find().sort({views: -1}).limit(5).exec(function(err, brews) {
|
||||||
|
cb(brews);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = function(app){
|
||||||
|
|
||||||
|
app.get('/homebrew/top', function(req, res){
|
||||||
|
getTopBrews(function(topBrews){
|
||||||
|
return res.json(topBrews);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post('/homebrew/api', function(req, res){
|
||||||
|
var newHomebrew = new HomebrewModel(req.body);
|
||||||
|
newHomebrew.save(function(err, obj){
|
||||||
|
if(err) return;
|
||||||
|
return res.json(obj);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
app.put('/homebrew/api/update/:id', function(req, res){
|
||||||
|
HomebrewModel.find({editId : req.params.id}, function(err, objs){
|
||||||
|
if(!objs.length || err) return res.status(404).send("Can not find homebrew with that id");
|
||||||
|
var resEntry = objs[0];
|
||||||
|
resEntry.text = req.body.text;
|
||||||
|
resEntry.title = req.body.title;
|
||||||
|
resEntry.updatedAt = new Date();
|
||||||
|
resEntry.save(function(err, obj){
|
||||||
|
if(err) return res.status(500).send("Error while saving");
|
||||||
|
return res.status(200).send(obj);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/homebrew/api/remove/:id', function(req, res){
|
||||||
|
HomebrewModel.find({editId : req.params.id}, function(err, objs){
|
||||||
|
if(!objs.length || err) return res.status(404).send("Can not find homebrew with that id");
|
||||||
|
var resEntry = objs[0];
|
||||||
|
resEntry.remove(function(err){
|
||||||
|
if(err) return res.status(500).send("Error while removing");
|
||||||
|
return res.status(200).send();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
//Removes all empty brews that are older than 3 days and that are shorter than a tweet
|
||||||
|
app.get('/homebrew/api/invalid', mw.adminOnly, function(req, res){
|
||||||
|
var invalidBrewQuery = HomebrewModel.find({
|
||||||
|
'$where' : "this.text.length < 140",
|
||||||
|
createdAt: {
|
||||||
|
$lt: Moment().subtract(3, 'days').toDate()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if(req.query.do_it){
|
||||||
|
invalidBrewQuery.remove().exec((err, objs)=>{
|
||||||
|
refreshCount();
|
||||||
|
return res.send(200);
|
||||||
|
})
|
||||||
|
}else{
|
||||||
|
invalidBrewQuery.exec((err, objs)=>{
|
||||||
|
if(err) console.log(err);
|
||||||
|
return res.json({
|
||||||
|
count : objs.length
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
app.get('/homebrew/api/search', mw.adminOnly, function(req, res){
|
||||||
|
var page = req.query.page || 0;
|
||||||
|
var count = req.query.count || 20;
|
||||||
|
|
||||||
|
HomebrewModel.find({}, {
|
||||||
|
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;
|
||||||
|
}
|
||||||
24
server/homebrew.model.js
Normal file
24
server/homebrew.model.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
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 : ""},
|
||||||
|
|
||||||
|
createdAt : { type: Date, default: Date.now },
|
||||||
|
updatedAt : { type: Date, default: Date.now},
|
||||||
|
lastViewed : { type: Date, default: Date.now},
|
||||||
|
views : {type:Number, default:0}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var Homebrew = mongoose.model('Homebrew', HomebrewSchema);
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
schema : HomebrewSchema,
|
||||||
|
model : Homebrew,
|
||||||
|
}
|
||||||
126
server/homebrew.server.js
Normal file
126
server/homebrew.server.js
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
var _ = require('lodash');
|
||||||
|
var vitreumRender = require('vitreum/render');
|
||||||
|
var HomebrewModel = require('./homebrew.model.js').model;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = function(app){
|
||||||
|
|
||||||
|
|
||||||
|
//Edit Page
|
||||||
|
app.get('/homebrew/edit/:id', function(req, res){
|
||||||
|
HomebrewModel.find({editId : req.params.id}, function(err, objs){
|
||||||
|
if(err || !objs.length) return res.status(404).send('Could not find Homebrew with that id');
|
||||||
|
|
||||||
|
var resObj = null;
|
||||||
|
var errObj = {text: "# oops\nCould not find the homebrew."}
|
||||||
|
if(objs.length){
|
||||||
|
resObj = objs[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
vitreumRender({
|
||||||
|
page: './build/homebrew/bundle.dot',
|
||||||
|
globals:{},
|
||||||
|
prerenderWith : './client/homebrew/homebrew.jsx',
|
||||||
|
initialProps: {
|
||||||
|
url: req.originalUrl,
|
||||||
|
brew : resObj || errObj
|
||||||
|
},
|
||||||
|
clearRequireCache : !process.env.PRODUCTION,
|
||||||
|
}, function (err, page) {
|
||||||
|
return res.send(page)
|
||||||
|
});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
//Share Page
|
||||||
|
app.get('/homebrew/share/:id', function(req, res){
|
||||||
|
HomebrewModel.find({shareId : req.params.id}, function(err, objs){
|
||||||
|
if(err || !objs.length) return res.status(404).send('Could not find Homebrew with that id');
|
||||||
|
|
||||||
|
var resObj = null;
|
||||||
|
var errObj = {text: "# oops\nCould not find the homebrew."}
|
||||||
|
|
||||||
|
if(objs.length){
|
||||||
|
resObj = objs[0];
|
||||||
|
resObj.lastViewed = new Date();
|
||||||
|
resObj.views = resObj.views + 1;
|
||||||
|
resObj.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
vitreumRender({
|
||||||
|
page: './build/homebrew/bundle.dot',
|
||||||
|
globals:{},
|
||||||
|
prerenderWith : './client/homebrew/homebrew.jsx',
|
||||||
|
initialProps: {
|
||||||
|
url: req.originalUrl,
|
||||||
|
brew : resObj || errObj
|
||||||
|
},
|
||||||
|
clearRequireCache : !process.env.PRODUCTION,
|
||||||
|
}, function (err, page) {
|
||||||
|
return res.send(page)
|
||||||
|
});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
//Print Page
|
||||||
|
var Markdown = require('marked');
|
||||||
|
var PHBStyle = '<style>' + require('fs').readFileSync('./phb.standalone.css', 'utf8') + '</style>'
|
||||||
|
app.get('/homebrew/print/:id', function(req, res){
|
||||||
|
HomebrewModel.find({shareId : req.params.id}, function(err, objs){
|
||||||
|
if(err || !objs.length) return res.status(404).send('Could not find Homebrew with that id');
|
||||||
|
|
||||||
|
var brew = null;
|
||||||
|
if(objs.length){
|
||||||
|
brew = objs[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
var content = _.map(brew.text.split('\\page'), function(pageText){
|
||||||
|
return '<div class="phb print">' + Markdown(pageText) + '</div>';
|
||||||
|
}).join('\n');
|
||||||
|
|
||||||
|
var dialog = '';
|
||||||
|
if(req.query && req.query.dialog) dialog = 'onload="window.print()"';
|
||||||
|
|
||||||
|
var title = '<title>' + brew.title + '</title>';
|
||||||
|
var page = `<html><head>${title} ${PHBStyle}</head><body ${dialog}>${content}</body></html>`
|
||||||
|
|
||||||
|
return res.send(page)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
//Source page
|
||||||
|
String.prototype.replaceAll = function(s,r){return this.split(s).join(r)}
|
||||||
|
app.get('/homebrew/source/:id', function(req, res){
|
||||||
|
HomebrewModel.find({shareId : req.params.id}, function(err, objs){
|
||||||
|
if(err || !objs.length) return res.status(404).send('Could not find Homebrew with that id');
|
||||||
|
var brew = null;
|
||||||
|
if(objs.length) brew = objs[0];
|
||||||
|
var text = brew.text.replaceAll('<', '<').replaceAll('>', '>');
|
||||||
|
return res.send(`<code><pre>${text}</pre></code>`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
//Home and 404, etc.
|
||||||
|
var welcomeText = require('fs').readFileSync('./client/homebrew/pages/homePage/welcome_msg.txt', 'utf8');
|
||||||
|
var changelogText = require('fs').readFileSync('./changelog.md', 'utf8');
|
||||||
|
app.get('/homebrew*', function (req, res) {
|
||||||
|
vitreumRender({
|
||||||
|
page: './build/homebrew/bundle.dot',
|
||||||
|
globals:{},
|
||||||
|
prerenderWith : './client/homebrew/homebrew.jsx',
|
||||||
|
initialProps: {
|
||||||
|
url: req.originalUrl,
|
||||||
|
welcomeText : welcomeText,
|
||||||
|
changelog : changelogText
|
||||||
|
},
|
||||||
|
clearRequireCache : !process.env.PRODUCTION,
|
||||||
|
}, function (err, page) {
|
||||||
|
return res.send(page)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return app;
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user